modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2024-09-26 10:54:43 +04:00
parent e9bcf77a4d
commit 4bb386167f
12 changed files with 278 additions and 148 deletions

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="nur\sery\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="nur\sery\" />

67
bin/bg-launcher.php Executable file
View File

@ -0,0 +1,67 @@
#!/usr/bin/php
<?php
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
use nur\sery\output\msg;
use nur\sery\wip\app\app2;
use nur\sery\wip\app\cli\Application;
use nur\sery\wip\app\cli\bg_launcher;
use nur\yaml;
class BgLauncherApp extends Application {
const PROJDIR = __DIR__.'/../..';
const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2;
const ARGS = [
"purpose" => "lancer un script en tâche de fond",
"usage" => "ApplicationClass args...",
["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS,
"help" => "Afficher des informations sur la tâche",
],
["-s", "--start", "name" => "action", "value" => self::ACTION_START,
"help" => "Démarrer la tâche",
],
["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP,
"help" => "Arrêter la tâche",
],
];
protected int $action = self::ACTION_START;
protected ?array $args = null;
function main() {
$appClass = $this->args[0] ?? null;
if ($appClass === null) {
self::die("Vous devez spécifier la classe de l'application");
}
$appClass = str_replace("/", "\\", $appClass);
if (!class_exists($appClass)) {
self::die("$appClass: classe non trouvée");
}
$args = array_slice($this->args, 1);
$useRunfile = constant("$appClass::USE_RUNFILE");
if (!$useRunfile) {
self::die("Cette application ne supporte le lancement en tâche de fond");
}
$runfile = app2::with($appClass)->getRunfile();
switch ($this->action) {
case self::ACTION_START:
bg_launcher::_start($args, $runfile);
break;
case self::ACTION_STOP:
bg_launcher::_stop($runfile);
break;
case self::ACTION_INFOS:
yaml::dump($runfile->read());
break;
}
}
}
$params = app2::params_getenv();
if ($params !== null) app2::init($params);
BgLauncherApp::run();

View File

@ -65,6 +65,7 @@
},
"bin": [
"lib/_launch.php",
"bin/bg-launcher.php",
"nur_bin/compctl.php",
"nur_bin/compdep.php",
"nur_bin/cachectl.php",

View File

@ -2,6 +2,6 @@
<?php
require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php';
use nur\sery\wip\tools\SteamTrainApp;
use nur\sery\tools\SteamTrainApp;
SteamTrainApp::run();

View File

@ -2,7 +2,9 @@
namespace nur\sery\os;
use nur\sery\cl;
use nur\sery\ExitError;
use nur\sery\StateException;
use nur\sery\wip\app\app2;
class sh {
static final function _quote(string $value): string {
@ -144,12 +146,12 @@ class sh {
$pid = pcntl_fork();
if ($pid == -1) {
// parent, impossible de forker
throw new StateException("unable to fork");
throw new ExitError(app2::EC_FORK_PARENT, "unable to fork");
} elseif ($pid) {
// parent, fork ok
pcntl_waitpid($pid, $status);
if (pcntl_wifexited($status)) $retcode = pcntl_wexitstatus($status);
else $retcode = 127;
else $retcode = app2::EC_FORK_CHILD;
return $retcode == 0;
}
// child, fork ok

View File

@ -1,27 +1,31 @@
<?php
namespace nur\sery\wip\tools;
namespace nur\sery\tools;
use nur\sery\output\msg;
use nur\sery\wip\app\app2;
use nur\sery\wip\app\cli\Application;
class SteamTrainApp extends Application {
const PROJDIR = __DIR__.'/../..';
const TITLE = "Train à vapeur";
const USE_SIGNAL_HANDLER = true;
const USE_LOGFILE = true;
const USE_RUNFILE = true;
const USE_RUNLOCK = true;
const USE_SIGNAL_HANDLER = true;
const ARGS = [
"purpose" => self::TITLE,
"description" => <<<EOT
Cette application peut être utilisée pour tester le lancement des tâches de fond
EOT,
];
function main() {
$runfile = app2::get()->getRunfile();
$runfile->action("Running train...", 100);
for ($i = 1; $i <= 100; $i++) {
$count = 100;
app2::action("Running train...", $count);
for ($i = 1; $i <= $count; $i++) {
msg::print("Tchou-tchou! x $i");
$runfile->step();
app2::step();
sleep(1);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace nur\sery\wip\app;
use nulib\tests\TestCase;
class argsTest extends TestCase {
function testFrom_array() {
self::assertSame([], args::from_array(null));
self::assertSame([], args::from_array([]));
self::assertSame([], args::from_array([false]));
self::assertSame(["x"], args::from_array(["x", false]));
self::assertSame(["--opt"], args::from_array(["--opt"]));
self::assertSame(["--opt", "value"], args::from_array(["--opt", "value"]));
self::assertSame([], args::from_array(["opt" => false]));
self::assertSame(["--opt"], args::from_array(["opt" => true]));
self::assertSame(["--opt", "value"], args::from_array(["opt" => "value"]));
self::assertSame(["--opt", "42"], args::from_array(["opt" => 42]));
self::assertSame(["--opt", "1", "2", "3", "--"], args::from_array(["opt" => [1, 2, 3]]));
self::assertSame(["x", "1", "2", "3", "y"], args::from_array(["x", [1, 2, 3], "y"]));
}
}

View File

@ -269,6 +269,7 @@ class RunFile {
"action_current_step" => 0,
];
});
app2::_dispatch_signals();
}
/** indiquer qu'une étape est franchie dans l'action en cours */

View File

@ -6,7 +6,6 @@ use nur\sery\cl;
use nur\sery\ExitError;
use nur\sery\os\path;
use nur\sery\os\sh;
use nur\sery\output\msg;
use nur\sery\php\func;
use nur\sery\str;
use nur\sery\ValueException;
@ -476,6 +475,10 @@ class app2 {
#############################################################################
const EC_UNDEAD = 247;
const EC_REAPABLE = 248;
const EC_FORK_CHILD = 249;
const EC_FORK_PARENT = 250;
const EC_DISABLED = 251;
const EC_LOCKED = 252;
const EC_BAD_COMMAND = 253;
@ -536,4 +539,14 @@ class app2 {
throw new ExitError(self::EC_DISABLED, "Planifications désactivées. La tâche n'a pas été lancée");
}
}
#############################################################################
static function action(?string $title, ?int $maxSteps=null): void {
self::get()->getRunfile()->action($title, $maxSteps);
}
static function step(int $nbSteps=1): void {
self::get()->getRunfile()->step($nbSteps);
}
}

39
wip/app/args.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace nur\sery\wip\app;
use nur\sery\A;
use nur\sery\str;
class args {
/**
* transformer une liste d'argument de la forme
* - ["myArg" => $value] devient ["--my-arg", "$value"]
* - ["myOpt" => true] devient ["--my-opt"]
* - ["myOpt" => false] est omis
* - les autres valeurs sont prises telles quelles
*/
static function from_array(?array $array): array {
$args = [];
if ($array === null) return $args;
$index = 0;
foreach ($array as $arg => $value) {
if ($value === false) continue;
if ($arg === $index) {
$index++;
} else {
$arg = str::us2camel($arg);
$arg = str::camel2us($arg, false, "-");
$arg = str_replace("_", "-", $arg);
$args[] = "--$arg";
if (is_array($value)) $value[] = "--";
elseif ($value === true) $value = null;
}
if (is_array($value)) {
A::merge($args, array_map("strval", $value));
} elseif ($value !== null) {
$args[] = "$value";
}
}
return $args;
}
}

View File

@ -1,5 +1,119 @@
<?php
namespace nur\sery\wip\app\cli;
use nur\sery\ExitError;
use nur\sery\file\TmpfileWriter;
use nur\sery\os\path;
use nur\sery\os\proc\Cmd;
use nur\sery\output\msg;
use nur\sery\StateException;
use nur\sery\wip\app\app2;
use nur\sery\wip\app\args;
use nur\sery\wip\app\RunFile;
class bg_launcher {
/**
* transformer une liste d'argument de la forme
* - ["myArg" => $value] devient ["--my-arg", "$value"]
* - ["myOpt" => true] devient ["--my-opt"]
* - ["myOpt" => false] est momis
* - les valeurs séquentielles sont prises telles quelles
*/
static function verifix_args(array $args): array {
$args = args::from_array($args);
# corriger le chemin de l'application pour qu'il soit absolu et normalisé
$args[0] = path::abspath($args[0]);
return $args;
}
static function launch(string $appClass, array $args): int {
$app = app2::get();
$vendorBindir = $app->getVendorbindir();
$launch_php = "$vendorBindir/_launch.php";
if (!file_exists($launch_php)) {
$launch_php = __DIR__."/../../lib/_launch.php";
}
$tmpfile = new TmpfileWriter();
$tmpfile->keep()->serialize($app->getParams());
$args = self::verifix_args($args);
$cmd = new Cmd([
$launch_php,
"--internal-use", $tmpfile->getFile(),
$appClass, "--", ...$args,
]);
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
$cmd->passthru($exitcode);
# attendre un peu que la commande aie le temps de s'initialiser
sleep(1);
$tmpfile->close();
return $exitcode;
}
static function _start(array $args, Runfile $runfile): bool {
if ($runfile->warnIfLocked()) return false;
$pid = pcntl_fork();
if ($pid == -1) {
# parent, impossible de forker
throw new ExitError(app2::EC_FORK_PARENT, "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_app_start.log";
$pid = posix_getpid();
$exitcode = app2::EC_FORK_CHILD;
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(app2::EC_REAPABLE);
msg::asuccess();
}
}

View File

@ -1,137 +0,0 @@
<?php
namespace nur\sery\wip\app;
use nur\sery\app\Runfile;
use nur\sery\cl;
use nur\sery\file\TmpfileWriter;
use nur\sery\os\path;
use nur\sery\os\proc\Cmd;
use nur\sery\output\msg;
use nur\sery\StateException;
use nur\sery\str;
use nur\sery\wip\app\app2;
class launcher {
/**
* transformer une liste d'argument de la forme
* - ["myArg" => $value] devient ["--my-arg", "$value"]
* - ["myOpt" => true] devient ["--my-opt"]
* - ["myOpt" => false] est momis
* - les valeurs séquentielles sont prises telles quelles
*/
static function verifix_args(array $args): array {
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 = app2::get();
$vendorBindir = $app->getVendorbindir();
$launch_php = "$vendorBindir/_launch.php";
if (!file_exists($launch_php)) {
$launch_php = __DIR__."/../../lib/_launch.php";
}
$tmpfile = new TmpfileWriter();
$tmpfile->keep()->serialize($app->getParams());
$args = self::verifix_args($args);
$cmd = new Cmd([
$launch_php,
"--internal-use", $tmpfile->getFile(),
$appClass, "--", ...$args,
]);
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
$cmd->passthru($exitcode);
# attendre un peu que la commande aie le temps de s'initialiser
sleep(1);
$tmpfile->close();
return $exitcode;
}
static function _start(array $args, Runfile $runfile): bool {
if ($runfile->warnIfLocked()) return false;
$pid = pcntl_fork();
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();
}
}