fix(cookbook): prevent auto-retry from restarting user-stopped downloads (#1778)
Two related bugs in the Cookbook task lifecycle: 1. "Stop all" fired kills via .click() inside a synchronous forEach but showed the success toast immediately after — the toast appeared before any of the async kill requests had been sent, giving the user false confidence the tasks were stopped. 2. The download auto-retry logic (triggered when DOWNLOAD_FAILED appears in the task output) had no way to distinguish a network interruption from a deliberate user stop. A download stopped via "Stop all" or the individual Stop button could be silently restarted up to two times by the background monitor. Fix: persist _userStopped: true to localStorage at the moment the user clicks Stop (individually) or Stop all. The auto-retry guard checks this flag before relaunching the download. The flag is written BEFORE the kill requests fire so there is no window where the monitor can race. Fixes #1458
This commit is contained in:
@@ -1705,6 +1705,9 @@ export function _renderRunningTab() {
|
||||
const running = _loadTasks().filter(t => (t.remoteHost || '') === host && t.status === 'running');
|
||||
if (!running.length) { uiModule.showToast(`Nothing running on ${_serverName(host)}`); return; }
|
||||
if (!await window.styledConfirm(`Stop ${running.length} running task${running.length > 1 ? 's' : ''} on ${_serverName(host)}?`, { confirmText: 'Stop all' })) return;
|
||||
// Mark every task as user-stopped BEFORE firing the kills so that the
|
||||
// download auto-retry logic never restarts a task the user just stopped.
|
||||
running.forEach(t => _updateTask(t.sessionId, { _userStopped: true }));
|
||||
// Reuse each task's own Stop action so it does the full teardown
|
||||
// (send C-c, drop the endpoint, mark stopped) consistently.
|
||||
running.forEach(t => {
|
||||
@@ -2166,6 +2169,7 @@ export function _renderRunningTab() {
|
||||
const badge = el.querySelector('.cookbook-task-status');
|
||||
if (badge) { badge.textContent = 'stopping...'; badge.className = 'cookbook-task-status cookbook-task-stopping'; }
|
||||
el.dataset.status = 'stopped';
|
||||
_updateTask(task.sessionId, { _userStopped: true });
|
||||
const outputText = el.querySelector('.cookbook-output-pre')?.textContent || task.output || '';
|
||||
// Drop the model endpoint so the picker stops listing it.
|
||||
if (task.type === 'serve' && task.payload) {
|
||||
@@ -2549,7 +2553,7 @@ async function _reconnectTask(el, task) {
|
||||
const _accessDenied = /Access to model.*is restricted|gated repo|GatedRepoError|401 Unauthorized|403 Forbidden|not in the authorized list|awaiting a review|must (?:be authenticated|have access)/i.test(snapshot);
|
||||
const _dlKey = task.payload?.repo_id || task.name;
|
||||
const _dlN = _dlRetryCount.get(_dlKey) || 0;
|
||||
if (!_accessDenied && task.type === 'download' && task.payload && _dlN < _DL_MAX_AUTO_RETRY) {
|
||||
if (!_accessDenied && !task._userStopped && task.type === 'download' && task.payload && _dlN < _DL_MAX_AUTO_RETRY) {
|
||||
// Auto-retry: kill the dead session and re-launch (resumes from
|
||||
// the cached .incomplete files) after a short delay.
|
||||
_dlRetryCount.set(_dlKey, _dlN + 1);
|
||||
|
||||
Reference in New Issue
Block a user