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