Stream the answer as it is written, and let the operator stop it
This commit is contained in:
@@ -207,6 +207,62 @@ if ($action === 'memory_set') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── stop ──────────────────────────────────────────────────────────────────────
|
||||
// Cancels a generation in flight. Only ever signals ONE pid, verified to be the worker for this
|
||||
// exact job — never a process group. Signalling a group is what took the WebGUI down on
|
||||
// 2026-08-07, and no group kill is needed here: the worker is a single php process whose only
|
||||
// child-like thing is an HTTP connection to Ollama, which dies with it.
|
||||
//
|
||||
// Whatever was already generated is kept. A turn stopped at 80% is usually stopped because the
|
||||
// operator has seen enough, not because they want it discarded.
|
||||
if ($action === 'stop') {
|
||||
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
|
||||
|
||||
$token = trim($_POST['token'] ?? '');
|
||||
if (vv_ai_job_path($token) === null) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid token']); exit;
|
||||
}
|
||||
|
||||
$job = vv_ai_job_read($token);
|
||||
if ($job === null) { echo json_encode(['ok' => false, 'error' => 'No such job']); exit; }
|
||||
|
||||
$status = (string)($job['status'] ?? '');
|
||||
if ($status === 'done' || $status === 'error' || $status === 'stopped') {
|
||||
echo json_encode(['ok' => true, 'already' => true, 'status' => $status]); exit;
|
||||
}
|
||||
|
||||
$pid = (int)($job['pid'] ?? 0);
|
||||
// Below 2 is init or nonsense. A pid we cannot verify is a pid we do not signal.
|
||||
$killed = false;
|
||||
if ($pid >= 2) {
|
||||
// Pid reuse is the reason for this: the recorded worker may have exited seconds ago and
|
||||
// the number been handed to something else entirely. The cmdline must name both this
|
||||
// worker and this job's own file before anything is signalled.
|
||||
$cmdline = @file_get_contents("/proc/$pid/cmdline");
|
||||
$cmdline = $cmdline === false ? '' : str_replace("\0", ' ', $cmdline);
|
||||
if (strpos($cmdline, 'ai_chat_worker.php') !== false && strpos($cmdline, $token) !== false) {
|
||||
$killed = @posix_kill($pid, SIGTERM);
|
||||
// No escalation ladder. The worker holds no lock and writes the job file atomically,
|
||||
// so there is no cleanup that a delay would protect — and a SIGKILL race could land
|
||||
// between the temp write and the rename.
|
||||
}
|
||||
}
|
||||
|
||||
// The job file is rewritten either way. If the pid could not be verified the worker is
|
||||
// already gone, and the page still needs a terminal state instead of polling to its ceiling.
|
||||
$job['status'] = 'stopped';
|
||||
$job['stopped'] = true;
|
||||
$job['answer'] = trim((string)($job['partial'] ?? $job['answer'] ?? ''));
|
||||
unset($job['partial']);
|
||||
@file_put_contents(vv_ai_job_path($token), json_encode($job));
|
||||
|
||||
vv_ai_log(sprintf('stop token=%s pid=%d signalled=%s kept=%d chars',
|
||||
substr($token, 0, 12), $pid, $killed ? 'yes' : 'no', strlen($job['answer'])));
|
||||
|
||||
echo json_encode(['ok' => true, 'signalled' => $killed, 'kept' => strlen($job['answer'])]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── clear ─────────────────────────────────────────────────────────────────────
|
||||
if ($action === 'clear') {
|
||||
if (!$isPost) { http_response_code(405); echo json_encode(['ok' => false, 'error' => 'POST only']); exit; }
|
||||
@@ -433,7 +489,12 @@ if ($action === 'ask') {
|
||||
exit;
|
||||
}
|
||||
|
||||
$cmd = 'nohup php ' . escapeshellarg($worker) . ' '
|
||||
// setsid, not just nohup. nohup detaches from the terminal but leaves the child in the
|
||||
// caller's process group — php-fpm's. That is the arrangement that took the WebGUI down on
|
||||
// 2026-08-07 when a Stop signalled a group it did not own. Stop below signals one verified
|
||||
// pid and never a group, so this is belt and braces, but it also means a php-fpm restart no
|
||||
// longer takes a running generation with it.
|
||||
$cmd = 'setsid nohup php ' . escapeshellarg($worker) . ' '
|
||||
. escapeshellarg($jobFile) . ' '
|
||||
. escapeshellarg($question) . ' '
|
||||
. escapeshellarg(json_encode($clean)) . ' '
|
||||
|
||||
Reference in New Issue
Block a user