modifs.mineures sans commentaires
This commit is contained in:
parent
b195888602
commit
333ddca4f5
|
@ -193,9 +193,9 @@ abstract class AbstractCmd implements ICmd {
|
||||||
return $retcode == 0;
|
return $retcode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fork_exec(int &$retcode=null): bool {
|
function fork_exec(?int &$retcode=null, bool $wait=true): bool {
|
||||||
$cmd = $this->getCmd(null, true);
|
$cmd = $this->getCmd(null, true);
|
||||||
sh::_fork_exec($cmd, $retcode);
|
sh::_fork_exec($cmd, $retcode, $wait);
|
||||||
return $retcode == 0;
|
return $retcode == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,27 +136,34 @@ class sh {
|
||||||
return $retcode == 0;
|
return $retcode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function _waitpid(int $pid, ?int &$retcode=null): bool {
|
||||||
|
pcntl_waitpid($pid, $status);
|
||||||
|
if (pcntl_wifexited($status)) $retcode = pcntl_wexitstatus($status);
|
||||||
|
elseif (pcntl_wifsignaled($status)) $retcode = -pcntl_wtermsig($status);
|
||||||
|
else $retcode = app2::EC_FORK_CHILD;
|
||||||
|
return $retcode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
|
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
|
||||||
* fin de son exécution.
|
* fin de son exécution.
|
||||||
*
|
*
|
||||||
* $cmd doit déjà être formaté comme il convient
|
* $cmd doit déjà être formaté comme il convient
|
||||||
*/
|
*/
|
||||||
static final function _fork_exec(string $cmd, int &$retcode=null): bool {
|
static final function _fork_exec(string $cmd, ?int &$retcode=null, bool $wait=true): bool {
|
||||||
$pid = pcntl_fork();
|
$pid = pcntl_fork();
|
||||||
if ($pid == -1) {
|
if ($pid == -1) {
|
||||||
// parent, impossible de forker
|
// parent, impossible de forker
|
||||||
throw new ExitError(app2::EC_FORK_PARENT, "unable to fork");
|
throw new ExitError(app2::EC_FORK_PARENT, "unable to fork");
|
||||||
} elseif ($pid) {
|
} elseif ($pid) {
|
||||||
// parent, fork ok
|
// parent, fork ok
|
||||||
pcntl_waitpid($pid, $status);
|
if ($wait) return self::_waitpid($pid, $retcode);
|
||||||
if (pcntl_wifexited($status)) $retcode = pcntl_wexitstatus($status);
|
$retcode = null;
|
||||||
else $retcode = app2::EC_FORK_CHILD;
|
return true;
|
||||||
return $retcode == 0;
|
|
||||||
}
|
}
|
||||||
// child, fork ok
|
// child, fork ok
|
||||||
pcntl_exec("/bin/sh", ["-c", $cmd]);
|
pcntl_exec("/bin/sh", ["-c", $cmd]);
|
||||||
return false;
|
throw StateException::unexpected_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\tools;
|
namespace nur\sery\tools;
|
||||||
|
|
||||||
|
use nur\sery\os\path;
|
||||||
|
use nur\sery\output\msg;
|
||||||
use nur\sery\wip\app\app2;
|
use nur\sery\wip\app\app2;
|
||||||
use nur\sery\wip\app\cli\Application;
|
use nur\sery\wip\app\cli\Application;
|
||||||
use nur\sery\wip\app\cli\bg_launcher;
|
use nur\sery\wip\app\cli\bg_launcher;
|
||||||
|
use nur\sery\wip\app\RunFile;
|
||||||
use nur\yaml;
|
use nur\yaml;
|
||||||
|
|
||||||
class BgLauncherApp extends Application {
|
class BgLauncherApp extends Application {
|
||||||
|
@ -31,6 +34,11 @@ class BgLauncherApp extends Application {
|
||||||
|
|
||||||
protected ?array $args = null;
|
protected ?array $args = null;
|
||||||
|
|
||||||
|
static function show_infos(RunFile $runfile, ?int $level=null): void {
|
||||||
|
msg::print($runfile->getDesc(), $level);
|
||||||
|
msg::print(yaml::with(["data" => $runfile->read()]), -1);
|
||||||
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
$args = $this->args;
|
$args = $this->args;
|
||||||
|
|
||||||
|
@ -52,18 +60,20 @@ class BgLauncherApp extends Application {
|
||||||
switch ($this->action) {
|
switch ($this->action) {
|
||||||
case self::ACTION_START:
|
case self::ACTION_START:
|
||||||
$appClass::_manage_runfile(count($args), $args, $runfile);
|
$appClass::_manage_runfile(count($args), $args, $runfile);
|
||||||
|
if ($runfile->warnIfLocked()) return;
|
||||||
array_splice($args, 0, 0, [
|
array_splice($args, 0, 0, [
|
||||||
PHP_BINARY,
|
PHP_BINARY,
|
||||||
NULIB_APP_app_launcher,
|
path::abspath(NULIB_APP_app_launcher),
|
||||||
]);
|
]);
|
||||||
app2::params_putenv();
|
app2::params_putenv();
|
||||||
bg_launcher::_start($args, $runfile);
|
bg_launcher::_start($args, $runfile);
|
||||||
break;
|
break;
|
||||||
case self::ACTION_STOP:
|
case self::ACTION_STOP:
|
||||||
bg_launcher::_stop($runfile);
|
bg_launcher::_stop($runfile);
|
||||||
|
self::show_infos($runfile);
|
||||||
break;
|
break;
|
||||||
case self::ACTION_INFOS:
|
case self::ACTION_INFOS:
|
||||||
yaml::dump($runfile->read());
|
self::show_infos($runfile);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,19 @@ class SteamTrainApp extends Application {
|
||||||
"description" => <<<EOT
|
"description" => <<<EOT
|
||||||
Cette application peut être utilisée pour tester le lancement des tâches de fond
|
Cette application peut être utilisée pour tester le lancement des tâches de fond
|
||||||
EOT,
|
EOT,
|
||||||
|
|
||||||
|
["-c", "--count", "args" => 1,
|
||||||
|
"help" => "spécifier le nombre d'étapes",
|
||||||
|
],
|
||||||
|
["-s", "--use-signal-handler", "value" => true,
|
||||||
|
"help" => "installer un gestionnaire de signaux",
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $count = 100;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
$count = 100;
|
$count = intval($this->count);
|
||||||
app2::action("Running train...", $count);
|
app2::action("Running train...", $count);
|
||||||
for ($i = 1; $i <= $count; $i++) {
|
for ($i = 1; $i <= $count; $i++) {
|
||||||
msg::print("Tchou-tchou! x $i");
|
msg::print("Tchou-tchou! x $i");
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\wip\app;
|
namespace nur\sery\wip\app;
|
||||||
|
|
||||||
|
use nur\sery\A;
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\file\SharedFile;
|
use nur\sery\file\SharedFile;
|
||||||
use nur\sery\os\path;
|
use nur\sery\os\path;
|
||||||
use nur\sery\output\msg;
|
use nur\sery\output\msg;
|
||||||
use nur\sery\php\time\DateTime;
|
use nur\sery\php\time\DateTime;
|
||||||
|
use nur\sery\php\time\Elapsed;
|
||||||
use nur\sery\str;
|
use nur\sery\str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,18 +43,12 @@ class RunFile {
|
||||||
], $merge);
|
], $merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function initData(bool $forStart=true): array {
|
protected function initData(): array {
|
||||||
if ($forStart) {
|
|
||||||
$pid = posix_getpid();
|
|
||||||
$dateStart = new DateTime();
|
|
||||||
} else {
|
|
||||||
$pid = $dateStart = null;
|
|
||||||
}
|
|
||||||
return [
|
return [
|
||||||
"name" => $this->name,
|
"name" => $this->name,
|
||||||
"id" => bin2hex(random_bytes(16)),
|
"mode" => null,
|
||||||
"pg_pid" => null,
|
"pgid" => null,
|
||||||
"pid" => $pid,
|
"pid" => null,
|
||||||
"serial" => 0,
|
"serial" => 0,
|
||||||
# lock
|
# lock
|
||||||
"locked" => false,
|
"locked" => false,
|
||||||
|
@ -60,10 +56,11 @@ class RunFile {
|
||||||
"date_release" => null,
|
"date_release" => null,
|
||||||
# run
|
# run
|
||||||
"logfile" => $this->logfile,
|
"logfile" => $this->logfile,
|
||||||
"date_start" => $dateStart,
|
"date_start" => null,
|
||||||
"date_stop" => null,
|
"date_stop" => null,
|
||||||
"exitcode" => null,
|
"exitcode" => null,
|
||||||
"is_done" => null,
|
"is_reaped" => null,
|
||||||
|
"is_ack_done" => null,
|
||||||
# action
|
# action
|
||||||
"action" => null,
|
"action" => null,
|
||||||
"action_date_start" => null,
|
"action_date_start" => null,
|
||||||
|
@ -75,7 +72,7 @@ class RunFile {
|
||||||
|
|
||||||
function read(): array {
|
function read(): array {
|
||||||
$data = $this->file->unserialize();
|
$data = $this->file->unserialize();
|
||||||
if (!is_array($data)) $data = $this->initData(false);
|
if (!is_array($data)) $data = $this->initData();
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +81,7 @@ class RunFile {
|
||||||
$file->lockWrite();
|
$file->lockWrite();
|
||||||
$data = $file->unserialize(null, false, true);
|
$data = $file->unserialize(null, false, true);
|
||||||
if (!is_array($data)) {
|
if (!is_array($data)) {
|
||||||
$data = $this->initData(false);
|
$data = $this->initData();
|
||||||
$file->ftruncate();
|
$file->ftruncate();
|
||||||
$file->serialize($data, false, true);
|
$file->serialize($data, false, true);
|
||||||
}
|
}
|
||||||
|
@ -160,14 +157,40 @@ class RunFile {
|
||||||
# cycle de vie de l'application
|
# cycle de vie de l'application
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* indiquer que l'application démarre. l'état est entièrement réinitialisé,
|
* Préparer le démarrage de l'application. Cette méhode est appelée par un
|
||||||
* sauf le PID du leader qui est laissé en l'état
|
* script externe qui doit préparer le démarrage du script
|
||||||
|
*
|
||||||
|
* - démarrer un groupe de process dont le process courant est le leader
|
||||||
*/
|
*/
|
||||||
|
function wfPrepare(?int &$pgid=null): void {
|
||||||
|
$this->update(function (array $data) use (&$pgid) {
|
||||||
|
posix_setsid();
|
||||||
|
$pgid = posix_getpid();
|
||||||
|
return cl::merge($this->initData(), [
|
||||||
|
"mode" => "session",
|
||||||
|
"pgid" => $pgid,
|
||||||
|
"pid" => null,
|
||||||
|
"date_start" => new DateTime(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** indiquer que l'application démarre. */
|
||||||
function wfStart(): void {
|
function wfStart(): void {
|
||||||
$this->update(function (array $data) {
|
$this->update(function (array $data) {
|
||||||
return cl::merge($this->initData(), [
|
$pid = posix_getpid();
|
||||||
"pg_pid" => $data["pg_pid"],
|
if ($data["mode"] === "session") {
|
||||||
]);
|
A::merge($data, [
|
||||||
|
"pid" => $pid,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$data = cl::merge($this->initData(), [
|
||||||
|
"mode" => "standalone",
|
||||||
|
"pid" => $pid,
|
||||||
|
"date_start" => new DateTime(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,13 +206,12 @@ class RunFile {
|
||||||
return $data["date_start"] !== null && $data["date_stop"] === null;
|
return $data["date_start"] !== null && $data["date_stop"] === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function _getCid(array $data=null): int {
|
||||||
* vérifier si l'application marquée comme démarrée tourne réellement
|
if ($data["mode"] === "session") return -$data["pgid"];
|
||||||
*/
|
else return $data["pid"];
|
||||||
function isRunning(?array $data=null): bool {
|
}
|
||||||
$data ??= $this->read();
|
|
||||||
if ($data["date_start"] === null) return false;
|
function _isRunning(array $data=null): bool {
|
||||||
if ($data["date_stop"] !== null) return false;
|
|
||||||
if (!posix_kill($data["pid"], 0)) {
|
if (!posix_kill($data["pid"], 0)) {
|
||||||
switch (posix_get_last_error()) {
|
switch (posix_get_last_error()) {
|
||||||
case 1: #PCNTL_EPERM:
|
case 1: #PCNTL_EPERM:
|
||||||
|
@ -208,10 +230,22 @@ class RunFile {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si l'application marquée comme démarrée tourne réellement
|
||||||
|
*/
|
||||||
|
function isRunning(?array $data=null): bool {
|
||||||
|
$data ??= $this->read();
|
||||||
|
if ($data["date_start"] === null) return false;
|
||||||
|
if ($data["date_stop"] !== null) return false;
|
||||||
|
return $this->_isRunning($data);
|
||||||
|
}
|
||||||
|
|
||||||
/** indiquer que l'application s'arrête */
|
/** indiquer que l'application s'arrête */
|
||||||
function wfStop(): void {
|
function wfStop(): void {
|
||||||
$this->update(function (array $data) {
|
$this->update(function (array $data) {
|
||||||
return ["date_stop" => new DateTime()];
|
return [
|
||||||
|
"date_stop" => new DateTime(),
|
||||||
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,16 +262,45 @@ class RunFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** après l'arrêt de l'application, mettre à jour le code de retour */
|
/** après l'arrêt de l'application, mettre à jour le code de retour */
|
||||||
function wfStopped(int $exitcode): void {
|
function wfReaped(int $exitcode): void {
|
||||||
$this->update(function (array $data) use ($exitcode) {
|
$this->update(function (array $data) use ($exitcode) {
|
||||||
return [
|
return [
|
||||||
"pg_pid" => null,
|
"mode" => null,
|
||||||
|
"pgid" => null,
|
||||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
||||||
"exitcode" => $exitcode,
|
"exitcode" => $exitcode,
|
||||||
|
"is_reaped" => true,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si on est dans le cas où la tâche devrait tourner mais en réalité
|
||||||
|
* ce n'est pas le cas
|
||||||
|
*/
|
||||||
|
function _isUndead(?int $pid=null): bool {
|
||||||
|
$data = $this->read();
|
||||||
|
if ($data["date_start"] === null) return false;
|
||||||
|
if ($data["date_stop"] !== null) return false;
|
||||||
|
$pid ??= $data["pid"];
|
||||||
|
if (!posix_kill($pid, 0)) {
|
||||||
|
switch (posix_get_last_error()) {
|
||||||
|
case 1: #PCNTL_EPERM:
|
||||||
|
# process auquel on n'a pas accès?! est-ce un autre process qui a
|
||||||
|
# réutilisé le PID?
|
||||||
|
return false;
|
||||||
|
case 3: #PCNTL_ESRCH:
|
||||||
|
# process inexistant
|
||||||
|
return true;
|
||||||
|
case 22: #PCNTL_EINVAL:
|
||||||
|
# ne devrait pas se produire
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# process existant auquel on a accès
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
|
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
|
||||||
* $updateDone==true
|
* $updateDone==true
|
||||||
|
@ -246,16 +309,42 @@ class RunFile {
|
||||||
$done = false;
|
$done = false;
|
||||||
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
|
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
|
||||||
$data = $ldata;
|
$data = $ldata;
|
||||||
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_done"]) {
|
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_ack_done"]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$done = true;
|
$done = true;
|
||||||
if ($updateDone) return ["is_done" => $done];
|
if ($updateDone) return ["is_ack_done" => $done];
|
||||||
else return null;
|
else return null;
|
||||||
});
|
});
|
||||||
return $done;
|
return $done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDesc(?array $data=null): ?string {
|
||||||
|
$data ??= $this->read();
|
||||||
|
$desc = $data["name"];
|
||||||
|
$dateStart = $data["date_start"];
|
||||||
|
$dateStop = $data["date_stop"];
|
||||||
|
$exitcode = $data["exitcode"];
|
||||||
|
if ($exitcode !== null) $exitcode = "\nCode de retour $exitcode";
|
||||||
|
if (!$this->wasStarted($data)) {
|
||||||
|
return "$desc: pas encore démarré";
|
||||||
|
} elseif ($this->isRunning($data)) {
|
||||||
|
$sinceStart = Elapsed::format_since($dateStart);
|
||||||
|
$started = "\nDémarré depuis $dateStart ($sinceStart)";
|
||||||
|
return "$desc: EN COURS pid $data[pid]$started";
|
||||||
|
} elseif ($this->isStopped($data)) {
|
||||||
|
$duration = "\nDurée ".Elapsed::format_delay($dateStart, $dateStop);
|
||||||
|
$sinceStop = Elapsed::format_since($dateStop);
|
||||||
|
$stopped = "\nArrêtée $sinceStop le $dateStop";
|
||||||
|
$reaped = $data["is_reaped"]? ", reaped": null;
|
||||||
|
$done = $data["is_ack_done"]? ", ACK done": null;
|
||||||
|
return "$desc: TERMINEE$duration$stopped$exitcode$reaped$done";
|
||||||
|
} else {
|
||||||
|
$stopped = $dateStop? "\nArrêtée le $dateStop": null;
|
||||||
|
return "$desc: CRASHED\nCommencé le $dateStart$stopped$exitcode";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# gestion des actions
|
# gestion des actions
|
||||||
|
|
||||||
|
@ -286,21 +375,20 @@ class RunFile {
|
||||||
function getActionDesc(?array $data=null): ?string {
|
function getActionDesc(?array $data=null): ?string {
|
||||||
$data ??= $this->read();
|
$data ??= $this->read();
|
||||||
$action = $data["action"];
|
$action = $data["action"];
|
||||||
if ($action === null) {
|
if ($action !== null) {
|
||||||
return "pid $data[pid] [$data[date_start]]";
|
$date ??= $data["action_date_step"];
|
||||||
|
$date ??= $data["action_date_start"];
|
||||||
|
if ($date !== null) $action = "$date $action";
|
||||||
|
$action = "Etape en cours: $action";
|
||||||
|
$current = $data["action_current_step"];
|
||||||
|
$max = $data["action_max_step"];
|
||||||
|
if ($current !== null && $max !== null) {
|
||||||
|
$action .= " ($current / $max)";
|
||||||
|
} elseif ($current !== null) {
|
||||||
|
$action .= " ($current)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return $action;
|
||||||
$date ??= $data["action_date_step"];
|
|
||||||
$date ??= $data["action_date_start"];
|
|
||||||
if ($date !== null) $action = "[$date] $action";
|
|
||||||
$current = $data["action_current_step"];
|
|
||||||
$max = $data["action_max_step"];
|
|
||||||
if ($current !== null && $max !== null) {
|
|
||||||
$action .= " ($current / $max)";
|
|
||||||
} elseif ($current !== null) {
|
|
||||||
$action .= " ($current)";
|
|
||||||
}
|
|
||||||
return "pid $data[pid] $action";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -313,64 +401,4 @@ class RunFile {
|
||||||
$name = str::join("/", [$this->name, $name]);
|
$name = str::join("/", [$this->name, $name]);
|
||||||
return new LockFile($file, $name, $title);
|
return new LockFile($file, $name, $title);
|
||||||
}
|
}
|
||||||
|
|
||||||
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# Gestionnaire de tâches (tm_*)
|
|
||||||
|
|
||||||
/** démarrer un groupe de process dont le process courant est le leader */
|
|
||||||
function tm_startPg(): void {
|
|
||||||
$this->update(function (array $data) {
|
|
||||||
posix_setsid();
|
|
||||||
return [
|
|
||||||
"pg_pid" => posix_getpid(),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* vérifier si on est dans le cas où la tâche devrait tourner mais en réalité
|
|
||||||
* ce n'est pas le cas
|
|
||||||
*/
|
|
||||||
function tm_isUndead(?int $pid=null): bool {
|
|
||||||
$data = $this->read();
|
|
||||||
if ($data["date_start"] === null) return false;
|
|
||||||
if ($data["date_stop"] !== null) return false;
|
|
||||||
$pid ??= $data["pid"];
|
|
||||||
if (!posix_kill($pid, 0)) {
|
|
||||||
switch (posix_get_last_error()) {
|
|
||||||
case 1: #PCNTL_EPERM:
|
|
||||||
# process auquel on n'a pas accès?! est-ce un autre process qui a
|
|
||||||
# réutilisé le PID?
|
|
||||||
return false;
|
|
||||||
case 3: #PCNTL_ESRCH:
|
|
||||||
# process inexistant
|
|
||||||
return true;
|
|
||||||
case 22: #PCNTL_EINVAL:
|
|
||||||
# ne devrait pas se produire
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# process existant auquel on a accès
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tm_isReapable(): bool {
|
|
||||||
$data = $this->read();
|
|
||||||
return $data["date_stop"] !== null && $data["exitcode"] === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** marquer la tâche comme terminée */
|
|
||||||
function tm_reap(?int $pid=null): void {
|
|
||||||
$data = $this->read();
|
|
||||||
$pid ??= $data["pid"];
|
|
||||||
pcntl_waitpid($pid, $status);
|
|
||||||
$exitcode = pcntl_wifexited($status)? pcntl_wexitstatus($status): 127;
|
|
||||||
$this->update(function (array $data) use ($exitcode) {
|
|
||||||
return [
|
|
||||||
"pg_pid" => null,
|
|
||||||
"date_stop" => $data["date_stop"] ?? new DateTime(),
|
|
||||||
"exitcode" => $data["exitcode"] ?? $exitcode,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\wip\app;
|
namespace nur\sery\wip\app;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use nur\sery\A;
|
use nur\sery\A;
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\ExitError;
|
use nur\sery\ExitError;
|
||||||
|
@ -475,15 +476,12 @@ class app2 {
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
const EC_UNDEAD = 247;
|
const EC_FORK_CHILD = 250;
|
||||||
const EC_REAPABLE = 248;
|
const EC_FORK_PARENT = 251;
|
||||||
const EC_FORK_CHILD = 249;
|
const EC_DISABLED = 252;
|
||||||
const EC_FORK_PARENT = 250;
|
const EC_LOCKED = 253;
|
||||||
const EC_DISABLED = 251;
|
const EC_BAD_COMMAND = 254;
|
||||||
const EC_LOCKED = 252;
|
const EC_UNEXPECTED = 255;
|
||||||
const EC_BAD_COMMAND = 253;
|
|
||||||
const EC_UNEXPECTED = 254;
|
|
||||||
const EC_SIGNAL = 255;
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
@ -492,7 +490,7 @@ class app2 {
|
||||||
static function install_signal_handler(bool $allow=true): void {
|
static function install_signal_handler(bool $allow=true): void {
|
||||||
if (!$allow) return;
|
if (!$allow) return;
|
||||||
$signalHandler = function(int $signo, $siginfo) {
|
$signalHandler = function(int $signo, $siginfo) {
|
||||||
throw new ExitError(self::EC_SIGNAL);
|
throw new ExitError(128 + $signo);
|
||||||
};
|
};
|
||||||
pcntl_signal(SIGHUP, $signalHandler);
|
pcntl_signal(SIGHUP, $signalHandler);
|
||||||
pcntl_signal(SIGINT, $signalHandler);
|
pcntl_signal(SIGINT, $signalHandler);
|
||||||
|
|
|
@ -94,12 +94,13 @@ abstract class Application {
|
||||||
break;
|
break;
|
||||||
case "infos":
|
case "infos":
|
||||||
case "i":
|
case "i":
|
||||||
|
$desc = $runfile->getDesc();
|
||||||
if ($runfile->isRunning()) {
|
if ($runfile->isRunning()) {
|
||||||
$action = $runfile->getActionDesc();
|
$actionDesc = $runfile->getActionDesc();
|
||||||
if ($action !== null) $action = ": $action";
|
if ($actionDesc !== null) $actionDesc = "\n$actionDesc";
|
||||||
echo "running$action\n";
|
echo "$desc$actionDesc\n";
|
||||||
} else {
|
} else {
|
||||||
echo "not running\n";
|
echo "$desc\n";
|
||||||
$ec = 1;
|
$ec = 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -113,7 +114,7 @@ abstract class Application {
|
||||||
static function run(?Application $app=null): void {
|
static function run(?Application $app=null): void {
|
||||||
$unlock = false;
|
$unlock = false;
|
||||||
$stop = false;
|
$stop = false;
|
||||||
register_shutdown_function(function() use (&$unlock, &$stop) {
|
$shutdown = function () use (&$unlock, &$stop) {
|
||||||
if ($unlock) {
|
if ($unlock) {
|
||||||
app2::get()->getRunfile()->release();
|
app2::get()->getRunfile()->release();
|
||||||
$unlock = false;
|
$unlock = false;
|
||||||
|
@ -122,7 +123,8 @@ abstract class Application {
|
||||||
app2::get()->getRunfile()->wfStop();
|
app2::get()->getRunfile()->wfStop();
|
||||||
$stop = false;
|
$stop = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
register_shutdown_function($shutdown);
|
||||||
app2::install_signal_handler(static::USE_SIGNAL_HANDLER);
|
app2::install_signal_handler(static::USE_SIGNAL_HANDLER);
|
||||||
try {
|
try {
|
||||||
static::_initialize_app();
|
static::_initialize_app();
|
||||||
|
|
|
@ -2,95 +2,38 @@
|
||||||
namespace nur\sery\wip\app\cli;
|
namespace nur\sery\wip\app\cli;
|
||||||
|
|
||||||
use nur\sery\ExitError;
|
use nur\sery\ExitError;
|
||||||
use nur\sery\file\TmpfileWriter;
|
|
||||||
use nur\sery\os\path;
|
|
||||||
use nur\sery\os\proc\Cmd;
|
use nur\sery\os\proc\Cmd;
|
||||||
|
use nur\sery\os\sh;
|
||||||
use nur\sery\output\msg;
|
use nur\sery\output\msg;
|
||||||
use nur\sery\wip\app\app2;
|
use nur\sery\wip\app\app2;
|
||||||
use nur\sery\wip\app\args;
|
|
||||||
use nur\sery\wip\app\RunFile;
|
use nur\sery\wip\app\RunFile;
|
||||||
|
|
||||||
class bg_launcher {
|
class bg_launcher {
|
||||||
/**
|
static function _start(array $args, Runfile $runfile): void {
|
||||||
* transformer une liste d'argument de la forme
|
|
||||||
* - ["myArg" => $value] devient ["--my-arg", "$value"]
|
|
||||||
* - ["myOpt" => true] devient ["--my-opt"]
|
|
||||||
* - ["myOpt" => false] est momis
|
|
||||||
* - les valeurs séquentielles sont prises telles quelles
|
|
||||||
*/
|
|
||||||
static function verifix_args(array $args): array {
|
|
||||||
$args = args::from_array($args);
|
|
||||||
# corriger le chemin de l'application pour qu'il soit absolu et normalisé
|
|
||||||
$args[0] = path::abspath($args[0]);
|
|
||||||
return $args;
|
|
||||||
}
|
|
||||||
|
|
||||||
static function launch(string $appClass, array $args): int {
|
|
||||||
$app = app2::get();
|
|
||||||
$vendorBindir = $app->getVendorbindir();
|
|
||||||
$launch_php = "$vendorBindir/_launch.php";
|
|
||||||
if (!file_exists($launch_php)) {
|
|
||||||
$launch_php = __DIR__."/../../lib/_launch.php";
|
|
||||||
}
|
|
||||||
$tmpfile = new TmpfileWriter();
|
|
||||||
$tmpfile->keep()->serialize($app->getParams());
|
|
||||||
|
|
||||||
$args = self::verifix_args($args);
|
|
||||||
$cmd = new Cmd([
|
|
||||||
$launch_php,
|
|
||||||
"--internal-use", $tmpfile->getFile(),
|
|
||||||
$appClass, "--", ...$args,
|
|
||||||
]);
|
|
||||||
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
|
|
||||||
$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->warnIfLocked()) return false;
|
|
||||||
$pid = pcntl_fork();
|
$pid = pcntl_fork();
|
||||||
if ($pid == -1) {
|
if ($pid == -1) {
|
||||||
# parent, impossible de forker
|
# parent, impossible de forker
|
||||||
throw new ExitError(app2::EC_FORK_PARENT, "Unable to fork");
|
throw new ExitError(app2::EC_FORK_PARENT, "Unable to fork");
|
||||||
} elseif ($pid) {
|
} elseif (!$pid) {
|
||||||
# parent, fork ok
|
# child, fork ok
|
||||||
return true;
|
$runfile->wfPrepare($pid);
|
||||||
} else {
|
|
||||||
## child, fork ok
|
|
||||||
# Créer un groupe de process, pour pouvoir tuer tous les enfants en même temps
|
|
||||||
$runfile->tm_startPg();
|
|
||||||
$logfile = $runfile->getLogfile() ?? "/tmp/NULIB_APP_app_start.log";
|
$logfile = $runfile->getLogfile() ?? "/tmp/NULIB_APP_app_start.log";
|
||||||
$pid = posix_getpid();
|
|
||||||
$exitcode = app2::EC_FORK_CHILD;
|
$exitcode = app2::EC_FORK_CHILD;
|
||||||
try {
|
try {
|
||||||
# puis lancer la commande
|
# puis lancer la commande
|
||||||
$cmd = new Cmd($args);
|
$cmd = new Cmd($args);
|
||||||
$cmd->addSource("/g/init.env");
|
$cmd->addSource("/g/init.env");
|
||||||
$cmd->addRedir("both", $logfile, true);
|
$cmd->addRedir("both", $logfile, true);
|
||||||
msg::debug("$pid: launching\n".$cmd->getCmd());
|
$cmd->fork_exec($exitcode, false);
|
||||||
$cmd->fork_exec($exitcode);
|
sh::_waitpid(-$pid, $exitcode);
|
||||||
msg::debug("$pid: exitcode=$exitcode");
|
|
||||||
return true;
|
|
||||||
} finally {
|
} finally {
|
||||||
$runfile->wfStopped($exitcode);
|
$runfile->wfReaped($exitcode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function _stop(Runfile $runfile): void {
|
private static function kill(int $pid, int $signal): bool {
|
||||||
$data = $runfile->read();
|
if (!posix_kill($pid, $signal)) {
|
||||||
$pid = $data["pg_pid"];
|
|
||||||
if ($pid === null) {
|
|
||||||
msg::warning("$data[name]: groupe de process inconnu");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
msg::action("kill $pid");
|
|
||||||
if (!posix_kill(-$pid, SIGKILL)) {
|
|
||||||
switch (posix_get_last_error()) {
|
switch (posix_get_last_error()) {
|
||||||
case PCNTL_ESRCH:
|
case PCNTL_ESRCH:
|
||||||
msg::afailure("process inexistant");
|
msg::afailure("process inexistant");
|
||||||
|
@ -102,17 +45,53 @@ class bg_launcher {
|
||||||
msg::afailure("signal invalide");
|
msg::afailure("signal invalide");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function _stop(Runfile $runfile): bool {
|
||||||
|
$data = $runfile->read();
|
||||||
|
$pid = $runfile->_getCid($data);
|
||||||
|
$stopped = false;
|
||||||
|
msg::action("term $pid");
|
||||||
$timeout = 10;
|
$timeout = 10;
|
||||||
while ($runfile->tm_isUndead($pid)) {
|
$delay = 300000;
|
||||||
sleep(1);
|
while (--$timeout >= 0) {
|
||||||
if (--$timeout == 0) {
|
if (!self::kill($pid, SIGTERM)) {
|
||||||
msg::afailure("impossible d'arrêter la tâche");
|
msg::afailure();
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
usleep($delay);
|
||||||
|
$delay = 1000000; // attendre 1 seconde à partir de la deuxième fois
|
||||||
|
if (!$runfile->_isRunning($data)) {
|
||||||
|
msg::asuccess();
|
||||||
|
$stopped = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$runfile->wfStopped(app2::EC_REAPABLE);
|
if (!$stopped) {
|
||||||
msg::asuccess();
|
msg::action("kill $pid");
|
||||||
|
$timeout = 3;
|
||||||
|
$delay = 300000;
|
||||||
|
while (--$timeout >= 0) {
|
||||||
|
if (!self::kill($pid, SIGKILL)) {
|
||||||
|
msg::afailure();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
usleep($delay);
|
||||||
|
$delay = 1000000; // attendre 1 seconde à partir de la deuxième fois
|
||||||
|
if (!$runfile->_isRunning($data)) {
|
||||||
|
msg::asuccess();
|
||||||
|
$stopped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($stopped) {
|
||||||
|
sh::_waitpid($pid, $exitcode);
|
||||||
|
$runfile->wfReaped($exitcode);
|
||||||
|
}
|
||||||
|
return $stopped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue