diff --git a/lib/_launch.php b/lib/_launch.php index b59b5f3..693b89b 100755 --- a/lib/_launch.php +++ b/lib/_launch.php @@ -3,7 +3,7 @@ $internalUse = $argv[1] ?? null; if ($internalUse !== "--internal-use") exit("Wrong args"); $paramsfile = $argv[2] ?? null; -if (!file_exists($paramsfile)) exit("bad params file"); +if (!file_exists($paramsfile)) exit("Bad params file"); $argc -= 2; $argv = array_merge( array_slice($argv, 0, 1), @@ -14,12 +14,14 @@ $app_params = unserialize(file_get_contents($paramsfile)); require $app_params["vendor"]["autoload"]; use nur\cli\Application; +use nur\sery\output\msg; use nur\sery\wip\app\app; use nur\sery\app\launcher; use nur\yaml; class _LaunchApp extends Application { const NAME = "_launch"; + const USE_LOGFILE = true; const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2; @@ -46,15 +48,18 @@ class _LaunchApp extends Application { function main() { $appClass = $this->args[0] ?? null; if ($appClass === null) { - self::die("Vous devez spécifier la classe de l'application"); + msg::error("Vous devez spécifier la classe de l'application"); + self::die(); } elseif (!class_exists($appClass)) { - self::die("$appClass: Cette classe n'existe pas"); + msg::error("$appClass: Cette classe n'existe pas"); + self::die(); } $args = array_slice($this->args, 1); $useRunfile = constant("$appClass::USE_RUNFILE"); if (!$useRunfile) { - self::die("Cette application ne supporte pas l'usage de runfile"); + msg::error("Cette application ne supporte pas l'usage de runfile"); + self::die(); } $runfile = app::with($appClass, self::$internal_use_app_params)->getRunfile(); diff --git a/nur_src/cli/Application.php b/nur_src/cli/Application.php index 99e212c..7638eaa 100644 --- a/nur_src/cli/Application.php +++ b/nur_src/cli/Application.php @@ -9,6 +9,9 @@ use nur\config\ArrayConfig; use nur\msg; use nur\os; use nur\path; +use nur\sery\app\launcher; +use nur\sery\app\RunFile; +use nur\sery\cl; use nur\sery\wip\app\app; use nur\sery\output\log as nlog; use nur\sery\output\msg as nmsg; @@ -365,4 +368,15 @@ abstract class Application { } abstract function main(); + + const BGLAUNCH_SCRIPT = null; + static function runfile(): RunFile { + $callerParams = app::get()->getParams(); + return app::with(static::class, $callerParams)->getRunfile(); + } + static function bglaunch(?array $args=null) { + launcher::launch(static::class, cl::merge([ + static::BGLAUNCH_SCRIPT, + ], $args)); + } } diff --git a/src/app/RunFile.php b/src/app/RunFile.php index 0e9fdc1..0a3f20c 100644 --- a/src/app/RunFile.php +++ b/src/app/RunFile.php @@ -37,12 +37,13 @@ class RunFile { } protected function initData(bool $withDateStart=true): array { + $pid = $withDateStart? posix_getpid(): null; $dateStart = $withDateStart? new DateTime(): null; return [ "name" => $this->name, "id" => bin2hex(random_bytes(16)), "pg_pid" => null, - "pid" => posix_getpid(), + "pid" => $pid, "serial" => 0, # lock "locked" => false, @@ -52,6 +53,7 @@ class RunFile { "date_start" => $dateStart, "date_stop" => null, "exitcode" => null, + "is_done" => null, # action "action" => null, "action_date_start" => null, @@ -72,22 +74,27 @@ class RunFile { $file->lockWrite(); $data = $file->unserialize(null, false, true); if (!is_array($data)) { - $data = $this->initData(); + $data = $this->initData(false); $file->ftruncate(); $file->serialize($data, false, true); } - $file->ftruncate(); return [$file, $data]; } protected function serialize(SharedFile $file, array $data, ?array $merge=null): void { + $file->ftruncate(); $file->serialize(self::merge($data, $merge), true, true); } protected function update(callable $func): void { + /** @var SharedFile$file */ [$file, $data] = $this->willWrite(); $merge = call_user_func($func, $data); - $this->serialize($file, $data, $merge); + if ($merge !== null && $merge !== false) { + $this->serialize($file, $data, $merge); + } else { + $file->cancelWrite(); + } } function haveWorked(int $serial, ?int &$currentSerial=null): bool { @@ -154,20 +161,20 @@ class RunFile { }); } - /** tester si l'application a déjà été démarrée */ + /** tester si l'application a déjà été démarrée au moins une fois */ function wasStarted(): bool { $data = $this->read(); return $data["date_start"] !== null; } /** tester si l'application est démarrée et non arrêtée */ - function isStarted(): bool { + function isStarted(?array &$data=null): bool { $data = $this->read(); return $data["date_start"] !== null && $data["date_stop"] === null; } /** - * vérifier si la tâche tourne et est accessible + * vérifier si l'application marquée comme démarrée tourne réellement */ function isRunning(?array &$data=null): bool { $data = $this->read(); @@ -198,14 +205,14 @@ class RunFile { }); } - /** tester si l'application est déjà été stoppée */ + /** tester si l'application est déjà été stoppée au moins une fois */ function wasStopped(): bool { $data = $this->read(); return $data["date_stop"] !== null; } /** tester si l'application a été démarrée puis arrêtée */ - function isStopped(): bool { + function isStopped(?array &$data=null): bool { $data = $this->read(); return $data["date_start"] !== null && $data["date_stop"] !== null; } @@ -221,6 +228,24 @@ class RunFile { }); } + /** + * comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si + * $updateDone==true + */ + function isDone(?array &$data=null, bool $updateDone=true): bool { + $done = false; + $this->update(function (array $ldata) use (&$done, &$data, $updateDone) { + $data = $ldata; + if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_done"]) { + return false; + } + $done = true; + if (!$updateDone) return null; + return ["is_done" => $done]; + }); + return $done; + } + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # gestion des actions diff --git a/src/app/launcher.php b/src/app/launcher.php index 73066d5..87834d4 100644 --- a/src/app/launcher.php +++ b/src/app/launcher.php @@ -8,7 +8,7 @@ use nur\sery\StateException; use nur\sery\wip\app\app; class launcher { - static function launch(string $appClass, ...$args): int { + static function launch(string $appClass, array $args): int { $app = app::get(); $vendorBindir = $app->getVendorbindir(); $launch_php = "$vendorBindir/_launch.php"; @@ -21,17 +21,20 @@ class launcher { $cmd = new Cmd([ $launch_php, "--internal-use", $tmpfile->getFile(), - $appClass, ...$args, + $appClass, "--", ...$args, ]); $cmd->addRedir("null"); $cmd->passthru($exitcode); + # attendre un peu que la commande aie le temps de s'initialiser + sleep(1); + $tmpfile->close(); return $exitcode; } static function _start(array $args, Runfile $runfile): bool { - if ($runfile->isLocked()) return false; + if ($runfile->warnIfLocked()) return false; $pid = pcntl_fork(); if ($pid == -1) { # parent, impossible de forker @@ -43,6 +46,7 @@ class launcher { ## child, fork ok # Créer un groupe de process, pour pouvoir les tuer toutes en même temps $runfile->tm_startPg(); + $pid = posix_getpid(); $exitcode = -776; try { # puis lancer la commande @@ -50,7 +54,9 @@ class launcher { #XXX fichier de log? $cmd->addSource("/g/init.env"); $cmd->addRedir("null"); + msg::debug("$pid: launching\n".$cmd->getCmd()); $cmd->fork_exec($exitcode); + msg::debug("$pid: exitcode=$exitcode"); return true; } finally { $runfile->wfStopped($exitcode); diff --git a/src/file/Stream.php b/src/file/Stream.php index f28ae0a..c601623 100644 --- a/src/file/Stream.php +++ b/src/file/Stream.php @@ -455,4 +455,12 @@ class Stream extends AbstractIterator implements IReader, IWriter { function serialize($object, bool $close=true, bool $alreadyLocked=false): void { $this->putContents(serialize($object), $close, $alreadyLocked); } + + /** + * annuler une tentative d'écriture commencée avec {@link self::canWrite()} + */ + function cancelWrite(bool $close=true): void { + if ($this->useLocking) $this->unlock($close); + elseif ($close) $this->close(); + } }