Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – III

Mit diesem Beitrag setze ich meine kleine Serie zu lang laufenden PHP-“RUN”s und asynchron gestarteten “CHECKER”-Programmen für Ajax-basierte Statusüberprüfungen des “RUN”s fort. Siehe:
Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – I
Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – II

Wir hatten bereits diskutiert, dass beide Jobs von ein und derselben Webseite über Ajax-Transaktionen zu starten und zu managen sind. Im besonderen ist das Status-Polling durch periodisches Starten eines “CHECKER”-Jobs zu erledigen. Das Starten erfolgt über eine periodische, jeweils nur kurze Zeit dauernde Ajax-Interaktion mit dem Server.

In beiden Fällen – beim einmaligen Start des RUNs und beim periodischen Start des CHECKERS – sind i.d.R. einige Daten zum Server zu übertragen. Diese Daten kann man z.B. in unsichtbaren Feldern eines Formulars hinterlegen und vor dem Ajax-Transfer serialisieren.

Im letzten Beitrag hatte ich bereits angedeutet, dass es im Zusammenhang mit der klaren Strukturierung der parallelen Ajax-Handhabung von mehreren Formularen folgende Vorgehensweisen sinnvoll sind:

  1. Jedes HTML Formular für einen per Ajax zu startenden Job wird auf der Javascript Seite vollständig auf ein zugehöriges, spezifisches Kontroll-Objekt “K” abgebildet. Definierte Methoden dieses Kontroll-Objektes übernehmen nicht nur die Serialisierung der asynchron zum Server zu transferierenden Daten, sondern kümmern sich auch um einen angemessene Reaktion auf Ajax-Rückmeldungen des Servers und übergeben bei Bedarf Teile der Server-Antwort an bestimmte HTML-Objekte (bzw. weitere zugehörige, abstrakte JS-Kontroll-Objekte).
  2. Um Submit-Events durch “K”-Methoden zu kontrollieren, ist die $.proxy – Funktion einzusetzen. Sie sorgt dafür, dass der “this”-Operator bei der Ersetzung der Submit-Funktionalität des HTML-Formulars (und seines Submit-Buttons) durch eine spezielle Submit-Methode (z.B. submit_event() ) des Kontroll-Objektes innerhalb dieser Methode weiterhin auf das Kontroll-Objekt zeigt – anstatt dem jQuery-Standard entsprechend auf das eventauslösende HTML-Element (also das Formular bzw. den dortigen Submit-Button).

Wir wollen diesen Punkt nachfolgend noch etwas vertiefen und anschließend beleuchten, wie sich der “this” Operator unter jQuery in den Methoden des Kontroll-Objektes zum Formular verhält. Wir berühren dabei zwar nur elementares jQuery/Ajax-Know-How, aber vielleicht ist das ja für den einen oder anderen interessant. Und es liefert die Basis für die Realisierung unseres RUN/CHECKER-Szenarios.

Einfacher Beispielcode für die Verwendung von $.proxy(), Ajax und this in einem Kontroll-Objekt zu einem Formular

Wir betrachten also ein Formular “F”, für das wir ein zugehöriges Kontroll-Objekt “K” definiert haben. Der Submit-Event des Formulars soll über eine Callback-Funktion “submit_event()” des “K”-Objektes abgewickelt und kontrolliert werden. Die spezielle Submit-Methode des Kontroll-Objekts ist über eine entsprechende “prototype”-Definition festzulegen. Also z.b. etwa so:

....
....
K = new Ctrl_k(); 	// in diesem Beispiel nur der Einfachheit halber eine globale Definition   

function Ctrl_K () {

	// Define and manage handles to other JS Control objects ...... 
	// Define and manage jQuery selectors for the form and its elements .....
	.......
	this.id_status_form = "# ...."; 
	.......
	.......
	
	// 
Register Events - e.g. replace the submit event of the form by a local method of the "K" object
	$(this.id_status_form).submit( 
		$.proxy(this, 'submit_event') 
	);
}

Ctrl_K.prototype.submit_event = function(e) {
		
	e.preventDefault();
	....
	....
	// Prepare Ajax transaction 	
	var url = $(this.id_status_form).attr('action'); 
	var form_data = $(this.id_status_form).serialize(); 
	var return_data_type = 'json'; 
	......
	$.ajaxSetup({
		contentType: "application/x-www-form-urlencoded; charset=ISO-8859-1",
		context:  this, 
		error: this.status_error, 
		.......
	});
	.....
	.....
	// Perform an Ajax transaction	
	$.post(url, form_data, this.status_response, return_data_type); 	
	.......		
	.......  				
};

Ctrl_K.prototype.status_response = function(status_result) {
	// Do something with the Ajax (Json) response 
	.....
	this.msg = status_result.msg;
	.....
};

Ctrl_K.prototype.status_error = function(jqxhr, error_type) {
	// Analyze the error  
	.....
	.....
};

“this” in der per $.proxy() adressierten Methode “submit_event” bezieht sich in diesem Beispiel auf das K-Objekt selber und nicht, wie es ansonsten unter jQuery der Fall wäre, auf das HTML-Objekt, das den Submit-Event ausgelöst hat (z.B. der Submit-Button des Formulars). Nur wegen $.proxy() können wir “this” in der gewählten Form überhaupt erfolgreich und in seiner angestammten Bedeutung in “submit_event()” verwenden.

Das bedeutet: Durch den konsequenten Einsatz von $.proxy ergibt sich die Möglichkeit innerhalb des JS-Codes systematisch und sehr weitgehend von Details der HTML-Oberfläche zu abstrahieren, ohne die intuitive Bedeutung für Objekt-Methoden unter JS/jQuery zu verletzen. Jeder HTML-Bereich (u.a. ein Formularbereich), der mit JS/jQuery zu behandeln ist, kann in Form eines

  • zugehörigen Kontroll-Objektes und
  • zugehöriger registrierter Methoden für die Events seiner HTML-Objekte

behandelt werden. Die notwendigen Kontroll-Objekte werden i.d.R. beim Laden der Web-Seite über Initialisierungsfunktionen erzeugt. Das ermöglicht einen sehr klaren Code-Aufbau – auch dann wenn man wie im RUN/CHECKER-Szenario für ein und dieselbe Webpage gleich mehrere Formulare und Ergebnisbereiche parallel und periodisch handhaben muss.

“this” und der Kontext der Ajax Success bzw. Error-Funktion

Wir wenden uns nun einem weiteren interessanten Punkt zu. Worauf verweist eigentlich “this” in der Ajax-Callback-Funktion “status_resonse” des “K”-Objektes ? Das ist ja die Funktion, die im Falle des Erfolges der Ajax-Transaktion aufgerufen werden soll.

In unserem Beispiel oben ist die Ajax Success-Funktion als Parameter der jQuery Convenience-Funktion $.post(..)” festgelegt worden. Hätte man nur $.ajax({ … }) zum Auslösen der Ajax-Transaktion verwendet, hätte man dort eine Callback-Funktion über
$.ajax({ …, success: this.status_response, … })
festgelegt. Etwas Analoges gilt natürlich für die Callback-Funktion zur Ajax-Fehlerbehandlung.

So ohne weiteres ist es leider überhaupt nicht klar, was “this” in einer Ajax-Success/Error-Callback-Funktion unter jQuery bedeutet oder bedeuten soll. Die Doku (http://api.jquery.com/jQuery.ajax/) sagt dazu:

By default, the context is an object that represents the ajax settings used in the call ($.ajaxSettings merged with the settings passed to $.ajax).

Na, wunderbar. Damit bezöge sich das “this” in unserer schönen Methode des “K”-Objekts also leider gar nicht auf das “K”-Objekt. Das diese Feststellung zutrifft, sollte der Leser selber testen. Siehe aber die Links unten.

Das
wollen wir aber nicht. Wenn wir eines klareren Programm-Designs halber schon ein abstraktes Kontroll-Objekt einführen, soll das “this” in seinen Methoden bitte schön auch immer auf das Kontroll-Objekt selbst verweisen – Ajax hin oder her. jQuery ist das aber zu Recht egal:

Bei etwas Nachdenken wird einem klar, dass der Kontext, auf den sich “this” im Ajax Success-Callbakc beziehen soll, sich auch nicht wirklich standardmäßig zuweisen lassen kann. Denn man hätte die Callback-Funktion ja auch direkt deklarieren können – nach dem Muster:

$.ajaxSetup({ 
	..., 
	success: function () {
		this.xxx = , ... 
	}, 
	.....
	.....
})      

Was soll “this” denn dann sein ??? Das hängt wohl von Bedarf und Design der Applikation ab. Die Antwort ist also:

Der Entwickler muss den Context für die Callback-Funktion explizit setzen. Er ergibt sich in sinnvoller Weise nicht von allein. Dazu verwendet man das Attribut “context” in demjenigen Objekt, das wir bei lokalen Ajax-Transaktionen der Funktion $.ajax() oder bei einer globalen Festlegung für alle Ajax -Transaktionen der Funktion $.ajaxSetup() übergeben.

Viele Entwickler weisen nun den Context des Success-Callbacks meist einem (hidden) HTML-Objekt zu. In unserem Ansatz mit einem abstrakten Kontroll-Objekt zum Formular tun wir das natürlich nicht! Wir weisen den Kontext vielmehr dem erzeugten Kontroll-Objekt “K” selbst zu. Sollte Output in anderen, von ihm nicht kontrollierten HTML-Objekte erzeugt werden müssen, können die notwendigen Daten aus der Ajax-Antwort ja immer noch in definierter Weise an andere Kontroll-Objekte weitergereicht werden.

Wenn das Objekt “K” global definiert ist, kann man also schreiben:

$.ajaxSetup({ 
	..., 
	context: K, 
	..... 
})     

Aufgrund des Einsatzes von $.proxy() geht es aber deutlich eleganter:

$.ajaxSetup({ 
	..., 
	context: this, 
	..... 
})     

So, nun haben wir gesehen, wie wir Formulare und zugehörige Ajax-Transaktionen mit $.proxy() und spezifischen Methoden von Kontroll-Objekten handhaben können. Im nächsten Beitrag dieser Serie

Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – IV

befasse ich mich mit dem Timing des periodischen Aufrufs des CHECKER-Prozesses.

Links

http://stackoverflow.com/questions/3863536/how-to-pass-context-in-jquery-ajax-success-callback-function
http://stackoverflow.com/questions/6394812/this-inside-of-ajax-success-not-working
http://stackoverflow.com/questions/11027276/jquery-this-not-accessible-after-ajax-post

Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – II

Im ersten Beitrag dieser Serie – siehe
“Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – I”
hatten wir uns angesehen, warum man bei der asynchronen Zustandsabfrage eines lang laufenden PHP-Jobs auf einem Server mittels Ajax eher auf zwischenzeitlich gespeicherte Zustandsdaten in einer Datenbank zuzugreifen sollte, anstatt korrespondierende Zustandsdaten in einem Sessionspeicher abzufragen.

Wir nennen in Anlehnung an den ersten Beitrag den einmalig auf dem Webserver zu startenden und danach asynchron zu beobachtenden PHP-Hauptjob “RUN”, während wir die vielen per Ajax auf dem Server periodisch gestarteten PHP-basierten Zustands-Prüf-Jobs als “CHECKER” bezeichnen.

In diesem Beitrag befassen wir uns mit einer ersten Tücke der Client-Seite – also mit dem Javascrip/jQuery-Programm, das sowohl den Hauptjob (einmalig) als auch die asynchronen, parallel zum Hauptjob laufenden Abfragen (periodisch) auf dem Server startet und die Ergebnisse auswertet. Das betrachtete Problem hat wie auch die in späteren Beiträgen angerissenen Fälle letztlich mit der Variabilität des “this”-Operators unter Javascript/jQuery zu tun.

Der Einsatz von Kontroll-Objekten unter Javascript und ihre Kopplung an Formulare oder andere DOM-Elemente der Oberfläche mittels jQuery

Eine vernünftige Programmierung unter JS wird sich an MVC-Grundprinzipien orientieren. Anstelle permanent direkt und über globale Javascript-Funktionen mit den DOM-Objekten des HTML-Interfaces zu interagieren, wird man sich eine korrespondierende Struktur von reinen JS-Objekten aufbauen, die die Behandlung der grafischen DOM-Objekte in entsprechenden Methoden kapseln. In unserem Beispiel liegt es u.a. nahe,

  • ein Objekt zu verwalten, das den Start und ggf. auch den Output des PHP-Hauptjobs kontrolliert, dessen Zustand wir während seiner Laufzeit abfragen wollen.
  • ein Objekt zu konstruieren, das einerseits die stetige, periodische Ajax-Interaktion des Web-Interfaces mit dem Server managed und andererseits die benötigten DOM-Objekte der Oberfläche zur Anzeige der asynchron ermittelten Zustandsdaten des Server-Jobs steuert.

Im ersten Fall spielt in der Regel ein Button eines Formulars die Hauptrolle beim Start des Serverjobs. Zeitgleich kann auf dem Client dann ein zusätzliches Browser-Fenster gestartet werden, das den produzierten regulären Output des RUN-Jobs während seiner Laufzeit aufnimmt. Weitere Aktionen auf der Web-Oberfläche mögen zudem anstehen. Es liegt also nahe, all diese Aktionen in Methoden mehrerer Kontrollobjekte für die erforderlichen Aufgaben (und zugehörige,abgegrenzte HTML-Objekte) zu bündeln und zu kapseln.

Auch im zweiten Fall wird es vermutlich erforderlich sein, den per Ajax zu startenden “CHECKER”-Programmen Parameter (z.B. die Nummer der zuletzt empfangenen Statusmeldung) zu übergeben. Man kann das intern im Kontrollobjekt oder aber in besonderen Fällen auch mal über versteckte oder sichtbare Input-Felder eines weiteren Formulars erledigen.
[Die Daten des (evtl. versteckten) Formulars sind dann zuvor in einem Ajax-tauglichen Datenobjekt zu serialisieren. Auch hierbei würde jQuery natürlich über seine formularbezogene “serialize”-Funktion helfen.]

In beiden Situationen stößt man also auf folgende Thematik:

  • Es gibt ein Formular “F”, das per jQuery mit einem Submit-Event verknüpft wird, der direkt aus einer Javascript-Funktion oder über eine besonderes Element (Button) ausgelöst wird.
  • Man hat ferner ein Javascript-Kontroll-Objekt “K”. Sein Prototyp-Objekt heiße “Ctrl_K”. Eine
    spezielle, z.B. über
     
    “Ctrl_K.prototype.submit_event = function (e) { …}”
     
    definierte “Methode” des Objektes soll im Zuge des Formular-Submit-Events eine (einmalige oder periodische) Programm-Aktion auf dem Server und gleichzeitig mehrere Aktionen auf der Weboberfläche auslösen. Dabei sollen weitere Eigenschaften und/oder Methoden von “K” benutzt und über den “this”-Operator (also mit Bezug zu “K”) angesprochen werden.
  • Man will den Submit-Event des Formulars “F” an die für das “K”-Objekt definierte Methode “submit_event” als Callback binden. Dabei soll ein unter “submit_event” benutztes “this” wie gewöhnlich auf “K” verweisen.

Falle: Das “this” des Eventhandlers ist nicht das “this” des den Eventhandler definierenden Kontroll-Objekts

Hier tritt dann die erste Falle auf, in die man trotz besseren, theoretischen Wissens immer mal wieder reinfallen kann. Nehmen wir mal an, das Formular habe die ID “formx”. Nehmen wir ferner an, wir haben irgendwo innerhalb einer Funktion von “K” das Statement

this.id_form = “formx”;
$(this.id_form).submit( this.submit_event );

zur Bindung der Eventhandler-Funktion an das Formular abgesetzt. Die Frage ist, ob dann ein späteres innerhalb von “K” abgesetztes

$(this.id_form).submit();

oder ein Klick auf den evtl. vorhandenen Formularbutton funktionieren wird ? Die Antwort ist Nein.

Der Grund hierfür ist, dass innerhalb von Eventhandlern – also den zur Behandlung des Events aufgerufenen Funktionen – “this” auf den Auslöser des Ereignisses innerhalb der DOM-Hierarchie zeigt. Das gilt auch für Funktionen von (externen) JS-Objekten, die dem Event eines DOM-Objektes per Referenz zugeordnet wurden. “this” verweist in diesem Fall also auf das Formular (oder einen zugehörigen Button) und nicht auf das Kontroll-Objekt “K”. “submit_event” wird zwar ausgelöst, aber “this” zeigt innerhalb dieser Funktion leider nicht – wie erwartet – auf “K”. Dies führt natürlich zu Fehlern, sobald innerhalb von “submit_event” auf weitere Funktionen/Methoden oder Eigenschaften von “K” über den “this”-Operator zugegriffen werden soll.

Lösung: Einsatz der “$.proxy”-Funktion von JQuery

Es gibt mehrere Lösungsansätze, wie man mit dem Problem umgehen kann. Die eleganteste ist aber aus meiner Sicht die Verwendung von “$.proxy”. Mit Hilfe dieser Funktion des jQuery-Objekts kann man den zu geltenden Kontext für das “this” in der Eventhandler-Funktion explizit setzen. Man definiert die Event-Handler-Funktion dabei innerhalb von “K” wie folgt:

$(this.id_form).submit(
    jQuery.proxy(this, ‘submit_event’)
);

Nach dem Auslösen des Events und dem Aufruf von “K.submit_event” weist “this” innerhalb der auszuführenden Funktions-Statements von “submit_event” dann tatsächlich wie gewöhnlich auf “K”. Man kann also die Eventhandler, die man DOM-Objekten der HTML-Oberfläche zuordnen will, sehr wohl auch bei Benutzung von JQuery als Methoden ordentlich strukturierter Modell- und Kontroll-Objekte zugeordneter Javascript-Programme kapseln.

In unserem Beispiel kann die “$.proxy”-Funktion bereits beim Aufruf von “RUN” aber auch beim periodischen Aufruf der “CHECKER”-Programme genutzt werden, wenn man im letzteren Fall einen Umweg über ein (ggf. verstecktes oder offenes) Formular der Web-Oberfläche gehen muss oder will.

Im folgenden Beitrag dieser Serie
Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – III
vertiefe ich den eben andiskutierten Punkt und betrachte dann die Bedeutung von “this” im Rahmen einer definierten Callback-Funktion, des “K”-Objektes die im Fall einer erfolgreichen Ajax-Transaktion aufgerufenen wird. Auch hier ist oder bleibt “this” nicht unbedingt das, was man vielleicht erwartet.

Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – I

Wir entwickeln gerade ein User-Interface für eine PHP-basierte “Supply Chain Netzwerk-Simulation”. Dabei kommen Javascript, jQuery und Ajax zum Einsatz. Vor kurzem mussten wir uns mit dem Problem befassen, wie man mit periodischen Ajax-Jobs den zwischenzeitlichen Status von lang laufenden PHP-Berechnungs- und Simulationsjobs abfragt. Dabei sind wir in zwei Fallen getappt, die uns eigentlich hätten bekannt sein müssen. Aber inaktives Wissen schützt manchmal nicht vor naiven Herangehensweisen :-).

