Skip to content

Commit

Permalink
Common batch run/detach functionality.
Browse files Browse the repository at this point in the history
* Conf::set_current_time() no arguments calls microtime(true).
* TokenInfo::update_use(null) resets current time.
* TokenInfo::run_live() knows how to detach the current session.
* Pass job IDs via the environment.
  • Loading branch information
kohler committed Feb 15, 2024
1 parent 62abc45 commit e094f7d
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 61 deletions.
28 changes: 14 additions & 14 deletions batch/autoassign.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Autoassign_Batch {
/** @var bool */
public $profile = false;
/** @var ?callable */
public $attached;
public $detacher;
/** @var ?TokenInfo */
private $_jtok;

Expand All @@ -55,13 +55,13 @@ static function autoassigner_names(Conf $conf) {
}

/** @param array<string,mixed> $arg
* @param ?callable $attached */
function __construct(Conf $conf, $arg, Getopt $getopt, $attached = null) {
* @param ?callable $detacher */
function __construct(Conf $conf, $arg, Getopt $getopt, $detacher = null) {
$this->conf = $conf;
$this->getopt = $getopt;
$this->attached = $attached;
$this->detacher = $detacher;
if (isset($arg["job"])) {
$this->_jtok = Job_Capability::claim($arg["job"], $this->conf, "batch/autoassign");
$this->_jtok = Job_Capability::claim($this->conf, $arg["job"], "Autoassign");
$this->user = $this->_jtok->user() ?? $conf->root_user();
} else {
$this->user = $conf->root_user();
Expand All @@ -76,7 +76,7 @@ function __construct(Conf $conf, $arg, Getopt $getopt, $attached = null) {
try {
$this->_jtok->update_use();
$this->parse_arg($arg);
$this->parse_arg($getopt->parse($this->_jtok->input("argv") ?? []));
$this->parse_arg($getopt->parse($this->_jtok->input("assign_argv") ?? []));
$this->complete_arg();
} catch (CommandLineException $ex) {
$this->report([MessageItem::error("<0>{$ex->getMessage()}")], $ex->exitStatus);
Expand All @@ -100,7 +100,6 @@ private function report($message_list, $exit_status = null) {
$this->_jtok->change_data("exit_status", $exit_status)
->change_data("status", "done");
}
Conf::set_current_time(microtime(true));
$this->_jtok->update_use()->update();
} else {
$s = MessageSet::feedback_text($message_list);
Expand Down Expand Up @@ -220,7 +219,6 @@ private function complete_arg() {
}

function report_progress($progress) {
Conf::set_current_time();
$this->_jtok->change_data("progress", $progress)->update_use()->update();
set_time_limit(240);
}
Expand Down Expand Up @@ -255,9 +253,9 @@ function execute() {
$this->report($aa->message_list(), $aa->has_error() ? 1 : null);

// run autoassigner
if ($this->attached) {
call_user_func($this->attached, $this);
$this->attached = null;
if ($this->detacher) {
call_user_func($this->detacher, $this);
$this->detacher = null;
}
if ($this->_jtok) {
$aa->add_progress_function([$this, "report_progress"]);
Expand Down Expand Up @@ -370,11 +368,13 @@ static function make_getopt() {
->interleave(true);
}

/** @return Autoassign_Batch */
static function make_args($argv) {
/** @param list<string> $argv
* @param ?callable $detacher
* @return Autoassign_Batch */
static function make_args($argv, $detacher = null) {
$getopt = self::make_getopt();
$arg = $getopt->parse($argv);
$conf = initialize_conf($arg["config"] ?? null, $arg["name"] ?? null);
return new Autoassign_Batch($conf, $arg, $getopt);
return new Autoassign_Batch($conf, $arg, $getopt, $detacher);
}
}
2 changes: 1 addition & 1 deletion src/api/api_job.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ static function job(Contact $user, Qrequest $qreq) {
return JsonResult::make_parameter_error("job");
}

$tok = Job_Capability::find($jobid, $user->conf);
$tok = Job_Capability::find($user->conf, $jobid);
if (!$tok) {
return new JsonResult(404, ["ok" => false]);
}
Expand Down
61 changes: 50 additions & 11 deletions src/capabilities/cap_job.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,46 @@
// Copyright (c) 2006-2024 Eddie Kohler; see LICENSE.

class Job_Capability {
/** @param string $command
* @param ?list<string> $argv */
static function make(Contact $user, $command, $argv = null) {
/** @param string $batch_class
* @param list<string> $argv
* @return TokenInfo */
static function make(Contact $user, $batch_class, $argv = []) {
return (new TokenInfo($user->conf, TokenInfo::JOB))
->set_user($user)
->set_token_pattern("hcj_[24]")
->set_invalid_after(86400)
->set_expires_after(86400)
->set_input(["command" => $command, "argv" => $argv]);
->set_input(["batch_class" => $batch_class, "argv" => $argv]);
}

/** @param string $salt
* @param Conf $conf
* @param ?string $command
* @param ?string $batch_class
* @param bool $allow_inactive
* @return TokenInfo */
static function find($salt, Conf $conf, $command = null, $allow_inactive = false) {
static function find($salt, Conf $conf, $batch_class = null, $allow_inactive = false) {
if (($salt === false || $salt === "e")
&& !($salt = getenv("HOTCRP_JOB"))) {
throw new CommandLineException("HOTCRP_JOB not set");
}
if ($salt !== null && strpos($salt, "_") === false) {
$salt = "hcj_{$salt}";
}
$tok = TokenInfo::find($salt, $conf);
if (!$tok
|| !self::validate_token($tok, $command)
|| !self::validate($tok, $batch_class)
|| (!$allow_inactive && !$tok->is_active())) {
throw new CommandLineException("Invalid job token `{$salt}`");
}
return $tok;
}

/** @param ?string $command
/** @param ?string $batch_class
* @return bool */
static function validate_token(TokenInfo $tok, $command) {
static function validate(TokenInfo $tok, $batch_class) {
return $tok->capabilityType === TokenInfo::JOB
&& ($command === null || $tok->input("command") === $command);
&& is_string(($bc = $tok->input("batch_class")))
&& ($batch_class === null || $batch_class === $bc);
}

/** @param string $salt
Expand All @@ -52,12 +58,45 @@ static function claim($salt, Conf $conf, $command = null) {
$new_data = '{"status":"run"}';
$result = Dbl::qe($conf->dblink,
"update Capability set `data`=? where salt=? and `data` is null",
$new_data, $salt);
$new_data, $tok->salt);
if ($result->affected_rows > 0) {
$tok->assign_data($new_data);
return $tok;
}
$tok->load_data();
}
}

/** @param string|callable():string $redirect_uri
* @return 'forked'|'detached'|'done' */
static function run_live(TokenInfo $tok, ?Qrequest $qreq = null, $redirect_uri = null) {
assert(self::validate($tok, null));
$batch_class = $tok->input("batch_class");
$argv = $tok->input("argv");

$status = "done";
$detacher = function () use (&$status, $qreq, $redirect_uri) {
if (PHP_SAPI === "fpm-fcgi" && $status === "done") {
$status = "detached";
if ($redirect_uri) {
$u = is_string($redirect_uri) ? $redirect_uri : call_user_func($redirect_uri);
header("Location: {$u}");
}
if ($qreq) {
$qreq->qsession()->commit();
}
fastcgi_finish_request();
}
};
putenv("HOTCRP_JOB={$tok->salt}");

try {
$x = call_user_func("{$batch_class}_Batch::make_args", $tok->input("argv"), $detacher);
$x->run();
} catch (CommandLineException $ex) {
}

putenv("HOTCRP_JOB=");
return $status;
}
}
8 changes: 4 additions & 4 deletions src/conference.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ function __construct($options, $connect) {
}
}

/** @param int|float $t */
static function set_current_time($t) {
global $Now;
/** @param null|int|float $t */
static function set_current_time($t = null) {
$t = $t ?? microtime(true);
self::$unow = $t;
$Now = Conf::$now = (int) $t;
Conf::$now = (int) $t;
if (Conf::$main) {
Conf::$main->refresh_time_settings();
}
Expand Down
2 changes: 1 addition & 1 deletion src/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
require_once(SiteLoader::find("src/helpers.php"));
require_once(SiteLoader::find("src/conference.php"));
require_once(SiteLoader::find("src/contact.php"));
Conf::set_current_time(microtime(true));
Conf::set_current_time();
if (defined("HOTCRP_TESTHARNESS")) {
Conf::$test_mode = true;
}
Expand Down
32 changes: 11 additions & 21 deletions src/pages/p_autoassign.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ private function print() {
Ht::stash_script("var hotcrp_pc_tags=" . json_encode($tagsjson) . ";");
foreach ($conf->viewable_user_tags($this->user) as $pctag) {
if ($pctag !== "pc")
$pctyp_sel[] = [$pctag, "#$pctag"];
$pctyp_sel[] = [$pctag, "#{$pctag}"];
}
$pctyp_sel[] = ["__flip__", "flip"];
$sep = "";
Expand Down Expand Up @@ -461,17 +461,9 @@ static function token_message_list(TokenInfo $tok) {
}


function detach() {
// The Autoassigner_Batch is about to run the autoassigner;
// we should arrange a redirect.
if (PHP_SAPI === "fpm-fcgi") {
$nav = $this->qreq->navigation();
$url = $nav->resolve($this->conf->hoturl_raw("autoassign", $this->qreq_parameters()));
header("Location: {$url}");
$this->qreq->qsession()->commit();
fastcgi_finish_request();
$this->detached = true;
}
function redirect_uri() {
$nav = $this->qreq->navigation();
return $nav->resolve($this->conf->hoturl_raw("autoassign", $this->qreq_parameters()));
}

function start_job() {
Expand Down Expand Up @@ -521,20 +513,18 @@ function start_job() {
$argmap->$k1 = $k;
}

$tok = Job_Capability::make($this->user, "batch/autoassign", $argv)
$tok = Job_Capability::make($this->user, "Autoassign", ["batch/autoassign.php", "-je", "-D"])
->set_input("assign_argv", $argv)
->set_input("argmap", $argmap);
$this->jobid = $tok->create();
assert($this->jobid !== null);

$getopt = Autoassign_Batch::make_getopt();
$arg = $getopt->parse(["batch/autoassign", "-j{$this->jobid}", "-D"]);
try {
(new Autoassign_Batch($this->conf, $arg, $getopt, [$this, "detach"]))->run();
} catch (CommandLineException $ex) {
}
$s = Job_Capability::run_live($tok, $this->qreq, [$this, "redirect_uri"]);

// Autoassign_Batch has completed its work.
if ($this->detached) {
if ($s === "forked") {
throw new Redirection($this->redirect_uri());
} else if ($s === "detached") {
exit();
}
$tok->load_data();
Expand Down Expand Up @@ -563,7 +553,7 @@ private function qreq_badpairs() {

function run_try_job() {
try {
$tok = Job_Capability::find($this->qreq->job, $this->conf, "batch/autoassign", true);
$tok = Job_Capability::find($this->conf, $this->qreq->job, "Autoassign", true);
} catch (CommandLineException $ex) {
$tok = null;
}
Expand Down
4 changes: 2 additions & 2 deletions src/reviewinfo.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
// reviewinfo.php -- HotCRP class representing reviews
// Copyright (c) 2006-2023 Eddie Kohler; see LICENSE.
// Copyright (c) 2006-2024 Eddie Kohler; see LICENSE.

class ReviewInfo implements JsonSerializable {
/** @var Conf */
Expand Down Expand Up @@ -527,7 +527,7 @@ function status_title($ucfirst = false) {
} else if ($this->subject_to_approval()) {
return $ucfirst ? "Subreview" : "subreview";
} else if ($this->is_ghost()) {
return $ucfirst ? "Tentative review" : "tentative review";
return $ucfirst ? "Hidden assignment" : "hidden assignment";
} else {
return $ucfirst ? "Review" : "review";
}
Expand Down
40 changes: 33 additions & 7 deletions src/tokeninfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class TokenInfo {
private $_user = false;
/** @var ?string */
private $_token_pattern;
/** @var ?callable(TokenInfo):bool */
private $_token_approver;
/** @var ?object */
private $_jinputData;
/** @var ?object */
Expand Down Expand Up @@ -120,6 +122,13 @@ function set_token_pattern($pattern) {
return $this;
}

/** @param callable(TokenInfo):bool $approver
* @return $this */
function set_token_approver($approver) {
$this->_token_approver = $approver;
return $this;
}

/** @param string $salt
* @return $this
* @suppress PhanAccessReadOnlyProperty */
Expand Down Expand Up @@ -317,6 +326,8 @@ function create() {
$this->timeExpires = $this->timeExpires ?? 0;
$need_salt = !$this->salt;
assert(!$need_salt || $this->_token_pattern);
$changes = $this->_changes;
$this->_changes = 0;

$qf = "";
$qv = [
Expand All @@ -333,16 +344,28 @@ function create() {
$qv[] = $this->inputData;
}

for ($tries = 0; $tries < ($need_salt ? 4 : 1); ++$tries) {
$salt = $need_salt ? $this->instantiate_token() : $this->salt;
$qv[0] = $salt;
for ($tries = 0; $tries < ($need_salt ? 5 : 1); ++$tries) {
if ($need_salt) {
$this->salt = $this->instantiate_token();
}
$qv[0] = $this->salt;
$result = Dbl::qe($this->dblink(), "insert into Capability (salt, capabilityType, contactId, paperId, timeCreated, timeUsed, timeInvalid, timeExpires, data{$qf}) values ?v", [$qv]);
if ($result->affected_rows > 0) {
$this->salt = $salt;
$this->_changes = 0;
return $salt;
if ($result->affected_rows <= 0) {
continue;
}
if ($this->_token_approver
&& !call_user_func($this->_token_approver, $this)) {
Dbl::qe($this->dblink(), "delete from Capability where salt=?", $this->salt);
continue;
}
$this->update();
return $this->salt;
}

if ($need_salt) {
$this->salt = null;
}
$this->_changes = $changes;
return null;
}

Expand All @@ -369,6 +392,9 @@ function input($key = null) {
/** @param ?int $within_sec
* @return $this */
function update_use($within_sec = null) {
if ($within_sec === null) {
Conf::set_current_time();
}
if ($within_sec === null || $this->timeUsed + $within_sec <= Conf::$now) {
/** @phan-suppress-next-line PhanAccessReadOnlyProperty */
$this->timeUsed = Conf::$now;
Expand Down

0 comments on commit e094f7d

Please sign in to comment.