diff --git a/.idea/nur-sery.iml b/.idea/nur-sery.iml index 35cbd28..74e150b 100644 --- a/.idea/nur-sery.iml +++ b/.idea/nur-sery.iml @@ -8,6 +8,7 @@ + diff --git a/composer.json b/composer.json index b7690fd..03c0a02 100644 --- a/composer.json +++ b/composer.json @@ -49,8 +49,9 @@ }, "autoload": { "psr-4": { - "nur\\sery\\": "src", "nur\\sery\\wip\\": "wip", + "nur\\sery\\app\\": "wip_app", + "nur\\sery\\": "src", "nur\\": "nur_src" }, "files": [ diff --git a/composer.lock b/composer.lock index a32155a..0d9997a 100644 --- a/composer.lock +++ b/composer.lock @@ -75,20 +75,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -134,7 +134,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -150,20 +150,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.40", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83" + "reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/81cad0ceab3d61fe14fe941ff18a230ac9c80f83", - "reference": "81cad0ceab3d61fe14fe941ff18a230ac9c80f83", + "url": "https://api.github.com/repos/symfony/yaml/zipball/7025b964f123bbf1896d7563db6ec7f1f63e918a", + "reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a", "shasum": "" }, "require": { @@ -209,7 +209,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.40" + "source": "https://github.com/symfony/yaml/tree/v5.4.44" }, "funding": [ { @@ -225,7 +225,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-09-16T14:36:56+00:00" } ], "packages-dev": [ @@ -747,16 +747,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -799,9 +799,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "nulib/tests", @@ -955,16 +955,16 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "1.29.0", + "version": "1.29.2", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + "reference": "3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", - "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f", + "reference": "3a5a818d7d3e4b5bd2e56fb9de44dbded6eae07f", "shasum": "" }, "require": { @@ -999,7 +999,7 @@ "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.1", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "phpunit/phpunit": "^8.5 || ^9.0", "squizlabs/php_codesniffer": "^3.7", "tecnickcom/tcpdf": "^6.5" }, @@ -1054,41 +1054,41 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.2" }, - "time": "2023-06-14T22:48:31+00:00" + "time": "2024-09-29T07:04:47+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1097,7 +1097,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -1126,7 +1126,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -1134,7 +1134,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1379,16 +1379,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", "shasum": "" }, "require": { @@ -1403,7 +1403,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -1462,7 +1462,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" }, "funding": [ { @@ -1478,7 +1478,7 @@ "type": "tidelift" } ], - "time": "2024-07-10T11:45:39+00:00" + "time": "2024-09-19T10:50:18+00:00" }, { "name": "psr/http-client", @@ -2656,20 +2656,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2716,7 +2716,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -2732,7 +2732,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "theseer/tokenizer", diff --git a/lib/_launch.php b/lib/_launch.php deleted file mode 100755 index 9c34847..0000000 --- a/lib/_launch.php +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/php - parent::ARGS, - "purpose" => "lancer une tâche de fond", - "usage" => "ApplicationClass args...", - - ["-s", "--start", "name" => "action", "value" => self::ACTION_START, - "help" => "démarrer la tâche, c'est la valeur par défaut" - ], - ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP, - "help" => "arrêter la tâche" - ], - ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, - "help" => "afficher des informations sur la tâche" - ], - ]; - - protected $action = self::ACTION_START; - - protected $args; - - function main() { - $appClass = $this->args[0] ?? null; - if ($appClass === null) { - msg::error("Vous devez spécifier la classe de l'application"); - self::die(); - } elseif (!class_exists($appClass)) { - msg::error("$appClass: Cette classe n'existe pas"); - self::die(); - } - $args = array_slice($this->args, 1); - - $useRunfile = constant("$appClass::USE_RUNFILE"); - if (!$useRunfile) { - msg::error("Cette application ne supporte pas l'usage de runfile"); - self::die(); - } - - $runfile = app::with($appClass, self::$internal_use_app_params)->getRunfile(); - switch ($this->action) { - case self::ACTION_START: - launcher::_start($args, $runfile); - break; - case self::ACTION_STOP: - launcher::_stop($runfile); - break; - case self::ACTION_INFOS: - yaml::dump($runfile->read()); - break; - } - } -} -_LaunchApp::internal_use_set_app_params($app_params); -_LaunchApp::run(); diff --git a/nur_src/cli/Application.php b/nur_src/cli/Application.php index ad25a5f..55d6989 100644 --- a/nur_src/cli/Application.php +++ b/nur_src/cli/Application.php @@ -12,7 +12,7 @@ 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\app\app; use nur\sery\output\log as nlog; use nur\sery\output\msg as nmsg; use nur\sery\output\console as nconsole; diff --git a/src/app/RunFile.php b/src/app/RunFile.php index 047d021..5b29e25 100644 --- a/src/app/RunFile.php +++ b/src/app/RunFile.php @@ -1,11 +1,14 @@ name = $name ?? static::NAME; $this->file = new SharedFile($file); - $this->logfile = $logfile; + $this->outfile = $outfile; } protected ?string $name; protected SharedFile $file; - protected ?string $logfile; + protected ?string $outfile; - function getLogfile(): ?string { - return $this->logfile; + function getOutfile(): ?string { + return $this->outfile; } protected static function merge(array $data, array $merge): array { @@ -41,29 +44,23 @@ class RunFile { ], $merge); } - protected function initData(bool $forStart=true): array { - if ($forStart) { - $pid = posix_getpid(); - $dateStart = new DateTime(); - } else { - $pid = $dateStart = null; - } + protected function initData(): array { return [ "name" => $this->name, - "id" => bin2hex(random_bytes(16)), - "pg_pid" => null, - "pid" => $pid, + "pgid" => null, + "pid" => null, "serial" => 0, # lock "locked" => false, "date_lock" => null, "date_release" => null, # run - "logfile" => $this->logfile, - "date_start" => $dateStart, + "logfile" => $this->outfile, + "date_start" => null, "date_stop" => null, "exitcode" => null, - "is_done" => null, + "is_reaped" => null, + "is_ack_done" => null, # action "action" => null, "action_date_start" => null, @@ -73,9 +70,19 @@ class RunFile { ]; } + function reset(bool $delete=false) { + $file = $this->file; + if ($delete) { + $file->close(); + unlink($file->getFile()); + } else { + $file->ftruncate(); + } + } + function read(): array { $data = $this->file->unserialize(); - if (!is_array($data)) $data = $this->initData(false); + if (!is_array($data)) $data = $this->initData(); return $data; } @@ -84,7 +91,7 @@ class RunFile { $file->lockWrite(); $data = $file->unserialize(null, false, true); if (!is_array($data)) { - $data = $this->initData(false); + $data = $this->initData(); $file->ftruncate(); $file->serialize($data, false, true); } @@ -160,14 +167,38 @@ class RunFile { # cycle de vie de l'application /** - * indiquer que l'application démarre. l'état est entièrement réinitialisé, - * sauf le PID du leader qui est laissé en l'état + * Préparer le démarrage de l'application. Cette méhode est appelée par un + * 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(), [ + "pgid" => $pgid, + "pid" => null, + "date_start" => new DateTime(), + ]); + }); + } + + /** indiquer que l'application démarre. */ function wfStart(): void { $this->update(function (array $data) { - return cl::merge($this->initData(), [ - "pg_pid" => $data["pg_pid"], - ]); + $pid = posix_getpid(); + if ($data["pgid"] !== null) { + A::merge($data, [ + "pid" => $pid, + ]); + } else { + $data = cl::merge($this->initData(), [ + "pid" => $pid, + "date_start" => new DateTime(), + ]); + } + return $data; }); } @@ -183,13 +214,12 @@ class RunFile { return $data["date_start"] !== null && $data["date_stop"] === null; } - /** - * 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; + function _getCid(array $data=null): int { + if ($data["pgid"] !== null) return -$data["pgid"]; + else return $data["pid"]; + } + + function _isRunning(array $data=null): bool { if (!posix_kill($data["pid"], 0)) { switch (posix_get_last_error()) { case 1: #PCNTL_EPERM: @@ -208,10 +238,22 @@ class RunFile { 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 */ function wfStop(): void { $this->update(function (array $data) { - return ["date_stop" => new DateTime()]; + return [ + "date_stop" => new DateTime(), + ]; }); } @@ -228,88 +270,75 @@ class RunFile { } /** 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) { return [ - "pg_pid" => null, + "pgid" => null, "date_stop" => $data["date_stop"] ?? new DateTime(), "exitcode" => $exitcode, + "is_reaped" => true, ]; }); } - /** - * 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; + private static function kill(int $pid, int $signal, ?string &$reason=null): bool { + if (!posix_kill($pid, $signal)) { + switch (posix_get_last_error()) { + case PCNTL_ESRCH: + $reason = "process inexistant"; + break; + case PCNTL_EPERM: + $reason = "process non accessible"; + break; + case PCNTL_EINVAL: + $reason = "signal invalide"; + break; } - $done = true; - if ($updateDone) return ["is_done" => $done]; - else return null; - }); - return $done; + return false; + } + return true; } - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # gestion des actions - - /** indiquer le début d'une action */ - function action(?string $title, ?int $maxSteps=null): void { - $this->update(function (array $data) use ($title, $maxSteps) { - return [ - "action" => $title, - "action_date_start" => new DateTime(), - "action_max_step" => $maxSteps, - "action_current_step" => 0, - ]; - }); - } - - /** indiquer qu'une étape est franchie dans l'action en cours */ - function step(int $nbSteps=1): void { - $this->update(function (array $data) use ($nbSteps) { - return [ - "action_date_step" => new DateTime(), - "action_current_step" => $data["action_current_step"] + $nbSteps, - ]; - }); - } - - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Divers - - function getLockFile(?string $name=null, ?string $title=null): LockFile { - $ext = self::LOCK_EXT; - if ($name !== null) $ext = ".$name$ext"; - $file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT); - $name = str::join("/", [$this->name, $name]); - 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(), - ]; - }); + function wfKill(?string &$reason=null): bool { + $data = $this->read(); + $pid = $this->_getCid($data); + $stopped = false; + $timeout = 10; + $delay = 300000; + while (--$timeout >= 0) { + if (!self::kill($pid, SIGTERM, $reason)) return false; + usleep($delay); + $delay = 1000000; // attendre 1 seconde à partir de la deuxième fois + if (!$this->_isRunning($data)) { + $stopped = true; + break; + } + } + if (!$stopped) { + $timeout = 3; + $delay = 300000; + while (--$timeout >= 0) { + if (!self::kill($pid, SIGKILL, $reason)) return false; + usleep($delay); + $delay = 1000000; // attendre 1 seconde à partir de la deuxième fois + if (!$this->_isRunning($data)) { + $stopped = true; + break; + } + } + } + if ($stopped) { + sh::_waitpid($pid, $exitcode); + $this->wfReaped($exitcode); + } + return $stopped; } /** * 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 { + function _isUndead(?int $pid=null): bool { $data = $this->read(); if ($data["date_start"] === null) return false; if ($data["date_stop"] !== null) return false; @@ -332,23 +361,104 @@ class RunFile { return false; } - function tm_isReapable(): bool { - $data = $this->read(); - return $data["date_stop"] !== null && $data["exitcode"] === null; + /** + * 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_ack_done"]) { + return false; + } + $done = true; + if ($updateDone) return ["is_ack_done" => $done]; + else return null; + }); + return $done; } - /** 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) { + 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 + + /** indiquer le début d'une action */ + function action(?string $title, ?int $maxSteps=null): void { + $this->update(function (array $data) use ($title, $maxSteps) { return [ - "pg_pid" => null, - "date_stop" => $data["date_stop"] ?? new DateTime(), - "exitcode" => $data["exitcode"] ?? $exitcode, + "action" => $title, + "action_date_start" => new DateTime(), + "action_max_step" => $maxSteps, + "action_current_step" => 0, ]; }); + app::_dispatch_signals(); + } + + /** indiquer qu'une étape est franchie dans l'action en cours */ + function step(int $nbSteps=1): void { + $this->update(function (array $data) use ($nbSteps) { + return [ + "action_date_step" => new DateTime(), + "action_current_step" => $data["action_current_step"] + $nbSteps, + ]; + }); + app::_dispatch_signals(); + } + + function getActionDesc(?array $data=null): ?string { + $data ??= $this->read(); + $action = $data["action"]; + if ($action !== null) { + $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; + } + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Divers + + function getLockFile(?string $name=null, ?string $title=null): LockFile { + $ext = self::LOCK_EXT; + if ($name !== null) $ext = ".$name$ext"; + $file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT); + $name = str::join("/", [$this->name, $name]); + return new LockFile($file, $name, $title); } } diff --git a/wip/app/app2.php b/src/app/app.php similarity index 99% rename from wip/app/app2.php rename to src/app/app.php index 83bc1fc..5ddbfcb 100644 --- a/wip/app/app2.php +++ b/src/app/app.php @@ -1,8 +1,8 @@ $name, ]); require $app; diff --git a/src/app/cli/template-bg-launcher.php b/src/app/cli/template-bg-launcher.php index bf4000a..a751c5c 100755 --- a/src/app/cli/template-bg-launcher.php +++ b/src/app/cli/template-bg-launcher.php @@ -7,12 +7,12 @@ require __DIR__.'/../vendor/autoload.php'; # (par défaut c'est le répertoire bin/) et modifier les paramètres si nécessaire use nur\sery\tools\BgLauncherApp; -use nur\sery\wip\app\app2; +use nur\sery\app\app; # chemin vers le lanceur PHP const NULIB_APP_app_launcher = __DIR__.'/../_cli/_launcher.php'; -app2::init([ +app::init([ "projdir" => __DIR__ . '/..', "appcode" => \app\config\bootstrap::APPCODE, ]); diff --git a/src/app/launcher.php b/src/app/launcher.php deleted file mode 100644 index b85530e..0000000 --- a/src/app/launcher.php +++ /dev/null @@ -1,137 +0,0 @@ - $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 { - if (!cl::is_list($args)) { - $fixedArgs = []; - $index = 0; - foreach ($args as $arg => $value) { - if ($arg === $index) { - $index++; - $fixedArgs[] = $value; - continue; - } elseif ($value === false) { - continue; - } - $arg = str::us2camel($arg); - $arg = str::camel2us($arg, false, "-"); - $arg = str_replace("_", "-", $arg); - $fixedArgs[] = "--$arg"; - if ($value !== true) $fixedArgs[] = "$value"; - } - $args = $fixedArgs; - } - # 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 = app::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(); - if ($pid == -1) { - # parent, impossible de forker - throw new StateException("unable to fork"); - } elseif ($pid) { - # parent, fork ok - return true; - } 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_launcher-_start.log"; - $pid = posix_getpid(); - $exitcode = -776; - try { - # puis lancer la commande - $cmd = new Cmd($args); - $cmd->addSource("/g/init.env"); - $cmd->addRedir("both", $logfile, true); - msg::debug("$pid: launching\n".$cmd->getCmd()); - $cmd->fork_exec($exitcode); - msg::debug("$pid: exitcode=$exitcode"); - return true; - } finally { - $runfile->wfStopped($exitcode); - } - } - } - - static function _stop(Runfile $runfile): void { - $data = $runfile->read(); - $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()) { - case PCNTL_ESRCH: - msg::afailure("process inexistant"); - break; - case PCNTL_EPERM: - msg::afailure("process non accessible"); - break; - case PCNTL_EINVAL: - msg::afailure("signal invalide"); - break; - } - return; - } - $timeout = 10; - while ($runfile->tm_isUndead($pid)) { - sleep(1); - if (--$timeout == 0) { - msg::afailure("impossible d'arrêter la tâche"); - return; - } - } - $runfile->wfStopped(-778); - msg::asuccess(); - } -} diff --git a/src/os/sh.php b/src/os/sh.php index 91bf370..04e6075 100644 --- a/src/os/sh.php +++ b/src/os/sh.php @@ -4,7 +4,7 @@ namespace nur\sery\os; use nur\sery\cl; use nur\sery\ExitError; use nur\sery\StateException; -use nur\sery\wip\app\app2; +use nur\sery\app\app; class sh { static final function _quote(string $value): string { @@ -140,7 +140,7 @@ class sh { 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; + else $retcode = app::EC_FORK_CHILD; return $retcode == 0; } @@ -154,7 +154,7 @@ class sh { $pid = pcntl_fork(); if ($pid == -1) { // parent, impossible de forker - throw new ExitError(app2::EC_FORK_PARENT, "unable to fork"); + throw new ExitError(app::EC_FORK_PARENT, "unable to fork"); } elseif ($pid) { // parent, fork ok if ($wait) return self::_waitpid($pid, $retcode); diff --git a/src/tools/BgLauncherApp.php b/src/tools/BgLauncherApp.php index bacd588..25dd365 100644 --- a/src/tools/BgLauncherApp.php +++ b/src/tools/BgLauncherApp.php @@ -6,9 +6,9 @@ use nur\sery\os\path; use nur\sery\os\proc\Cmd; use nur\sery\os\sh; use nur\sery\output\msg; -use nur\sery\wip\app\app2; -use nur\sery\wip\app\cli\Application; -use nur\sery\wip\app\RunFile; +use nur\sery\app\app; +use nur\sery\app\cli\Application; +use nur\sery\app\RunFile; use nur\yaml; class BgLauncherApp extends Application { @@ -58,16 +58,16 @@ class BgLauncherApp extends Application { self::die("Cette application ne supporte le lancement en tâche de fond"); } - $runfile = app2::with($appClass)->getRunfile(); + $runfile = app::with($appClass)->getRunfile(); switch ($this->action) { case self::ACTION_START: $appClass::_manage_runfile(count($args), $args, $runfile); - if ($runfile->warnIfLocked()) self::exit(app2::EC_LOCKED); + if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED); array_splice($args, 0, 0, [ PHP_BINARY, path::abspath(NULIB_APP_app_launcher), ]); - app2::params_putenv(); + app::params_putenv(); self::_start($args, $runfile); break; case self::ACTION_STOP: @@ -84,20 +84,18 @@ class BgLauncherApp extends Application { $pid = pcntl_fork(); if ($pid == -1) { # parent, impossible de forker - throw new ExitError(app2::EC_FORK_PARENT, "Unable to fork"); + throw new ExitError(app::EC_FORK_PARENT, "Unable to fork"); } elseif (!$pid) { # child, fork ok $runfile->wfPrepare($pid); $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out"; - $exitcode = app2::EC_FORK_CHILD; + $exitcode = app::EC_FORK_CHILD; try { # rediriger STDIN, STDOUT et STDERR - fclose(STDIN); - $in = fopen("/dev/null", "rb"); - fclose(STDOUT); - $out = fopen($outfile, "a+b"); - fclose(STDERR); - $err = fopen($outfile, "a+b"); + fclose(fopen($outfile, "wb")); // vider le fichier + fclose(STDIN); $in = fopen("/dev/null", "rb"); + fclose(STDOUT); $out = fopen($outfile, "ab"); + fclose(STDERR); $err = fopen($outfile, "ab"); # puis lancer la commande $cmd = new Cmd($args); $cmd->addSource("/g/init.env"); diff --git a/src/tools/SteamTrainApp.php b/src/tools/SteamTrainApp.php index 75cd41e..0e7e11d 100644 --- a/src/tools/SteamTrainApp.php +++ b/src/tools/SteamTrainApp.php @@ -4,8 +4,8 @@ namespace nur\sery\tools; use nur\sery\output\msg; use nur\sery\php\time\DateTime; use nur\sery\text\words; -use nur\sery\wip\app\app2; -use nur\sery\wip\app\cli\Application; +use nur\sery\app\app; +use nur\sery\app\cli\Application; class SteamTrainApp extends Application { const PROJDIR = __DIR__.'/../..'; @@ -33,13 +33,13 @@ EOT, protected bool $installSignalHandler = true; function main() { - if ($this->installSignalHandler) app2::install_signal_handler(); + if ($this->installSignalHandler) app::install_signal_handler(); $count = intval($this->count); msg::info("Starting train for ".words::q($count, "step#s")); - app2::action("Running train...", $count); + app::action("Running train...", $count); for ($i = 1; $i <= $count; $i++) { msg::print("Tchou-tchou! x $i"); - app2::step(); + app::step(); sleep(1); } msg::info("Stopping train at ".new DateTime()); diff --git a/tests/wip/app/app2Test.php b/tests/wip/app/app2Test.php index 661cf36..a5bcf29 100644 --- a/tests/wip/app/app2Test.php +++ b/tests/wip/app/app2Test.php @@ -2,7 +2,7 @@ namespace nur\sery\wip\app { use nulib\tests\TestCase; use nur\sery\wip\app\impl\config; - use nur\sery\wip\app\impl\myapp2; + use nur\sery\wip\app\impl\myapp; use nur\sery\wip\app\impl\MyApplication1; use nur\sery\wip\app\impl\MyApplication2; @@ -11,8 +11,8 @@ namespace nur\sery\wip\app { $projdir = config::get_projdir(); $cwd = getcwd(); - myapp2::reset(); - $app1 = myapp2::with(MyApplication1::class); + myapp::reset(); + $app1 = myapp::with(MyApplication1::class); self::assertSame([ "projdir" => $projdir, "vendor" => [ @@ -30,7 +30,7 @@ namespace nur\sery\wip\app { "title" => null, ], $app1->getParams()); - $app2 = myapp2::with(MyApplication2::class, $app1); + $app2 = myapp::with(MyApplication2::class, $app1); self::assertSame([ "projdir" => $projdir, "vendor" => [ @@ -53,8 +53,8 @@ namespace nur\sery\wip\app { $projdir = config::get_projdir(); $cwd = getcwd(); - myapp2::reset(); - myapp2::init(MyApplication1::class); + myapp::reset(); + myapp::init(MyApplication1::class); self::assertSame([ "projdir" => $projdir, "vendor" => [ @@ -70,9 +70,9 @@ namespace nur\sery\wip\app { "profile" => "devel", "name" => "my-application1", "title" => null, - ], myapp2::get()->getParams()); + ], myapp::get()->getParams()); - myapp2::init(MyApplication2::class); + myapp::init(MyApplication2::class); self::assertSame([ "projdir" => $projdir, "vendor" => [ @@ -88,7 +88,7 @@ namespace nur\sery\wip\app { "profile" => "devel", "name" => "my-application2", "title" => null, - ], myapp2::get()->getParams()); + ], myapp::get()->getParams()); } } } @@ -96,7 +96,7 @@ namespace nur\sery\wip\app { namespace nur\sery\wip\app\impl { use nur\cli\Application2; use nur\sery\os\path; - use nur\sery\wip\app\app2; + use nur\sery\app\app; class config { const PROJDIR = __DIR__.'/../../..'; @@ -106,7 +106,7 @@ namespace nur\sery\wip\app\impl { } } - class myapp2 extends app2 { + class myapp extends app { static function reset(): void { self::$app = null; } diff --git a/tests/wip/app/appTest.php b/tests/wip/app/appTest.php index dca7ab3..4ea66a4 100644 --- a/tests/wip/app/appTest.php +++ b/tests/wip/app/appTest.php @@ -3,6 +3,7 @@ namespace nur\sery\wip\app; use nulib\tests\TestCase; +use nur\sery\app\app; class appTest extends TestCase { function testVerifix_name() { diff --git a/tests/wip/app/argsTest.php b/tests/wip/app/argsTest.php index bea16f0..8a8b1f1 100644 --- a/tests/wip/app/argsTest.php +++ b/tests/wip/app/argsTest.php @@ -3,6 +3,7 @@ namespace nur\sery\wip\app; use nulib\tests\TestCase; +use nur\sery\app\args; class argsTest extends TestCase { function testFrom_array() { diff --git a/wip/app/LockFile.php b/wip/app/LockFile.php deleted file mode 100644 index 743f22c..0000000 --- a/wip/app/LockFile.php +++ /dev/null @@ -1,89 +0,0 @@ -file = new SharedFile($file); - $this->name = $name ?? static::NAME; - $this->title = $title ?? static::TITLE; - } - - /** @var SharedFile */ - protected $file; - - /** @var ?string */ - protected $name; - - /** @var ?string */ - protected $title; - - protected function initData(): array { - return [ - "name" => $this->name, - "title" => $this->title, - "locked" => false, - "date_lock" => null, - "date_release" => null, - ]; - } - - function read(bool $close=true): array { - $data = $this->file->unserialize(null, $close); - if (!is_array($data)) $data = $this->initData(); - return $data; - } - - function isLocked(?array &$data=null): bool { - $data = $this->read(); - return $data["locked"]; - } - - function warnIfLocked(?array $data=null): bool { - if ($data === null) $data = $this->read(); - if ($data["locked"]) { - msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]"); - return true; - } - return false; - } - - function lock(?array &$data=null): bool { - $file = $this->file; - $data = $this->read(false); - if ($data["locked"]) { - $file->close(); - return false; - } else { - $file->ftruncate(); - $file->serialize(cl::merge($data, [ - "locked" => true, - "date_lock" => new DateTime(), - "date_release" => null, - ])); - return true; - } - } - - function release(?array &$data=null): void { - $file = $this->file; - $data = $this->read(false); - $file->ftruncate(); - $file->serialize(cl::merge($data, [ - "locked" => false, - "date_release" => new DateTime(), - ])); - } -} diff --git a/wip/app/RunFile.php b/wip/app/RunFile.php deleted file mode 100644 index dbc7946..0000000 --- a/wip/app/RunFile.php +++ /dev/null @@ -1,464 +0,0 @@ -name = $name ?? static::NAME; - $this->file = new SharedFile($file); - $this->outfile = $outfile; - } - - protected ?string $name; - - protected SharedFile $file; - - protected ?string $outfile; - - function getOutfile(): ?string { - return $this->outfile; - } - - protected static function merge(array $data, array $merge): array { - return cl::merge($data, [ - "serial" => $data["serial"] + 1, - ], $merge); - } - - protected function initData(): array { - return [ - "name" => $this->name, - "pgid" => null, - "pid" => null, - "serial" => 0, - # lock - "locked" => false, - "date_lock" => null, - "date_release" => null, - # run - "logfile" => $this->outfile, - "date_start" => null, - "date_stop" => null, - "exitcode" => null, - "is_reaped" => null, - "is_ack_done" => null, - # action - "action" => null, - "action_date_start" => null, - "action_current_step" => null, - "action_max_step" => null, - "action_date_step" => null, - ]; - } - - function reset(bool $delete=false) { - $file = $this->file; - if ($delete) { - $file->close(); - unlink($file->getFile()); - } else { - $file->ftruncate(); - } - } - - function read(): array { - $data = $this->file->unserialize(); - if (!is_array($data)) $data = $this->initData(); - return $data; - } - - protected function willWrite(): array { - $file = $this->file; - $file->lockWrite(); - $data = $file->unserialize(null, false, true); - if (!is_array($data)) { - $data = $this->initData(); - $file->ftruncate(); - $file->serialize($data, false, true); - } - 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); - if ($merge !== null && $merge !== false) { - $this->serialize($file, $data, $merge); - } else { - $file->cancelWrite(); - } - } - - function haveWorked(int $serial, ?int &$currentSerial=null, ?array $data=null): bool { - $data ??= $this->read(); - $currentSerial = $data["serial"]; - return $serial !== $currentSerial; - } - - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # verrouillage par défaut - - function isLocked(?array &$data=null): bool { - $data = $this->read(); - return $data["locked"]; - } - - function warnIfLocked(?array $data=null): bool { - $data ??= $this->read(); - if ($data["locked"]) { - msg::warning("$data[name]: possède le verrou depuis $data[date_lock]"); - return true; - } - return false; - } - - function lock(): bool { - $this->update(function ($data) use (&$locked) { - if ($data["locked"]) { - $locked = false; - return null; - } else { - $locked = true; - return [ - "locked" => true, - "date_lock" => new DateTime(), - "date_release" => null, - ]; - } - }); - return $locked; - } - - function release(): void { - $this->update(function ($data) { - return [ - "locked" => false, - "date_release" => new DateTime(), - ]; - }); - } - - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # cycle de vie de l'application - - /** - * Préparer le démarrage de l'application. Cette méhode est appelée par un - * 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(), [ - "pgid" => $pgid, - "pid" => null, - "date_start" => new DateTime(), - ]); - }); - } - - /** indiquer que l'application démarre. */ - function wfStart(): void { - $this->update(function (array $data) { - $pid = posix_getpid(); - if ($data["pgid"] !== null) { - A::merge($data, [ - "pid" => $pid, - ]); - } else { - $data = cl::merge($this->initData(), [ - "pid" => $pid, - "date_start" => new DateTime(), - ]); - } - return $data; - }); - } - - /** tester si l'application a déjà été démarrée au moins une fois */ - function wasStarted(?array $data=null): bool { - $data ??= $this->read(); - return $data["date_start"] !== null; - } - - /** tester si l'application est démarrée et non arrêtée */ - function isStarted(?array $data=null): bool { - $data ??= $this->read(); - return $data["date_start"] !== null && $data["date_stop"] === null; - } - - function _getCid(array $data=null): int { - if ($data["pgid"] !== null) return -$data["pgid"]; - else return $data["pid"]; - } - - function _isRunning(array $data=null): bool { - if (!posix_kill($data["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 false; - case 22: #PCNTL_EINVAL: - # ne devrait pas se produire - return false; - } - } - # process existant auquel on a accès - 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 */ - function wfStop(): void { - $this->update(function (array $data) { - return [ - "date_stop" => new DateTime(), - ]; - }); - } - - /** tester si l'application est déjà été stoppée au moins une fois */ - function wasStopped(?array $data=null): bool { - $data ??= $this->read(); - return $data["date_stop"] !== null; - } - - /** tester si l'application a été démarrée puis arrêtée */ - function isStopped(?array $data=null): bool { - $data ??= $this->read(); - return $data["date_start"] !== null && $data["date_stop"] !== null; - } - - /** après l'arrêt de l'application, mettre à jour le code de retour */ - function wfReaped(int $exitcode): void { - $this->update(function (array $data) use ($exitcode) { - return [ - "pgid" => null, - "date_stop" => $data["date_stop"] ?? new DateTime(), - "exitcode" => $exitcode, - "is_reaped" => true, - ]; - }); - } - - private static function kill(int $pid, int $signal, ?string &$reason=null): bool { - if (!posix_kill($pid, $signal)) { - switch (posix_get_last_error()) { - case PCNTL_ESRCH: - $reason = "process inexistant"; - break; - case PCNTL_EPERM: - $reason = "process non accessible"; - break; - case PCNTL_EINVAL: - $reason = "signal invalide"; - break; - } - return false; - } - return true; - } - - function wfKill(?string &$reason=null): bool { - $data = $this->read(); - $pid = $this->_getCid($data); - $stopped = false; - $timeout = 10; - $delay = 300000; - while (--$timeout >= 0) { - if (!self::kill($pid, SIGTERM, $reason)) return false; - usleep($delay); - $delay = 1000000; // attendre 1 seconde à partir de la deuxième fois - if (!$this->_isRunning($data)) { - $stopped = true; - break; - } - } - if (!$stopped) { - $timeout = 3; - $delay = 300000; - while (--$timeout >= 0) { - if (!self::kill($pid, SIGKILL, $reason)) return false; - usleep($delay); - $delay = 1000000; // attendre 1 seconde à partir de la deuxième fois - if (!$this->_isRunning($data)) { - $stopped = true; - break; - } - } - } - if ($stopped) { - sh::_waitpid($pid, $exitcode); - $this->wfReaped($exitcode); - } - return $stopped; - } - - /** - * 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 - * $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_ack_done"]) { - return false; - } - $done = true; - if ($updateDone) return ["is_ack_done" => $done]; - else return null; - }); - 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 - - /** indiquer le début d'une action */ - function action(?string $title, ?int $maxSteps=null): void { - $this->update(function (array $data) use ($title, $maxSteps) { - return [ - "action" => $title, - "action_date_start" => new DateTime(), - "action_max_step" => $maxSteps, - "action_current_step" => 0, - ]; - }); - app2::_dispatch_signals(); - } - - /** indiquer qu'une étape est franchie dans l'action en cours */ - function step(int $nbSteps=1): void { - $this->update(function (array $data) use ($nbSteps) { - return [ - "action_date_step" => new DateTime(), - "action_current_step" => $data["action_current_step"] + $nbSteps, - ]; - }); - app2::_dispatch_signals(); - } - - function getActionDesc(?array $data=null): ?string { - $data ??= $this->read(); - $action = $data["action"]; - if ($action !== null) { - $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; - } - - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Divers - - function getLockFile(?string $name=null, ?string $title=null): LockFile { - $ext = self::LOCK_EXT; - if ($name !== null) $ext = ".$name$ext"; - $file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT); - $name = str::join("/", [$this->name, $name]); - return new LockFile($file, $name, $title); - } -} diff --git a/wip/app/app.php b/wip/app/app.php deleted file mode 100644 index a403bb8..0000000 --- a/wip/app/app.php +++ /dev/null @@ -1,366 +0,0 @@ - "vendor/bin", - "autoload" => "vendor/autoload.php", - ]; - - private static function isa_Application($app): bool { - if (!is_string($app)) return false; - return $app === Application::class || is_subclass_of($app, Application::class); - } - - private static function verifix_name(string &$name): void { - # si $name est une classe, enlever le package et normaliser - # my\package\MyApplication --> my-application - $name = preg_replace('/.*\\\\/', "", $name); - $name = str::without_suffix("-app", str::camel2us($name, false, "-")); - } - - /** @param Application|string */ - static function with($app, ?array $internal_use_params=null): self { - if ($app instanceof Application) { - $params = [ - "projdir" => $app::PROJDIR, - "vendor" => $app::VENDOR, - "appcode" => $app::APPCODE, - "apptype" => "cli", - "name" => $app::NAME, - "title" => $app::TITLE, - "datadir" => $app::DATADIR, - "etcdir" => $app::ETCDIR, - "vardir" => $app::VARDIR, - "logdir" => $app::LOGDIR, - ]; - } elseif (self::isa_Application($app)) { - $params = [ - "projdir" => constant("$app::PROJDIR"), - "vendor" => constant("$app::VENDOR"), - "appcode" => constant("$app::APPCODE"), - "apptype" => "cli", - "name" => constant("$app::NAME"), - "title" => constant("$app::TITLE"), - "datadir" => constant("$app::DATADIR"), - "etcdir" => constant("$app::ETCDIR"), - "vardir" => constant("$app::VARDIR"), - "logdir" => constant("$app::LOGDIR"), - ]; - } elseif (is_array($app)) { - $params = $app; - } else { - throw ValueException::invalid_type($app, Application::class); - } - if ($internal_use_params !== null) { - $params = array_merge($internal_use_params, cl::selectm($params, [ - "name", - "title", - ], [ - "apptype" => "cli", - ])); - self::verifix_name($params["name"]); - } - return new static($params, $internal_use_params !== null); - } - - protected static ?app $app = null; - - static function init($app, ?array $internal_use_params=null): void { - self::$app = static::with($app, $internal_use_params); - } - - static function get(): self { - return self::$app ??= new self(null); - } - - function __construct(?array $params, bool $internalUse_asis=false) { - if ($internalUse_asis) { - [ - "projdir" => $this->projdir, - "vendor" => $this->vendor, - "appcode" => $this->appcode, - "apptype" => $this->apptype, - "name" => $this->name, - "title" => $this->title, - "profile" => $this->profile, - "cwd" => $this->cwd, - "datadir" => $this->datadir, - "etcdir" => $this->etcdir, - "vardir" => $this->vardir, - "logdir" => $this->logdir, - ] = $params; - } else { - $this->projdir = $projdir = path::abspath($params["projdir"] ?? "."); - $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; - $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); - $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); - $this->vendor = $vendor; - $this->appcode = $appcode = $params["appcode"] ?? "app"; - $this->apptype = $apptype = $params["apptype"] ?? "cli"; - $name = $params["name"] ?? null; - if ($name === null) { - $name = $appcode; - } else { - # si $name est une classe, enlever le package et normaliser - $name = preg_replace('/.*\\\\/', "", $name); - $name = str::without_suffix("-app", str::camel2us($name, false, "-")); - } - $this->name = $name; - $this->title = $params["title"] ?? null; - $appcode = str_replace("-", "_", strtoupper($appcode)); - # profile - $profile = getenv("${appcode}_PROFILE"); - if ($profile === false) $profile = getenv("APP_PROFILE"); - if ($profile === false) $profile = $params["profile"] ?? null; - if ($profile === null) { - if (file_exists("$projdir/.default-profile-devel")) $profile = "devel"; - else $profile = "prod"; - } - $this->profile = $profile; - # cwd - $this->cwd = getcwd(); - # datadir - $datadir = getenv("${appcode}_DATADIR"); - if ($datadir === false) $datadir = $params["datadir"] ?? null; - if ($datadir === null) $datadir = "devel/$apptype"; - $this->datadir = $datadir = path::reljoin($projdir, $datadir); - # etcdir - $etcdir = getenv("${appcode}_ETCDIR"); - if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; - if ($etcdir === null) $etcdir = "etc"; - $this->etcdir = $etcdir = path::reljoin($datadir, $etcdir); - # vardir - $vardir = getenv("${appcode}_VARDIR"); - if ($vardir === false) $vardir = $params["vardir"] ?? null; - if ($vardir === null) $vardir = "var"; - $this->vardir = $vardir = path::reljoin($datadir, $vardir); - # logdir - $logdir = getenv("${appcode}_LOGDIR"); - if ($logdir === false) $logdir = $params["logdir"] ?? null; - if ($logdir === null) $logdir = "log"; - $this->logdir = $logdir = path::reljoin($datadir, $logdir); - } - } - - /** recréer le tableau des paramètres */ - function getParams(): array { - return [ - "projdir" => $this->projdir, - "vendor" => $this->vendor, - "appcode" => $this->appcode, - "apptype" => $this->apptype, - "name" => $this->name, - "title" => $this->title, - "profile" => $this->profile, - "cwd" => $this->cwd, - "datadir" => $this->datadir, - "etcdir" => $this->etcdir, - "vardir" => $this->vardir, - "logdir" => $this->logdir, - ]; - } - - protected string $projdir; - - function getProjdir(): string { - return $this->projdir; - } - - protected array $vendor; - - function getVendorBindir(): string { - return $this->vendor["bindir"]; - } - - function getVendorAutoload(): string { - return $this->vendor["autoload"]; - } - - protected string $appcode; - - function getAppcode(): string { - return $this->appcode; - } - - protected string $apptype; - - function getApptype(): string { - return $this->apptype; - } - - protected string $name; - - function getName(): ?string { - return $this->name; - } - - protected ?string $title; - - function getTitle(): ?string { - return $this->title; - } - - protected string $profile; - - function getProfile(): string { - return $this->profile; - } - - /** - * @param ?string|false $profile - */ - function withProfile(string $file, $profile): string { - if ($profile !== false) { - if ($profile === null) $profile = $this->getProfile(); - [$dir, $filename] = path::split($file); - $basename = path::basename($filename); - $ext = path::ext($file); - $file = path::join($dir, "$basename.$profile$ext"); - } - return $file; - } - - function findFile(array $dirs, array $names, $profile=null): string { - # d'abord chercher avec le profil - if ($profile !== false) { - foreach ($dirs as $dir) { - foreach ($names as $name) { - $file = path::join($dir, $name); - $file = $this->withProfile($file, $profile); - if (file_exists($file)) return $file; - } - } - } - # puis sans profil - foreach ($dirs as $dir) { - foreach ($names as $name) { - $file = path::join($dir, $name); - if (file_exists($file)) return $file; - } - } - # la valeur par défaut est avec profil - return $this->withProfile(path::join($dirs[0], $names[0]), $profile); - } - - function fencedJoin(string $basedir, string $path): string { - $path = path::reljoin($basedir, $path); - if (!path::is_within($path, $basedir)) { - throw ValueException::invalid_value($path, "path"); - } - return $path; - } - - protected string $cwd; - - function getCwd(): string { - return $this->cwd; - } - - protected string $datadir; - - function getDatadir(): string { - return $this->datadir; - } - - protected string $etcdir; - - function getEtcdir(): string { - return $this->etcdir; - } - - function getEtcfile(string $name, $profile=null): string { - return $this->findFile([$this->etcdir], [$name], $profile); - } - - protected string $vardir; - - function getVardir(): string { - return $this->vardir; - } - - function getVarfile(string $name, $profile=null): string { - $file = $this->withProfile($this->fencedJoin($this->vardir, $name), $profile); - sh::mkdirof($file); - return $file; - } - - protected string $logdir; - - function getLogdir(): string { - return $this->logdir; - } - - function getLogfile(?string $name=null, $profile=null): string { - if ($name === null) $name = "{$this->name}.log"; - $file = $this->withProfile($this->fencedJoin($this->logdir, $name), $profile); - sh::mkdirof($file); - return $file; - } - - /** - * obtenir le chemin absolu vers un fichier de travail - * - si le chemin est absolu, il est inchangé - * - si le chemin est qualifié (commence par ./ ou ../) ou sans chemin, il est - * exprimé par rapport à $vardir - * - sinon le chemin est exprimé par rapport au répertoire de travail de base - * $datadir - * - * is $ensure_dir, créer le répertoire du fichier s'il n'existe pas déjà - */ - function getWorkfile(?string $file, $profile=null, bool $ensureDir=true): ?string { - if ($file === null) return null; - if (path::is_qualified($file) || !path::have_dir($file)) { - $file = path::reljoin($this->vardir, $file); - } else { - $file = path::reljoin($this->datadir, $file); - } - $file = $this->withProfile($file, $profile); - if ($ensureDir) sh::mkdirof($file); - return $file; - } - - /** - * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. - * - si le chemin commence par /, il est laissé en l'état - * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd - * - sinon le chemin est exprimé par rapport au répertoire de travail $vardir - */ - function getUserfile(?string $file): ?string { - if ($file === null) return null; - if (path::is_qualified($file)) { - return path::reljoin($this->cwd, $file); - } else { - return path::reljoin($this->vardir, $file); - } - } - - protected ?RunFile $runfile = null; - - function getRunfile(): RunFile { - $name = $this->name; - $runfile = $this->getWorkfile($name); - $logfile = $this->getLogfile(); - return $this->runfile ??= new RunFile($name, $runfile, $logfile); - } - - protected ?array $lockFiles = null; - - function getLockfile(?string $name=null): LockFile { - $this->lockFiles[$name] ??= $this->getRunfile()->getLockFile($name, $this->title); - return $this->lockFiles[$name]; - } -} diff --git a/wip/os/proc/ManagedTask.php b/wip/os/proc/ManagedTask.php deleted file mode 100644 index 68bd4e3..0000000 --- a/wip/os/proc/ManagedTask.php +++ /dev/null @@ -1,576 +0,0 @@ - ["string", null, "identifiant de la tâche"], - "serial" => ["int", null, "numéro de série permettant de distinguer deux occurrences de la tâche"], - "title" => ["?string", null, "description de la tâche"], - "valid" => ["bool", false, "la tâche est-elle valide?"], - "owner_login" => ["?string", null, "compte de la personne qui a lancé la tâche"], - "owner_name" => ["?string", null, "nom de la personne qui a lancé la tâche"], - "owner_page" => ["?string", null, "page qui a créé cette tâche"], - "owner_params" => ["?array", null, "paramètres à passer à la page"], - "cmd" => [null, null, "commande à lancer"], - "logfile" => ["?string", null, "sortie de la commande"], - ]; - - const SCHEMA = [ - "definition" => [ - "?array", null, "définition de la tâche", - "schema" => self::DEFINITION_SCHEMA, - ], - "state" => [ - "?array", null, "instance de la tâche", - "schema" => [ - "definition" => [ - "array", null, "copie de la définition de la tâche", - "schema" => self::DEFINITION_SCHEMA, - ], - "started" => ["bool", false, "la tâche a-t-elle été démarrée?"], - "date_start" => ["?datetime", null, "date du démarrage de la tâche"], - "pid" => ["?int", null, "PID du process contrôleur"], - "status" => ["?string", null, "Message de statut indiqué par la tâche"], - "stopped" => ["bool", false, "la tâche est-elle terminée?"], - "date_stop" => ["?datetime", null, "date de l'arrêt de la tâche"], - "retcode" => ["?int", null, "code de retour de la commande"], - "done" => ["bool", false, "la fin de la tâche a-t-elle été prise en compte?"], - ], - ], - "" => [ - "auto_properties" => [ - "id" => "definition.id", - "serial" => "definition.serial", - "title" => "definition.title", - "valid" => "definition.valid", - "owner_login" => "definition.owner_login", - "owner_name" => "definition.owner_name", - "owner_page" => "definition.owner_page", - "owner_params" => "definition.owner_params", - "cmd" => "definition.cmd", - "logfile" => "definition.logfile", - ] - ], - ]; - - const _AUTO_PROPERTIES = self::SCHEMA[""]["auto_properties"]; - - const _SCHEMA = [ - "id" => ["string", null, "identifiant de la tâche"], - "serial" => ["string", null, "numéro de série permettant de distinguer deux occurrences de la tâche"], - "title" => ["?string", null, "description de la tâche"], - "valid" => ["bool", false, "la tâche est-elle valide?"], - "owner_login" => ["?string", null, "compte de la personne qui a lancé la tâche"], - "owner_name" => ["?string", null, "nom de la personne qui a lancé la tâche"], - "page" => ["?array", null, "page qui a créé cette tâche et paramètres à passer à la page"], - "cmd" => [null, null, "commande à lancer"], - "logfile" => ["?string", null, "sortie de commande"], - "started" => ["bool", false, "la tâche a-t-elle été démarrée?"], - "date_start" => ["?datetime", null, "date du démarrage de la tâche"], - "pid" => ["?int", null, "PID du process contrôleur"], - "stopped" => ["bool", false, "la tâche est-elle terminée?"], - "date_stop" => ["?datetime", null, "date de l'arrêt de la tâche"], - "retcode" => ["?int", null, "code de retour de la commande"], - "done" => ["bool", false, "la fin de la tâche a-t-elle été prise en compte?"], - ]; - - function __construct(string $id, bool $autoUpdate=false, ?callable $init=null) { - # ne pas appeler parent::__construct() - if (file_exists($id)) { - $file = $id; - } else { - $authz = authz::get(); - $this->data = [ - "id" => $id, - "serial" => 0, - "owner_login" => $authz->getUsername(), - "owner_name" => $authz->getDisplayName(), - ]; - $file = tasks::pf("$id.task"); - } - $this->init = $init; - $this->file = $file; - $this->ensureTask($autoUpdate); - } - - /** @var ?callable */ - protected $init; - - /** @var string */ - protected $file; - - private function ensureTask(bool $autoUpdate): void { - lock::exlusive(self::LOCK); - try { - if (is_file($this->file)) { - $this->_reload(); - } else { - $this->_reset(); - $this->_save(); - } - $logfile = $this->getLogfile(); - if ($logfile !== null) os::mkdirof($logfile); - } finally { - lock::release(self::LOCK); - } - if ($autoUpdate) $this->update(); - } - - private function _init(): bool { - $init = $this->init; - if ($init !== null) { - func::call($init, $this); - return true; - } - return false; - } - - function init(): void { - if ($this->init !== null) { - lock::exlusive(self::LOCK); - try { - $this->_init(); - $this->_save(); - } finally { - lock::release(self::LOCK); - } - } - } - - private function _reset(): void { - $authz = authz::get(); - $id = $this->data["id"]; - $serial = A::get($this->data, "serial", 0); - $this->data = $this->ensureData([ - "id" => $id, - "serial" => $serial + 1, - "owner_login" => $authz->getUsername(), - "owner_name" => $authz->getDisplayName(), - "logfile" => logs::pf("$id/latest.log"), - ]); - $this->_init(); - } - - function reset(): void { - lock::exlusive(self::LOCK); - try { - $this->_reset(); - $this->_save(); - } finally { - lock::release(self::LOCK); - } - } - - private function _reload(): void { - $this->data = unserialize(file_get_contents($this->file)); - } - - function reload(): void { - lock::exlusive(self::LOCK); - try { - $this->_reload(); - } finally { - lock::release(self::LOCK); - } - } - - private function _save(): void { - os::mkdirof($this->file); - $outf = fopen($this->file, "w+"); - fwrite($outf, serialize($this->data)); - fclose($outf); - } - - function save(): void { - lock::exlusive(self::LOCK); - try { - $this->_save(); - } finally { - lock::release(self::LOCK); - } - } - - /** vérifier que l'objet est bien initialisé */ - function validate(): void { - if (!$this->isValid()) { - if ($this->getCmd() === null) { - throw new ValueException("cmd is required"); - } - $this->setValid(true); - $this->save(); - } - } - - function _launch(): void { - $args = [ - __DIR__.'/../../lib/launch_task.php', - "--envname", envs::get(), - $this->getId(), - ]; - $logfile = $this->getLogfile(); - if ($logfile !== null) A::merge($args, ["--logfile", $logfile]); - $cmd = new Cmd($args); - $cmd->addRedir("null"); - $cmd->passthru(); - $this->reload(); - } - - function isLaunchable(): bool { - return $this->isStarted(); - } - - function launch(): void { - if (!$this->isStartable()) return; - if ($this->isDone()) $this->reset(); - $this->_launch(); - } - - function isUpdatable(): bool { - return $this->isLaunchable() && !$this->isDone(); - } - - function update(): void { - if ($this->isUpdatable()) $this->_launch(); - else $this->init(); - } - - function kill(): void { - if (!$this->isStarted() || $this->isStopped()) return; - $args = [ - __DIR__.'/../../lib/launch_task.php', - "-e", envs::get(), - "--kill", - $this->getId(), - ]; - $logfile = $this->getLogfile(); - if ($logfile !== null) A::merge($args, ["-L", $logfile]); - $cmd = new Cmd($args); - $cmd->addRedir("null"); - $cmd->passthru(); - $this->reload(); - } - - function isStartable(): bool { - return !$this->isStarted() || $this->isDone(); - } - - /** - * démarrer la commande. doit être lancé depuis launch_task.php - */ - function ltStart(?string $logfile): void { - $pid = pcntl_fork(); - if ($pid == -1) { - # parent, impossible de forker - throw new IllegalAccessException("unable to fork"); - } elseif ($pid) { - # parent, fork ok - $this->setStarted(true); - $this->setDateStart(date::datetime()); - $this->setPid($pid); - $this->save(); - } else { - ## child, fork ok - # Créer un groupe de process, pour pouvoir les tuer toutes en même temps - posix_setsid(); - msg::push($oldMsg, null, [ - "output" => $logfile, - ]); - $retcode = -776; - try { - # tout d'abord synchroniser les fichiers le cas échéant - $command = $this->get("command"); - $append = false; - if ($command !== null) { - $files = $command["files"]; - $forceSync = $this->get("force_sync"); - files::sync($files, $forceSync, $logfile, "wb"); - $append = true; - } - # puis lancer la commande - $cmd = Cmd::with($this->getCmd()); - if ($logfile !== null) $cmd->addRedir("both", $logfile, $append); - $cmd->fork_exec($retcode); - } catch (Exception $e) { - msg::error($e); - } finally { - $this->reload(); - $this->setStopped(true); - $this->setDateStop(date::datetime()); - $this->setRetcode($retcode); - $this->save(); - msg::pop($oldMsg); - } - } - } - - /** arrêter la commande. doit être lancé depuis launch_task.php */ - function ltKill(?string $logfile): void { - msg::push($oldMsg, null, $logfile); - try { - $id = $this->getId(); - $pid = $this->getPid(); - msg::action("$id: $pid"); - if (!posix_kill(-$pid, SIGKILL)) { - switch (posix_get_last_error()) { - case PCNTL_ESRCH: - msg::afailure("process inexistant"); - break; - case PCNTL_EPERM: - msg::afailure("process non accessible"); - break; - case PCNTL_EINVAL: - msg::afailure("signal invalide"); - break; - } - return; - } - $timeout = 10; - while ($this->ltIsUndead()) { - sleep(1); - if (--$timeout == 0) { - msg::afailure("tentative d'arrêt de la tâche"); - return; - } - } - msg::asuccess("tâche arrêtée"); - $this->setStopped(true); - $this->setDateStop(date::datetime()); - $this->setRetcode(-787); - $this->setDone(true); - $this->save(); - } finally { - msg::pop($oldMsg); - } - } - - function isReapable(): bool { - return $this->isStopped() && !$this->isDone(); - } - - /** - * marquer la commande comme terminée. doit être lancé depuis launch_task.php - */ - function ltReap(): void { - pcntl_waitpid($this->getPid(), $status); - $this->setDone(true); - $this->save(); - } - - /** - * vérifier si on est dans le cas où la tâche est censée tourner mais en - * réalité ce n'est pas le cas. doit être lancé depuis launch_task.php - */ - function ltIsUndead(): bool { - if (!posix_kill($this->getPid(), 0)) { - switch (posix_get_last_error()) { - case PCNTL_ESRCH: - # process inexistant - return true; - case PCNTL_EPERM: - # process auquel on n'a pas accès: ce doit être un autre process qui a - # réutilisé le PID - return true; - case PCNTL_EINVAL: - # ne devrait pas se produire - return false; - } - } - # process existant - return false; - } - - /** - * marquer la tâche comme terminée avec un code d'erreur si elle n'existe - * plus. doit être lancé depuis launch_task.php - */ - function ltCleanUndead(): void { - if (!$this->isStopped()) { - $this->setStopped(true); - $this->setDateStop(date::datetime()); - $this->setRetcode(-777); - } - $this->setDone(true); - $this->save(); - } - - function getIdTitle(): string { - $idTitle = $this->getId(); - $title = $this->getTitle(); - if ($title) $idTitle .= " -- $title"; - return $idTitle; - } - - function getNameOrLogin(): string { - $nameOrLogin = $this->getOwnerName(); - if ($nameOrLogin === null) $nameOrLogin = $this->getOwnerLogin(); - if ($nameOrLogin === null) $nameOrLogin = "(unknown)"; - return $nameOrLogin; - } - - const MAX_LOG_SIZE = 256 * 1024; - const CACTION_NONE = "n"; - const CACTION_REPLACE = "r"; - const CACTION_UPDATE = "u"; - - function export(?int $serial=null, ?int $cs=null, ?int $ce=null): array { - $task = $this->array(); - $dateStart = new Datetime($this->getDateStart()); - $dateStop = new Datetime($this->getDateStop()); - # $ca = action à faire par le client: replace ou update - # ls = local start, le = local end (local === server en l'occurrence) - # cs = client start, ce = client end (pour CACTION_REPLACE) - # $ps = plus start, $pe = plus end (pour CACTION_UPDATE) - # $rs = read start, $re = read end - if ($serial !== null && $cs !== null && $ce !== null && $this->isStarted()) { - lock::exlusive(self::LOCK); - $inf = false; - try { - $logfile = $this->getLogfile(); - if (!file_exists($logfile)) { - # s'assurer que le fichier existe (il peut avoir été nettoyé entre temps) - f::close(f::open($logfile, "cb")); - } - $inf = f::open($logfile, "rb"); - $le = f::seek($inf, 0, SEEK_END); - $ls = $le - self::MAX_LOG_SIZE; - if ($ls <= 0) { - $ls = 0; - } else { - # trouver le premier saut de ligne - $ls = f::find_nl($inf, $ls); - } - - if ($serial != $this->getSerial()) { - # nouvelle tâche, on recommence tout - $rs = $cs = $ls; - $re = $ce = $le; - $ca = self::CACTION_REPLACE; - } elseif ($ls <= $cs) { - # cas courant, on rajoute du contenu, mais pas plus que MAX_LOG_SIZE - $ls = $cs; - $ps = 0; - $pe = $le - $ce; - $rs = $ce; - $re = $le; - $ca = self::CACTION_UPDATE; - $cs = $ps; - $ce = $pe; - } elseif ($ls <= $ce) { - # on a dépassé MAX_LOG_SIZE, il faut recalculer - # garder une partie des logs précédents - $ps = $ls - $cs; - $pe = $le - $ce; - $rs = $ce; - $re = $le; - $ca = self::CACTION_UPDATE; - $cs = $ps; - $ce = $pe; - } else { - # ne rien garder des logs précédents - $rs = $cs = $ls; - $re = $ce = $le; - $ca = self::CACTION_REPLACE; - } - - $logSize = $re - $rs; - if ($logSize > 0) { - f::seek($inf, $rs, SEEK_SET); - $log = f::read($inf, $logSize); - $lf = new BaseF(); #XXX - $lf->formatContent($log); - } elseif ($ca == self::CACTION_REPLACE) { - $log = ""; - } else { - $log = false; - } - } finally { - if ($inf) f::close($inf); - lock::release(self::LOCK); - } - } else { - $cs = $ce = false; - $ca = self::CACTION_NONE; - $log = false; - } - $page = $this->getPage(); - if ($page !== null) { - $dest = A::get($page, 0); - $params = A::get($page, 1); - $pageUrl = page::bu($dest, $params); - } else { - $pageUrl = false; - } - A::merge($task, [ - "id_title" => $this->getIdTitle(), - "name_or_login" => $this->getNameOrLogin(), - "page_url" => $pageUrl, - "elapsed_start" => $dateStart->getElapsed()->formatAt(), - "elapsed_stop" => $dateStop->getElapsed()->formatSince(), - "elapsed_total" => $dateStart->getElapsed($dateStop)->formatDelay(), - "launchable" => $this->isLaunchable(), - "updatable" => $this->isUpdatable(), - "startable" => $this->isStartable(), - "reapable" => $this->isReapable(), - "working" => $this->isStarted() && !$this->isDone(), - "ok" => $this->isDone() && $this->getRetcode() == 0, - "ko" => $this->isDone() && $this->getRetcode() != 0, - "log" => $log, - ]); - return [$task, $ca, $cs, $ce]; - } - - ############################################################################# - const _AUTOGEN_CONSTS = [ - "_AUTO_GETTERS" => [Autogen::class, "auto_getters", self::SCHEMA], - "_AUTO_SETTERS" => [Autogen::class, "auto_setters", self::SCHEMA], - ]; - const _AUTOGEN_METHODS = [ - [Autogen::class, "auto_getters_methods", self::SCHEMA], - [Autogen::class, "auto_setters_methods", self::SCHEMA], - ]; - const _AUTO_GETTERS = /*autogen*/[ - 'getId' => 'id', - 'getSerial' => 'serial', - 'getTitle' => 'title', - 'isValid' => 'valid', - 'getOwnerLogin' => 'owner_login', - 'getOwnerName' => 'owner_name', - 'getPage' => 'page', - 'getCmd' => 'cmd', - 'getLogfile' => 'logfile', - 'isStarted' => 'started', - 'getDateStart' => 'date_start', - 'getPid' => 'pid', - 'isStopped' => 'stopped', - 'getDateStop' => 'date_stop', - 'getRetcode' => 'retcode', - 'isDone' => 'done', - ]; - const _AUTO_SETTERS = /*autogen*/[ - 'setId' => 'id', - 'setSerial' => 'serial', - 'setTitle' => 'title', - 'setValid' => 'valid', - 'setOwnerLogin' => 'owner_login', - 'setOwnerName' => 'owner_name', - 'setPage' => 'page', - 'setCmd' => 'cmd', - 'setLogfile' => 'logfile', - 'setStarted' => 'started', - 'setDateStart' => 'date_start', - 'setPid' => 'pid', - 'setStopped' => 'stopped', - 'setDateStop' => 'date_stop', - 'setRetcode' => 'retcode', - 'setDone' => 'done', - ]; -} diff --git a/wip/os/proc/tasks.php b/wip/os/proc/tasks.php deleted file mode 100644 index 4c3edbb..0000000 --- a/wip/os/proc/tasks.php +++ /dev/null @@ -1,75 +0,0 @@ -isValid()) { - continue; - } - $tasks[] = [$taskfile, $task]; - } - if ($sort) { - clearstatcache(); - usort($tasks, function ($fta, $ftb) { - /** - * @var ManagedTask $ta - * @var ManagedTask $tb - */ - [$fa, $ta] = $fta; - [$fb, $tb] = $ftb; - # comparer l'état "running" - $wa = $ta->isStarted() && !$ta->isDone(); - $wb = $tb->isStarted() && !$tb->isDone(); - $c = -base::compare($wa, $wb); - if ($c != 0) return $c; - # comparer la date de dernière modification du fichier - $mta = filemtime($fa); - $mtb = filemtime($fb); - return -base::compare($mta, $mtb); - }); - } - return $tasks; - } - - /** supprimer toutes les tâches */ - static function delete_all(): void { - lock::exlusive(ManagedTask::LOCK); - try { - foreach (self::_list(true, false) as [$taskfile, $task]) { - unlink($taskfile); - } - } finally { - lock::release(ManagedTask::LOCK); - } - } - - /** - * retourner la liste des tâches valides - * @return ManagedTask[] - */ - static function list(?string $selectId=null): array { - $tasks = []; - lock::exlusive(ManagedTask::LOCK); - try { - foreach (self::_list(false, true) as [$taskfile, $task]) { - $id = $task->getId(); - if ($selectId !== null && $id !== $selectId) continue; - $tasks[$id] = $task; - } - } finally { - lock::release(ManagedTask::LOCK); - } - return $tasks; - } -} diff --git a/wip/app/cli/Application.php b/wip_app/cli/Application.php similarity index 91% rename from wip/app/cli/Application.php rename to wip_app/cli/Application.php index 67f41ed..9c43c53 100644 --- a/wip/app/cli/Application.php +++ b/wip_app/cli/Application.php @@ -1,19 +1,18 @@ getRunfile()->release(); + app::get()->getRunfile()->release(); $unlock = false; } if ($stop) { - app2::get()->getRunfile()->wfStop(); + app::get()->getRunfile()->wfStop(); $stop = false; } }; register_shutdown_function($shutdown); - app2::install_signal_handler(static::INSTALL_SIGNAL_HANDLER); + app::install_signal_handler(static::INSTALL_SIGNAL_HANDLER); try { static::_initialize_app(); $useRunfile = static::USE_RUNFILE; $useRunlock = static::USE_RUNLOCK; if ($useRunfile) { - $runfile = app2::get()->getRunfile(); + $runfile = app::get()->getRunfile(); global $argc, $argv; self::_manage_runfile($argc, $argv, $runfile); - if ($useRunlock && $runfile->warnIfLocked()) exit(app2::EC_LOCKED); + if ($useRunlock && $runfile->warnIfLocked()) exit(app::EC_LOCKED); $runfile->wfStart(); $stop = true; @@ -191,12 +190,12 @@ EOT); exit($e->getCode()); } catch (Exception $e) { msg::error($e); - exit(app2::EC_UNEXPECTED); + exit(app::EC_UNEXPECTED); } } protected static function _initialize_app(): void { - app2::init(static::class); + app::init(static::class); msg::set_messenger(new StdMessenger([ "min_level" => msg::DEBUG, ])); @@ -211,7 +210,7 @@ EOT); ]); if (static::USE_LOGFILE) { $msgs["log"] = new StdMessenger([ - "output" => app2::get()->getLogfile(), + "output" => app::get()->getLogfile(), "min_level" => msg::MINOR, "add_date" => true, ]); @@ -254,12 +253,12 @@ EOT); ["group", ["-p", "--profile", "--app-profile", "args" => 1, "argsdesc" => "PROFILE", - "action" => [app2::class, "set_profile"], + "action" => [app::class, "set_profile"], "help" => "spécifier le profil d'exécution", ], - ["-P", "--prod", "action" => [app2::class, "set_profile", config::PROD]], - ["-T", "--test", "action" => [app2::class, "set_profile", config::TEST]], - ["--devel", "action" => [app2::class, "set_profile", config::DEVEL]], + ["-P", "--prod", "action" => [app::class, "set_profile", config::PROD]], + ["-T", "--test", "action" => [app::class, "set_profile", config::TEST]], + ["--devel", "action" => [app::class, "set_profile", config::DEVEL]], ], ]; @@ -364,7 +363,7 @@ EOT); /** retourner le profil courant en couleur */ static function get_profile(?string $profile=null): string { - if ($profile === null) $profile = app2::get_profile(); + if ($profile === null) $profile = app::get_profile(); foreach (static::PROFILE_COLORS as $text => $color) { if (strpos($profile, $text) !== false) { return $color? "$profile": $profile; diff --git a/wip/app/cli/TODO.md b/wip_app/cli/TODO.md similarity index 100% rename from wip/app/cli/TODO.md rename to wip_app/cli/TODO.md