Proc::PID::File für PHP

Nach langer Zeit gibt es nun auch mal wieder was von mir zu lesen. Diesmal mit einem kleinen Code-Schnipsel. Bei einer Diskussion über in PHP implementierte Cronjobs ist mir aufgefallen, dass es in PHP keine vernünftige Möglichkeit gibt, zu erkennen, ob von einem Script bereits eine Instanz läuft. Gerade für länger laufende Cronjobs kann das aber sehr nützlich sein. In Perl habe ich das bisher immer mit dem schönen CPAN-Modul Proc::PID::File gemacht. Das muss doch mit PHP auch irgendwie gehen 😉

Da PHP keine Möglichkeit hat, auf die unterliegende open-Funktion von C mit all ihren Parametern zu zu greifen, müssen wir uns anderweitig aushelfen. Ich möchte hier flock() verwenden. Hier also der Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
/**
 * Klasse, welche überprüft, ob noch eine andere Instanz eines Skriptes irgendwo läuft
 */
class PIDManager
{
	private $fh;
	/*
	 * Konstruktor
	 * 
	 * @param string $filename Dateiname, der als Pseudo-PID-File benutzt werden soll
	 *
	 * @throws PIDManagerRunningException, wenn bereits eine Instanz läuft
	 */
	public function __construct($filename)
	{
		$this->fh = fopen($filename, 'w');
		if (!flock($this->fh, LOCK_EX + LOCK_NB)) {
			throw new PIDManagerRunningException('already running');
		}
	}
 
	public function __destruct()
	{
		flock($this->fh, LOCK_UN);
		fclose($this->fh);
	}
}
 
/**
 * Exception, die geworfen wird, wenn bereits eine Instanz mit dem gleichen PID-File läuft
 */
class PIDManagerRunningException extends Exception
{
}
 
?>

Kurz zur Erklärung: Der Konstruktor versucht, für eine übergebene Datei eine exklusive Sperre zu erhalten. Schlägt dies fehl, so hat bereits ein anderer Prozess eine solche Sperre und somit sind wir nicht allein. In diesem Fall wird eine Exception geworfen, die wir im eigentlichen Script abfangen können.

Hier ein Beispielcode für die Anwendung:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
 
require_once 'PIDManager.php';
 
try {
	$manager = new PIDManager('test.pid');
} catch (PIDManagerRunningException $e) {
	echo "Prozess läuft bereits...\n";
	exit();
}
echo "Beginne mit der Arbeit...\n";
sleep(5);
echo "Arbeit abgeschlossen...\n";
 
?>

Wir versuchen in einem try-catch-Block, die Klasse zu instanzieren. Wenn dabei eine PIDManagerRunningException ausgelöst wird, dann läuft bereits ein Script. In diesem Fall brechen wir ab. Simpel, wa? 😉

Aber wie geben wir die Sperre wieder frei? Dies wird automatisch von der Garbage-Collection von PHP übernommen. Sobald die Instanz der PIDManager-Klasse gelöscht wird (und das passiert spätestens beim Ende des Scripts), wird der Destruktor der Klasse ausgeführt, der die Sperre wieder aufhebt.

Benötigt man die Sperre nicht über die komplette Laufzeit des Scriptes, so kann das Objekt natürlich schon vorher beseitigt und damit die Sperre aufgehoben werden:

unset($manager);

Da wir hier flock() einsetzen, gelten natürlich auch alle damit verbundenen Einschränkungen. So dürfen die „PID-Files“ nicht auf einem NFS-Laufwerk oder in einem FAT-Dateisystem liegen und multi-threaded Umgebungen sind laut PHP-Dokumentation auch nicht zu empfehlen. Für den Großteil der Anwendungen sollte das aber ausreichen.

2 Kommentare

  1. Uli sagt:

    Kompliment – die beste Lösung die ich diesbezüglich gesehen hab 😉 – Greetz Uli

  2. fireballo sagt:

    genau danach habe ich gesucht…
    Danke dafür!

Schreibe einen Kommentar