Skip to content

Commit

Permalink
Big autoassigner rewrite.
Browse files Browse the repository at this point in the history
* Web autoassigner uses the *batch* autoassigner.
* Web autoassigner uses fastcgi_finish_request() and a job API to
  check back.

The goal is to support longer autoassigner runs.
Also introduce "job" capabilities. These capabilities have inputData
and outputData. Update the TokenInfo API.
  • Loading branch information
kohler committed Jan 16, 2024
1 parent df792e7 commit cff53e4
Show file tree
Hide file tree
Showing 34 changed files with 1,206 additions and 742 deletions.
304 changes: 201 additions & 103 deletions batch/autoassign.php

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion batch/makedist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ src/assignmentset.php
src/author.php
src/authormatcher.php
src/autoassigner.php
src/autoassignerinterface.php
src/autoassigners/aa_clear.php
src/autoassigners/aa_discussionorder.php
src/autoassigners/aa_paperpc.php
Expand Down
6 changes: 5 additions & 1 deletion etc/apifunctions.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@
},
{
"name": "graphdata", "get": true,
"function": "GraphData_Api::graphdata"
"function": "GraphData_API::graphdata"
},
{
"name": "job", "get": true,
"function": "Job_API::job"
},
{
"name": "jserror", "get": true,
Expand Down
4 changes: 3 additions & 1 deletion etc/msgs.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,7 @@
["signin_error", "<5>{message:ftext}. Use <a href=\"{forgotpassword}\">“Forgot your password?”</a> to reset it.", ["{problem}=password_expired", "{forgotpassword}"]],
["signin_error", "<5>{message:ftext}. If that’s your account, <a href=\"{signin}\">sign in</a> using your password.", ["{problem}=account_exists", "{signin}"]],
["signin_error", "<5>{message:ftext}. If that’s your account, <a href=\"{signin}\">sign in</a> using your password, or <a href=\"{forgotpassword}\">reset your password</a> if you have forgotten it.", ["{problem}=account_exists", "{forgotpassword}"]],
["signin_error", "<5>{message:ftext}. Enter your password or <a href=\"{forgotpassword}\">reset your password</a> if you have forgotten it.", ["{problem}=bad_password", "{forgotpassword}"], 1]
["signin_error", "<5>{message:ftext}. Enter your password or <a href=\"{forgotpassword}\">reset your password</a> if you have forgotten it.", ["{problem}=bad_password", "{forgotpassword}"], 1],

["<5><a href=\"{url}\">{Submissions} {pids:numlist#}</a> got fewer assignments than you requested.", "<5><a href=\"{url}\">{Submission} {pids:numlist#}</a> got fewer assignments than you requested.", ["#{pids}=1"]]
]
47 changes: 47 additions & 0 deletions lib/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,58 @@ function json_encode_browser($x, $flags = 0) {
return json_encode($x, $flags);
}
}

/** @return string */
function json_encode_db($x, $flags = 0) {
return json_encode($x, $flags | JSON_UNESCAPED_UNICODE);
}

/** @param ?string $x
* @return ?object */
function json_decode_object($x) {
if ($x === null || $x === "" || !is_object(($j = json_decode($x)))) {
return null;
}
return $j;
}

/** @param ?string &$s
* @param ?object &$x
* @param mixed $k
* @param mixed $v
* @param 1|2 $n
* @return bool */
function json_encode_object_change(&$s, &$x, $k, $v, $n) {
if ($n === 1) {
if ($k === null || is_string($k)) {
$news = $k;
} else {
$news = json_encode_db($k);
}
if ($s === $news) {
return false;
}
$s = $news;
$x = null;
return true;
}
assert(is_string($k));
if ($x === null) {
$x = json_decode_object($s);
}
if (($x->$k ?? null) === $v) {
return false;
}
if ($v !== null) {
$x = $x ?? (object) [];
$x->$k = $v;
} else {
unset($x->$k);
}
$s = json_encode_db($x);
return true;
}


// array and object helpers

Expand Down
9 changes: 8 additions & 1 deletion lib/getopt.php
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,18 @@ function __construct($message = "", $getopt = null, $exit_status = null) {
$this->exitStatus = $exit_status ?? self::$default_exit_status;
}
/** @param int $exit_status
* @return $this */
* @return $this
* @deprecated */
function exit_status($exit_status) {
$this->exitStatus = $exit_status;
return $this;
}
/** @param int $exit_status
* @return $this */
function set_exit_status($exit_status) {
$this->exitStatus = $exit_status;
return $this;
}
/** @param string ...$context
* @return $this */
function add_context(...$context) {
Expand Down
7 changes: 7 additions & 0 deletions lib/messageset.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ function __construct($field, $message, $status) {
$this->status = $status;
}

/** @param object $x
* @return MessageItem */
static function from_json($x) {
// XXX context, pos1, pos2?
return new MessageItem($x->field ?? null, $x->message ?? "", $x->status ?? 0);
}

/** @param int $format
* @return string */
function message_as($format) {
Expand Down
10 changes: 9 additions & 1 deletion lib/mincostmaxflow.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,19 @@ function add_edge($vs, $vd, $cap, $cost = 0, $mincap = 0) {
}

/** @param callable(MinCostMaxFlow,int,...) $progressf
* @return void */
* @return void
* @deprecated */
function add_progress_handler($progressf) {
$this->progressf[] = $progressf;
}

/** @param callable(MinCostMaxFlow,int,...) $progressf
* @return $this */
function add_progress_function($progressf) {
$this->progressf[] = $progressf;
return $this;
}


// extract information

Expand Down
45 changes: 45 additions & 0 deletions scripts/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4935,6 +4935,50 @@ handle_ui.on("js-request-review-preview-email", function (evt) {
handle_ui.stopPropagation(evt);
});

hotcrp.monitor_autoassignment = function (jobid) {
let start = now_sec(), tries = 0;
function success(data) {
const e = $$("propass");
if (data.message_list) {
let ex = e.firstElementChild;
while (ex && ex.nodeName === "H3") {
ex = ex.nextElementSibling;
}
if (!ex || ex.nodeName === "P") {
const ee = $e("div", "msg msg-warning");
e.insertBefore(ee, ex);
ex = ee;
}
ex.replaceChildren(render_feedback_list(data.message_list));
}
if (data.progress) {
let ex = e.firstElementChild;
while (ex && ex.nodeName !== "P") {
ex = ex.nextElementSibling;
}
if (!ex) {
ex = $e("p");
e.appendChild(ex);
}
ex.replaceChildren($e("strong", null, "Status:"), " " + data.progress.replace(/\.*$/, "..."));
}
if (data.status === "done") {
document.location.reload();
} else if (tries < 20) {
setTimeout(retry, 250);
} else {
setTimeout(retry, 500);
}
}
function retry() {
++tries;
$.ajax(hoturl("api/job", {job: jobid}), {
method: "GET", cache: false, success: success
});
}
retry();
};


// mail
handle_ui.on("change.js-mail-recipients", function () {
Expand Down Expand Up @@ -13565,6 +13609,7 @@ Object.assign(window.hotcrp, {
// load_paper_sidebar
// make_review_field
// make_time_point
// monitor_autoassignment
// onload
paper_edit_conditions: function () {}, // XXX
popup_skeleton: popup_skeleton,
Expand Down
26 changes: 26 additions & 0 deletions src/api/api_job.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
// api_job.php -- HotCRP job-related API calls
// Copyright (c) 2008-2023 Eddie Kohler; see LICENSE.

class Job_API {
/** @return JsonResult */
static function job(Contact $user, Qrequest $qreq) {
if (($jobid = trim($qreq->job ?? "")) === "") {
return JsonResult::make_missing_error("job");
} else if (strlen($jobid) < 24
|| !preg_match('/\A\w+\z/', $jobid)) {
return JsonResult::make_parameter_error("job");
}

$tok = Job_Capability::find($jobid, $user->conf);
if (!$tok) {
return new JsonResult(404, ["ok" => false]);
} else {
$ok = $tok->is_active();
$jdata = $tok->data();
$answer = ["ok" => $ok] + (array) $jdata;
$answer["ok"] = $ok;
return new JsonResult($ok ? 200 : 409, $answer);
}
}
}
14 changes: 7 additions & 7 deletions src/api/api_upload.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ static function add_range($range, $lo, $hi) {
/** @return bool */
private function assign_token() {
for ($tries = 1; $tries !== 10; ++$tries) {
$this->_cap->salt = "hcup" . base48_encode(random_bytes(12));
$this->_cap->set_salt("hcup" . base48_encode(random_bytes(12)));
if (($handle = fopen($this->segment_file(), "x"))) {
fclose($handle);
if ($this->_cap->create()) {
Expand Down Expand Up @@ -195,7 +195,7 @@ function exec_start(Contact $user, Qrequest $qreq, PaperInfo $prow = null) {
$data["hashctx"] = base64_encode(serialize($hashctx));
$data["crc32ctx"] = base64_encode(serialize($crc32ctx));
}
$this->_cap->data = json_encode_db($data);
$this->_cap->set_data($data);
if ($this->assign_token()) {
$qreq->token = $this->_cap->salt;
return ["ok" => true];
Expand Down Expand Up @@ -325,12 +325,12 @@ private function modify_capd($callable) {
$this->conf->dblink,
"select `data` from Capability where salt=?", [$this->_cap->salt],
function ($oldd) use ($callable) {
$this->_cap->data = $oldd;
$this->_cap->set_data($oldd);
if (!$oldd || !($this->_capd = json_decode($oldd))) {
return $oldd;
}
call_user_func($callable, $this->_capd);
$this->_cap->data = json_encode_db($this->_capd);
$this->_cap->set_data($this->_capd);
return $this->_cap->data;
},
"update Capability set `data`=?{desired} where salt=? and `data`=?{expected}", [$this->_cap->salt]
Expand All @@ -339,7 +339,7 @@ function ($oldd) use ($callable) {

/** @return int */
private function reload_capd() {
$this->_cap->data = $this->conf->fetch_value("select `data` from Capability where salt=?", $this->_cap->salt);
$this->_cap->load_data();
$this->_capd = json_decode($this->_cap->data);
return $this->_capd->status ?? -1;
}
Expand Down Expand Up @@ -525,15 +525,15 @@ private function transfer($synchronous, $debugid) {
$new_data = json_encode_db($this->_capd);
$result = $this->conf->qe("update Capability set `data`=? where salt=? and `data`=?", $new_data, $this->_cap->salt, $this->_cap->data);
if ($result->affected_rows > 0) {
$this->_cap->data = $new_data;
$this->_cap->set_data($new_data);
$have_lock = $this->_capd->s3_lock;
break;
}
} else if (!$synchronous) {
return;
}
usleep(250000);
$this->_cap->data = $this->conf->fetch_value("select `data` from Capability where salt=?", $this->_cap->salt);
$this->_cap->load_data();
$this->_capd = json_decode($this->_cap->data);
if (!$this->_capd) {
$this->_error_ftext = "<0>Capability changed underneath us";
Expand Down
9 changes: 8 additions & 1 deletion src/assignmentset.php
Original file line number Diff line number Diff line change
Expand Up @@ -1062,11 +1062,18 @@ function __construct(Contact $user, $overrides = null) {
}

/** @param callable(AssignmentSet,?CsvRow) $progressf
* @return $this */
* @return $this
* @deprecated */
function add_progress_handler($progressf) {
$this->progressf[] = $progressf;
return $this;
}
/** @param callable(AssignmentSet,?CsvRow) $progressf
* @return $this */
function add_progress_function($progressf) {
$this->progressf[] = $progressf;
return $this;
}
/** @param string $search_type
* @return $this */
function set_search_type($search_type) {
Expand Down
Loading

0 comments on commit cff53e4

Please sign in to comment.