Ich stelle die relevanten Punkte in mehreren Blog-Beiträgen mal für interessierte Leser zusammen. Die erste potentielle Falle liegt auf der PHP-Seite und hat mit Sessions zu tun; die zweite Falle liegt im JS/jQuery-Bereich des Clients und hat damit zu tun, dass sich die Bedeutung der “this”-Referenz kontext-abhängig und damit trotz konsequenten Einsatzes von Objekt-Patterns ggf. unerwartet ändern kann.

Aufgabenstellung

Wir haben es mit folgender Aufgabe zu tun:
Ein lang laufender PHP Job [wir nennen ihn nachfolgend “RUN”] wird von einem User-Interface (Web-Seite im Browser – nachfolgend “CLIENT” genannt) gestartet. In unserem Fall ist RUN z.B. eine 60 Sekunden lang laufende PHP-Simulationsberechnung. Dieser PHP-Job schreibt seinen sehr langen fachlich-technischen Ergebnis-Output einerseits in Datenbanktabellen, aber Teile davon zur direkten User-Information sukzessive auch in ein separat geöffnetes zweites Fenster des Browsers. Gleichzeitig – d.h. während der Laufzeit – wollen wir aber zu verschiedenen Zwischenschritten des RUNs knappe Statusmeldungen abfragen und diese in speziellen DIVs des ursprünglichen CLIENT-Windows anzeigen. Um diese Zielsetzung zu erreichen, sehen wir 3 Schritte vor:

  1. Unmittelbar vor dem Start von RUN über eine Submit-Ereignis öffnen wir per Javascript ein zweites Browserfenster.
  2. RUN wird vom CLIENT-Window per Javascript und eine gezielte “submit”-Methode mit geeignetem Target so gestartet, dass sein primärer fachlich/technischer Output an das zuvor geöffnetes zweite Browserfenster übermittelt wird.
  3. RUN schreibt seine Statusmeldungen auf dem PHP-Server kontinuierlich in einen selbst verwalteten Message-Buffer.
  4. Vom CLIENT-Window aus starten wir kurz nach dem Submit von RUN mittels Javascript und JQuery periodisch Ajax-PHP-Jobs, die die Änderungen im Message-Buffer des Servers abfragen und zum CLIENT transferieren. Diese kurz laufenden Ajax-Jobs nennen wir nachfolgend “CHECKER”.
     
    Die durch die CHECKER ermittelten Statusdaten werden z.B. als JSON-Daten über den jeweils geöffneten Ajax-Kanal zum CLIENT transferiert. Dort werden sie über Callback-Methoden definierter Javascript-Objekte ausgewertet, aufbereitet und mit jQuery in die dafür vorgesehenen, speziellen DIVs des CLIENTs geschrieben.
    [Für den periodischen Start kann man Javascripts “setInterval” einsetzen und über die ermittelten Status-Daten den Timer am CLIENT nach dem Ende des RUN-Jobs auf dem Server auch wieder abbrechen.]

Unser erster naiver Ansatz für den Message-Buffer war der, den PHP-Sessionspeicher als Ort eines Message-Arrays zu nutzen. Ein Motiv dafür war der technisch einfache Zugriff auf die Daten des Sessionspeichers. Mit diesem Ansatz sind wir (natürlich) kläglich gescheitert.

Falle 1: Locked $_Session-Array

Normalerweise nutzen wir Ajax im Rahmen unseres CMS-Frameworks “ixCMF”. Damit werden Formular- und Webseiten des CMS selbst, wie später auch die Ergebnisseiten dynamisch auf Basis von Datenbank-Inhalten und PEAR-ITX-Templates erstellt. Das funktioniert zuverlässig und
schnell. Die zum Browser übermittelten HTML-Seiten nutzen dann wiederum Ajax-Funktionalitäten, um bestimmte Transaktionen wie ein Zwischenspeichern oder Abfragen von Daten im Hintergrund asynchron zu bewältigen.

Für Transaktionen des CMS wird auch der PHP Session-Speicher genutzt. Strukturell und vom Bedienungsablauf sind die Verhältnisse allerdings so, dass über die ixCMF-PHP-Programme und Ajax-Jobs ein Zugriff auf den Sessionspeicher während einer Websitzung des Users sequentiell und damit synchron abläuft. Über parallele, asynchrone Abfragen des Sessionspeichers durch mehrere gleichzeitig arbeitende PHP-Ajax-Jobs mussten wir uns bislang nie Gedanken machen. Da die Ajax-Jobs den Sessionspeicher lesend nutzen, gab es auch keinen Anlass, sich über potentielle Inkonsistenzen aufgrund parallel schreibender Jobs Sorgen zu machen.

Denkt man über das oben als Aufgabe beschriebene Szenario aber erst einmal in allgemeingültiger Form nach, dann fallen einem jedoch zwei Dinge auf:

  • Es kann zu Race-Conditions kommen. Parallel laufende Jobs können sich gegenseitig Session-Daten zerstören, wenn sie gleichzeitig schreibend auf ein und denselben Sessionspeicher zugreifen dürfen.
  • Sequentielle Zugriffe auf den Sessionspeicher lassen sich vom Webserver und nicht zuletzt auch von den PHP-Programmen selbst besser steuern und überwachen. Dies dient u.a. der Sicherheit. Ein parallel erlaubter Zugriff würde viele mögliche Schutz-Mechanismen wie die sequentiell Vergabe und Überwachung von kryptierten zufälligen Transaktionsnummern oder zusätzliche zeit- und ID-bezogene Schutzmechanismen über sequentiell vergebene kryptierte Cookies, die über eine Session hinweg verfolgt werden sollen, von vornherein aushebeln.

Der erste Punkt ist aufgrund der speziellen Session-Behandlung von PHP im Detail ggf. noch komplexer, als man meinen möchte. Siehe hierzu die gründliche Diskussion unter folgendem Link.
https://00f.net/2011/01/19/thoughts-on-php-sessions/

Der zweite Punkt hätte uns eigentlich selbst sofort zu denken geben müssen, da wir entsprechende Session-Schutz-Mechanismen im Rahmen unseres ixCMF-Frameworks selbst programmiert und immer wieder genutzt haben.

Jedenfalls gilt: Aus den genannten Gründen wird der Standardzugriff auf Sessions von PHP sequentialisiert:

PHP -Programme können nur sequentiell auf einen Standard-Session-Speicher zugreifen. Der Job, der sich gerade über session_start() den Zugriff verschafft hat, sperrt den Sessionspeicher gegen Zugriffe von anderen Programmen vollständig, bis er die Session über “session_commit” oder “session_write_close” freigegeben hat. Oft geschieht dies erst implizit am Ende der Laufzeit eines PHP-Programms.

Zu welchem Verhalten führt das in unserem Szenario ? Ganz einfach:

RUN blockt den Sessionspeicher bis zum Ende seiner Laufzeit und alle zwischenzeitlich gestarteten Ajax-Jobs müssen bis dahin warten. Damit aber wird eine zwischenzeitliches Verfolgen der Statusmeldungen von RUN unmöglich. Sprich: der ganze geplante parallele und periodische Abgriff von Statusinformationen, die RUN in S_SESSION hinterlegen sollte, funktioniert nicht. Es kommt vielmehr zu einem sinnlosen Stau der CHECKER-Jobs und jeder dieser Jobs ermittelt nach Abschluss von RUN Statusdaten raus, die bereits veraltet sind.

Das gleiche Sperr- und Warteverhalten erleben viele User und Administratoren in schlecht durchdachten Systemen, wenn schnell hintereinander gestartete PHP-Jobs die gleiche Session nutzen sollen oder müssen.

session_write_close() ist keine Lösung!

Nun empfehlen viele durch ähnliche Probleme Betroffene in Internet-Beiträgen die frühest mögliche Anwendung von “session_write_close()” als
Lösung. Siehe hierzu die am Ende des Beitrags als Beispiele angegebenen Links. Die Idee ist eine frühzeitige Freigabe des Sessionspeichers durch das jeweilige Programm, das aktuell auf den Sessionspeicher zugreift. Das mag ja in vielen Fällen gehen, nicht aber in unserem:

Hat ein Programm den Sessionspeicher über “session_commit()” oder “session_write_close()” erst einmal freigegeben, können zwar andere Programme auf den Sessionspeicher zugreifen, es selbst aber während seiner Laufzeit nicht mehr !

Die kurz laufenden Ajax-CHECKER-Jobs könnten session_write_close() nutzen – nicht aber RUN. Denn RUN muss fortwährend Statusinformationen in den Sessionspeicher schreiben – und die erste Session-Freigabe über die genannten Kommandos würde nachfolgende Schreibaktionen in die Session unmöglich machen.

Lösung für Falle 1: Nutze eine Datenbank zum Schreiben von Statusinformationen lang laufender Jobs

Wir haben uns dann sinnvollerweise entschlossen, als Ort für den Status-Message-Buffer von RUN eine spezielle Tabelle in einer MariaDB/MySQL-Datenbank zu nutzen, die für die Simulationsrechnungen sowieso erforderlich ist. Eine Alternative wäre ein File als Message-Buffer gewesen. Wir haben Files aber verworfen, weil sie aus unserer Sicht wieder andere Nachteile haben und uns der Zugriff auf einen Datenbank-Tabelle letztlich einfacher, flexibler und ausbaufähiger erschien. Zumal in unserem auf Datenbank-Nutzung ausgelegten Framework.

Die Nutzung einer Datenbank hat über die Lösung des oben besprochenen Session-Locks hinaus noch andere Vorteile:

  • Es zwingt zur sauberen Strukturierung der Status-Information. Das vereinfacht zudem auch den XML- oder JSON-Transfer der Daten im Zuge der periodischen Ajax-Prozess.
  • Erweiterungen sind zügig möglich. Die Feldstruktur ist über Schema-Verfahren insgesamt schnell geändert.
  • Es ist einfach, nur die seit dem letzten CHECKER-Zugriff neu eingetragenen Daten zu selektieren.
  • Liegt die Datenbank auf ein- und demselben LAMP-Server , ist sowohl der schreibende Zugriff durch RUN als auch der lesende Zugriff durch die CHECKER wegen der Kürze der Tabelle extrem performant. Man kann das Intervall für die zu startenden Ajax-CHECKER auf diese Performance hin anpassen.
  • Man kann die Timestamps der Datenbank nutzen, um genaue Zeitinformationen über das Ende der Zwischenschritte zu erhalten.
  • Die Statusinformation kann über die Laufzeit und Garbage Collection Time hinaus aufbewahrt und in weiteren Tabellen historisiert werden.
  • Für eine Historisierung von RUNs kann man in der Bank noch andere nützliche Daten hinterlegen.

Je nach Netz-Anbindung an den Server und dessen Leistungsfähigkeit können wir durch Nutzung der Datenbank als Message-Buffer von RUNs nun mit CHECKER-Jobs im Abstand von 250 bis 500 msec arbeiten, ohne Server und Client übermäßig zu belasten. Damit eröffnen sich während der Laufzeit der Simulationen alle Möglichkeiten einer kontinuierlichen, sehr fein-granularen Statusinformation im gleichen CLIENT-Window, von dem aus der RUN-Job ursprünglich gestartet wurde. Eine Darstellung von fachlich-technischem Output in einem zweiten Browserfenster während der Laufzeit bleibt davon unberührt und ist unbenommen möglich.

In einem kommenden zweiten
Fallen beim Statuscheck lang laufender PHP-Jobs mit Ajax – II
befassen wir uns prophylaktisch mit potentiellen “this”-Fallen auf der Javascript/jQuery-Seite der Ajax-Prozesse. In späteren Beiträgen werden wir dann die gewonnen Erkenntnisse zu einem Verfahren
zusammensetzen, das es erlaubt den “RUN” wie die “CHECKER”-Jobs systematisch mit Ajax zu behandeln. .

Links

Nutzung von commit_session oder write_close-session
http://konrness.com/php5/how-to-prevent-blocking-php-requests/
http://blog.preinheimer.com/index.php?/archives/416-PHP-and-Async-requests-with-file-based-sessions.html
http://www.held.org.il/blog/2008/02/php-session-locks/

Sicherheit
http://php.net/manual/de/session.security.php
http://phpsec.org/projects/guide/4.html