Py_DECREF sucks

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Hallo,
ich habe mal wieder Probleme mit PY_DECREF.
nachdem meine Extension beendet wurde gibt Pytheon noch die Grafik aus und stürzt dann ab.
wenn ich alle Py_DECREFS auskommentiere läufts.
Jetzt darf ich mal wieder RATEN wo Referenzen aufgelöst werden müssen, um keine Speicherlecks entstehen zu lassen und wo sie bestehen bleiben müssen.

Wenn ich in der Extension in einer Schleife embedded Python habe und den Rückgabewert aus dem Aufruf immer dem gleichen PyObject zuweise, wird die Referenz dann jedesmal neu angelegt oder überschrieben?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

wenn du raten musst, ob du den Referenzzähler anpassen musst oder nicht, dann hast du beim Programmieren eindeutig etwas falsch verstanden. Natürlich wird dein Programm immer funktionieren, wenn du alle DECREFs rausnimmst, da alle Objekte dann zu jeder Zeit gültig sind und du niemals auf ungültigen Speicher zugreifen wirst. Händische Speicherverwaltung lässt sich nur mit genauer Kenntnis der Dokumentation, Wissen über den Programmablauf und viel Disziplin korrekt erledigen.

Sebastian
Das Leben ist wie ein Tennisball.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

beim einmaligen Aufruf konnte ich das am Ende der Schleife stehende "Py_DECREF(PyCallMethod)" aufrufen, hier führt es zum absturz wenn ich es nicht auskommentiere. mit den Listen des Koordinatensystems "Abscissa" und "Ordinate" arbeite ich noch weiter. (beim bisherigen einmaligen Aufruf habe ich auch den Rückgabewert nicht mehr benötigt, aber warum dartf ich "PyCallMethod" nicht freigeben? PyRetval zeigt doch auf das gleiche Objekt?)

Code: Alles auswählen

// Call a function in another Module
	PyObject 	*PyRetVal = NULL, \
				*PyCallObj = NULL, \
				*PyCallDict = NULL, \
				*PyCallModule = NULL, \
				*PyCallMethod = NULL;

	PyCallDict = PyDict_New();
	PyAbscissaList = PyList_New(frequencySteps + 1);
	PyOrdinateList = PyList_New(frequencySteps + 1);

	sprintf(StrBuf, "%ld", times);
	PyDict_SetItem(PyCallDict, PyString_FromString("times"), PyString_FromString(StrBuf));
	sprintf(StrBuf, "%s", AVGCN0_KEY);
	PyDict_SetItem(PyCallDict, PyString_FromString("AvgCN0Key"), PyString_FromString(StrBuf));
	PyCallObj = Py_BuildValue("OOs", PyDevice, PyCallDict, FilePath);

	PyCallModule = PyImport_ImportModule("Device.Name.Ext");

	for (i = 0; i <= frequencySteps; i++){
		fprintf (ResultFile, "\n%s\t%f\n", FREQ_KEY, frequencyHz);

		if(PyCallModule){
			fclose (ResultFile);
			PyCallDict = PyModule_GetDict(PyCallModule);
			PyCallMethod = PyDict_GetItemString(PyCallDict, "GPS_CN0Avg");
		}
		else{
			printf("Error: PyModule \"Name.Ext\" not found\n");
		}
		if (PyCallable_Check(PyCallMethod)){
			PyRetVal = PyObject_CallObject(PyCallMethod, PyCallObj);
			if (PyDict_Check(PyRetVal)){
				if (PyDict_Contains(PyRetVal, PyString_FromString(VAL_KEY))){
					PyList_SET_ITEM(PyOrdinateList, i, PyDict_GetItem(PyRetVal, PyString_FromString(VAL_KEY)));
					PyList_SET_ITEM(PyAbscissaList, i, PyFloat_FromDouble((double)frequencyHz));
				}
			}
		}
		else{
			printf("Error: PyMethod \"MethodeName\" not found\n");
		}
		ResultFile = fopen (FilePath, "a");
	}
	//Py_DECREF(PyCallMethod);
	Py_DECREF(PyCallModule);
	Py_DECREF(PyCallObj);
	Py_DECREF(PyCallDict);
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

natürlich ist es raten, wenn ein programm ohne fehlermeldung abschmiert.
BlackJack

@hypnoticum: Das ist schon wieder alles so ohne wirkliche Fehlerbehandlung. Das ist kein robuster Code, da können noch ganz andere schlimme Dinge passieren. Zum Beispiel stellst Du in keiner Weise sicher, dass die Listen am Ende auch an jedem Index ein gültiges Python-Objekt stehen haben.

Deine letzte Frage im ersten Beitrag stellt sich so gar nicht weil Du nirgends `PyObject`\s etwas zuweist, sondern immer nur mit Zeigern auf solche Strukturen arbeitest. Du überschreibst also nie ein `PyObject`. Wenn Du allerdings wiederholt einem Zeiger `PyObject`\s zuweist und dabei einen vorherigen Zeiger auf so ein Objekt überschreibst auf das Du nicht anderweitig noch heran kommst, und für das Du die Verantwortung hast den Zähler zu verringern wenn Du es nicht mehr benötigst, dann hast Du an der Stelle ein Speicherleck. *Ob* Du diese Verantwortung hast, hängt davon ab auf welchem Weg Du an den Zeiger gekommen bist. Da musst Du aufmerksam die Dokumentation lesen, wie EyDu ja schon gesagt hat.

Deine Namen sind IMHO übrigens sehr verwirrend.

Der Programmablauf erscheint auch wenig durchdacht. Vor der Schleife wird zum Beispiel `PyCallModule` etwas zugewiesen, was dann erst *in* der Schleife bei jedem Durchlauf getestet wird, ob das überhaupt erfolgreich war. Falls es das nicht wahr wird an Python komplett vorbei eine Fehlermeldung ausgegeben und es wird danach dann trotzdem geprüft ob `PyCallMethod` — was in diesem Fall gar nicht an einen gültigen Wert gebunden sein *kann* — etwas aufrufbares ist. Falls das nicht schon abstürzt geht dieses Spielchen fröhlich alle Schleifendurchläufe weiter, wobei immer neue Dateien geöffnet werden (`ResultFile`), die nicht wieder geschlossen werden. Das ist Murks.

Ein Speicherleck hast Du da wohl bei `PyRetVal`. Und beim innersten ``if`` beim `PyString_FromString()`-Rückgabewert wohl auch.

Warum Du `PyCallMethod` nicht `PY_DECREF`\fen darfst steht in der Dokumentation zu `PyDict_GetItemString()`. So ziemlich bei jeder Funktion die einen Zeiger auf ein `PyObject` liefert, steht wem die Referenz gehört.

Auch das hier ist wieder ein Quelltext bei dem ich nicht verstehe warum der in C geschrieben sein muss. Gerade weil Du offensichtlich nicht so gut programmieren kannst, hättest Du bei Python den Vorteil das Du keine Speicherverwaltung betreiben musst, und dass das Programm bei Fehlern viel sicherer, wohldefiniert, und sauberer reagiert. Und das auch noch mit einem ordentlichen Stacktrace der bei der Fehlersuche hilft, statt einfach so hart abzustürzen.

Es ist kein raten wenn man bei einem Absturz den Quelltext systematisch nach Fehlern durchsucht. Ich habe den ziemlich zielsicher gefunden, in dem ich mir angeschaut habe wo dem Zeiger etwas zugewiesen wird und was die Dokumentation zu der Funktion an der Stelle zu sagen hat. Das hat nichts mit raten zu tun.

Edit: Um das mit der Sprache noch einmal zu illustrieren habe ich auch den Quelltext mal in Python formuliert:

Code: Alles auswählen

from device.name import ext

# ...

     a_dict = {
        'times': str(times),
        'AvgCN0Key': AVGCN0_KEY,
    }
    abscissas = list()
    ordinates = list()
    for _ in xrange(frequency_steps + 1):
        result_file.write('\n%s\t%f\n' % (FREQ_KEY, frequency))
        abscissas.append(frequency)
        ordinates.append(ext.GPS_CN0Avg(device, a_dict, filepath)[VAL_KEY])
Keine Speicherlecks und keine Abstürze die schwer zu finden sind, dafür aber wesentlich weniger Quelltext.

Beim „Übersetzen“ habe ich übrigens noch ein Speicherleck entdeckt: `PyCallDict`.
scoder
User
Beiträge: 13
Registriert: Freitag 4. Februar 2011, 19:04

Ernsthaft - du willst Cython. Dann kannst du dir das ganze Herumgedebugge an Ref-Leaks und Ref-Crashes erstmal sparen.
BlackJack

@scoder: hypnoticum hat rund um den Themenkomplex „C-Erweiterungen für Python schreiben” diverse Themen eröffnet und das er das eigentlich grösstenteils in Python hätte schreiben sollen habe ich mehrfach erwähnt und Cython für die C-Code-Anbindung mindestens einmal, wenn nicht öfter. Aber das wurde genau so ignoriert wie der mehrfache Hinweis, das Fehlerbehandlung wichtig ist.
Antworten