Skip to content

Fix and finish background processing mode (opt-in) - #1054

Merged
KevinJump merged 3 commits into
v17/mainfrom
feature/background-processing-mode
Aug 27, 2026
Merged

Fix and finish background processing mode (opt-in)#1054
KevinJump merged 3 commits into
v17/mainfrom
feature/background-processing-mode

Conversation

@KevinJump

@KevinJump KevinJump commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Finishes the previously half-wired SyncProcessingMode.Background path: the run's requestId was being passed as the long-running-operation type (so the concurrency guard never worked), the enqueue result was discarded, and the client got back an unrelated id it could never use to check on the run.
  • Adds GET Status/GET Running endpoints, backed by a small per-run progress cache, so the client can poll progress and reattach to a run after a page reload.
  • Fixes export-to-file timing - it now downloads on actual completion instead of immediately after the background enqueue call returns.
  • De-statics a shared Stopwatch in SyncActionService that made elapsed-time logging a cross-run/cross-user race.
  • Makes status polling staleness-aware: previously a crashed/restarted server left the client stuck reporting "running" forever, because Umbraco's GetStatusAsync doesn't expire a stale row the way GetByTypeAsync does. Now self-heals within Umbraco's expiration window (~5 min default), plus a manual dismiss escape hatch in the UI for when that's not fast enough.
  • Polling is now a SignalR fallback only (not an unconditional fixed 2s interval), with backoff while a run drags on - the earlier fixed interval was itself enough concurrent DB traffic to reproduce SQLite 'table is locked' errors during a background import.
  • Polished the "running in background" banner: a compact icon-only dismiss button (styled to blend into the alert rather than compete for attention - it's a rarely-needed escape hatch, not a primary action), moved to the top of the page so it's always the first thing seen.

Scope decision

ProcessingMode stays defaulted to Normal - this makes Background mode functional and safe to opt into, not a default change mid-version. SQLite-specific locking behavior is documented for anyone who turns it on: docs/perf/background-processing-mode.md.

API compatibility

ISyncManagementService (public) gained two new members (GetOperationStatusAsync, GetRunningOperationAsync), given default interface implementations ("not supported"/"nothing running") so an existing external implementation of the interface keeps compiling and behaves sensibly without needing to add them. Also checked uSync.Complete (our own downstream consumer) - it doesn't implement this interface, so unaffected either way.

Test plan

  • dotnet build on uSync.Backoffice.Management.Api and uSync.BackOffice - clean
  • dotnet test uSync.Tests - 141/141 passing
  • Client: tsc --noEmit and full npm run build (tsc + vite) - clean
  • Manual browser verification of Background mode end-to-end (reload-reattach, concurrent-run rejection, export/import-with-file) - see docs/perf/background-processing-mode.md for the verification checklist

🤖 Generated with Claude Code

KevinJump and others added 3 commits August 27, 2026 15:24
…changed)

The background sync path (SyncProcessingMode.Background) existed but was
half-wired: it passed the per-run requestId as the long-running-operation
*type* (so allowConcurrentExecution never worked), discarded the enqueue
result, and returned an unrelated id the client could never use to check on
the run. There was no way to poll progress or reattach after a page reload,
and export-to-file downloaded before the export had actually finished.

- Give each run a stable operation type (uSync:<Action>) and thread the real
  operation id back to the client; surface an "already running" rejection
  instead of reporting a phantom success.
- Add GET Status/Running endpoints backed by a small per-run progress cache,
  so the client can poll for progress and reattach to a run it didn't start
  itself (e.g. after F5). Cache entries expire after an hour.
- Fix export-to-file timing in the client - it now downloads on actual
  completion instead of immediately after the background enqueue returns.
- De-static a shared Stopwatch in SyncActionService that made elapsed-time
  tracking a cross-run/cross-user race; key it by requestId instead.
- Make status polling staleness-aware: a crashed/restarted server used to
  leave the client stuck reporting "running" indefinitely (Umbraco's
  GetStatusAsync doesn't expire a stale Enqueued/Running row the way
  GetByTypeAsync does). Now self-heals within Umbraco's expiration window,
  plus a manual "reset this view" escape hatch in the UI for when that's not
  fast enough.
- Poll only as a SignalR fallback (not unconditionally every 2s) and back
  off while a run drags on - the earlier fixed interval was itself enough
  concurrent DB traffic to trigger SQLite 'table is locked' errors during a
  background import.
- Keep ProcessingMode defaulting to Normal - this is opt-in, not a default
  change mid-version. Document the SQLite locking caveat for anyone who
  turns Background mode on (docs/perf/background-processing-mode.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Replace the full-width "reset this view" button with a compact
  icon-only dismiss (top-right of the alert), styled to match the
  banner's own colours so it blends in rather than competing for
  attention - it's a rarely-needed escape hatch, not a primary action.
- Move the banner to the top of the page so it's the first thing seen
  regardless of scroll position or other banners/content below it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Give GetOperationStatusAsync/GetRunningOperationAsync default interface
implementations instead of requiring every implementer to add them.
ISyncManagementService is public, so an external implementation of it
would otherwise fail to compile against this change; the defaults report
"not supported"/"nothing running" - safe, honest answers for an
implementation that predates background processing - and uSyncManagementService's
own explicit overrides still take precedence as normal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@KevinJump
KevinJump merged commit ef36222 into v17/main Aug 27, 2026
4 checks passed
@KevinJump
KevinJump deleted the feature/background-processing-mode branch August 27, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant