Skip to content

fix(galleryop): persist cancellable so restarted in-flight ops stay cancellable - #10454

Merged
mudler merged 1 commit into
masterfrom
fix/persist-gallery-op-cancellable
Jun 22, 2026
Merged

fix(galleryop): persist cancellable so restarted in-flight ops stay cancellable#10454
mudler merged 1 commit into
masterfrom
fix/persist-gallery-op-cancellable

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Problem

In distributed mode, when LocalAI restarts while a model/backend install is in flight, the operation reappears in the Operations panel after restart but cannot be cancelled - the cancel button is gone. It only clears once the 30-minute stale reaper expires it.

Root cause

The cancellable flag was never persisted to the gallery_operations table:

  • During an install the live path sets OpStatus.Cancellable = true on every progress tick, but GalleryService.UpdateStatus persisted only progress and status (UpdateProgress/UpdateStatus), never the cancellable column.
  • Create left the column at its zero value false.

So after a restart, Hydrate() rebuilt the op from the DB row with Cancellable: false, /api/operations reported "cancellable": false, and the UI hid the cancel button. The orphaned op then lingered until CleanStale/ReapStaleOperations flipped it to failed (~30 min) - exactly the reported "stays there on restart, can't cancel, after a bit it expires".

The functional cancel path already survived restart (CancelOperation persists store.Cancel even with no live CancelFunc in distributed mode, see existing cancel_persist_test.go); the only thing missing was the UI affordance that drives it.

Fix

  • distributed/gallery.go: UpdateProgress persists cancellable on every tick; UpdateStatus clears it on terminal transitions (a done/cancelled op is never cancellable).
  • galleryop/service.go: pass op.Cancellable through to the store; set Cancellable at row-create time (true for installs, !op.Delete for models) to cover the brief "pending" window before the first progress tick.
  • New cancellable_persist_test.go reproduces the restart-then-rehydrate scenario and asserts the op stays cancellable.

A rehydrated in-flight op is now cancellable, so an admin can dismiss the orphaned op immediately instead of waiting out the reaper.

Testing

go test ./core/services/galleryop/ passes (new spec red before the fix, green after).

…ancellable

In distributed mode a model/backend install marks OpStatus.Cancellable=true
while downloading, but the gallery_operations row never recorded it:
UpdateStatus persisted only progress/status and Create left the cancellable
column at its zero value. After a replica restart Hydrate rebuilt the op with
cancellable=false, /api/operations reported false, and the UI hid the cancel
button - the orphaned op then lingered until the 30-minute stale reaper
expired it ("stays there on restart, can't cancel, after a bit it expires").

Persist the flag on every progress tick and at row creation (installs are
cancellable, deletes are not), and clear it on terminal transitions. A
rehydrated in-flight op is now cancellable, so an admin can dismiss the
orphaned op immediately instead of waiting out the reaper. The functional
cancel path already survived restart (CancelOperation persists store.Cancel
even with no live CancelFunc); this restores the UI affordance that drives it.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
@mudler
mudler merged commit 56f8a66 into master Jun 22, 2026
55 of 58 checks passed
@mudler
mudler deleted the fix/persist-gallery-op-cancellable branch June 22, 2026 20:41
mudler added a commit that referenced this pull request Jun 22, 2026
…10460)

PR #10454 added a `cancellable bool` parameter to GalleryStore.UpdateProgress
but missed two callers under tests/e2e/distributed, breaking the build on
master (golangci-lint and tests-e2e-backend both failed to compile with
"not enough arguments in call to ... UpdateProgress").

Pass cancellable=true (both ops are downloading installs, which are
cancellable) and assert the flag is persisted, exercising the new behavior.


Assisted-by: Claude:claude-opus-4-8 [Claude Code]

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
@localai-bot localai-bot added the bug Something isn't working label Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants