Hallo zusammen,
vorweg: das Problem tritt bei der CGI-Programmierung auf, hat meiner Meinung nach aber nichts damit zu tun (siehe ganz unten), daher bitte ggf. verschieben, wenn das CGI-Forum der falsche Ort ist.
Bei der Fehlersuche eines Skripts habe ich festgestellt, dass ein Objekt zu früh zerstört wird. Die Frage ist, wieso und wie ich das sauber verhindern kann.
Konkret betroffen ist das Zusammenspiel zwischen Apache::Session und DBI. Apache::Session bietet zum einen die Möglichkeit, selbst eine Datenbankverbindung aufzumachen, zum anderen ein bereits bestehendes DBI-Handle zu verwenden. Im ersten Fall gibt es nie Probleme, im zweiten Fall manchmal.
Für alle, die Apache::Session noch nicht verwendet habe, kurzer Überblick über die Funktionsweise:
Man bindet einen Hash an die Klasse und initiiert auf diese Weise ein Objekt. Dieses Objekt bekommt eine _session_id und wird sofort in der DB abgelegt. Über den Hash kann man auf diese _session_id und beliebige weitere Schlüssel-Wert-Paare zugreifen. Diese weiteren Schlüssel-Wert-Paare werden aber erst dann in der Datenbank abgelegt, wenn der Destruktor aufgerufen wird. In manchen Situationen wird allerdings der Destruktor von DBI zuerst aufgerufen, das Update der Session-Tabelle erfolgt nicht mehr. Ich denke, dass das kein Fehler von Apache::Session ist, sondern entweder mein eigener oder ein Fehler im Garbage Collector.
Eine Programmversion bringt bei wiederholter Ausführung scheinbar immer das gleiche Ergebnis, es ist also nicht zufällig, was passiert.
Mir geht es darum, diesen Fehler zu finden, ich habe allerdings das Problem, dass ich bisher in keinem Minimalbeispiel diesen Fehler reproduzieren konnte, obwohl das Problemskript auch schon nicht übermäßig lange ist. Wenn ich etwas annehmbar kurzes gefunden habe, dann werde ich das noch nachreichen.
Die Situation ist ungefähr die:
- das Hauptprogramm main.pl
- ein Paket Model.pm, das über einen Konstruktor die Verbindung zur Datenbank herstellt, dann das Objekt $model zurückgibt über das man dann Abfragen vornimmt. Das DBI-Handle wird auch übergeben an:
- ein Paket Session.pm, das mit einem DBI-Objekt instantiiert wird und mit einer Methode create(), die Apache::Session verwendet, um Sessions abzulegen. Es wird eine Hashreferenz, die zum Session.pm-Objekt gehört an Apache::Session::MySQL gebunden.
Nicht gerade hilfreich bei der Fehlersuche ist, dass Änderungen, die scheinbar überhaupt nichts mit dem Sachverhalt zu tun haben, dennoch das Ergebnis ändern.
1. Ausgangslage: Alles funktioniert, das übergebene DBI-Handle wird korrekt verwendet, die Destruktoren arbeiten in der richtigen Reihenfolge. Passt.
2. Ich lege eine Methode Model::foobar an:
sub foobar { my $self = shift; }
die Methode wird nicht aufgerufen, keine Änderung, alles passt.
3. Ich ändere die Methode Model::foobar ab:
sub foobar { my $self = shift; my $param = shift; }
die Methode wird im ganzen Programm nicht einmal aufgerufen, der Methodenname ist absolut egal, ebenso die Variablenbezeichnungen: Das Programm funktioniert nicht mehr korrekt, der DBI-Destruktor wird vor dem Apache::Session-Destruktor aufgerufen.
4. Es gibt mehrere Möglichkeiten, das Programm wieder zum laufen zu bekommen, aber alle sind gebastelt. Eine der folgenden Möglichkeiten reicht aus, das Programm wieder funktionstüchtig zu machen:
4a. Nach Erzeugung des Models erstelle ich im Hauptprogramm eine zusätzliche Referenz auf das DBI-Objekt.
4b. Ich verzichte darauf, dem Session-Hash Ergebnisse aus einer Datenbankabfrage zuzuweisen.
4c. Ich ergänze use CGI::Carp qw(fatalsToBrowser);
4d. Ich wende das "tie" auf einen normalen Hash an und referenziere in meinem Session.pm-Objekt auf diesen.
4e. Um den Destruktor künstlich aufzurufen, ergänze ich am Ende des Hauptprogramms ein delete( $session->{data} ). (Das ist die Referenz auf den "ge_tie_den" Hash)
4n. to be continued.
Alles in allem sind ein paar nachvollziehbare Varianten darunter und ein paar weniger nachvollziehbare. Die Lösung 4e gefällt mir bisher am besten, aber ob das eine wirklich saubere Lösung ist, weiß ich nicht und auch nicht, ob das immer funktioniert.
Der Perl-GC ist als Referenz-zählende Implementierung ja nicht so wahnsinnig aufwendig gestaltet, gibt es deswegen vielleicht Probleme? Aber eigentlich halte ich dieses Problem für ein Standardproblem, hat eigentlich nichts mit CGI oder Apache::Session oder DBI zu tun. Daher zweifle ich eher an meinem Programmcode, der aber zu wenig komplex ist, um grundsätzliche Denkfehler zu vermuten.
Tjo, doch etwas länger geworden, schönen Dank fürs Lesen.
Viele Grüße
Martin
Edit: Wodurch könnte das Problem begründet sein und wo könnte ich ansetzen, um nicht nur die Symptome zu beheben (explizite DESTROY-Auslösung durch delete()-Aufruf), sondern die Ursache zu finden (möglicherweise ein Problem mit meinen Referenzen?)? Ist vielleicht irgendwo das Überschreiben/Ergänzen eines Destruktors notwendig? Meiner Meinung nach eher nicht, denn eine Referenz des Model-Objekts wird vom Session-Objekt verwendet und nicht umgekehrt. Ich kann das zerstören des Models also eigentlich nicht hinauszögern.
Nochwas: Zwischenzeitlich hatte ich in den verwendeten CPAN-Modulen Debug-Ausgaben eingebaut, die mir durch die Ausgabe auf STDERR angezeigt haben, in welcher Reihenfolge die Destruktoren sowie Folge-Methoden abgearbeitet werden. Dabei ist mir aufgefallen, dass im Fehlerfall teilweise Überschneidungen da waren, die Ausführung also sehr zeitnah gewesen sein muss.