Skip to content

perf: one list read per refresh, parallel loads, no duplicate watcher refresh (v0.12.0) - #15

Merged
TadMSTR merged 1 commit into
mainfrom
feat/parallel-refresh-shared-list
Sep 30, 2026
Merged

TadMSTR merged 1 commit into
mainfrom
feat/parallel-refresh-shared-list

Conversation

@TadMSTR

@TadMSTR TadMSTR commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • One upstream GET /tasks per refresh, down from two. /tasks (unfiltered) and /headless-runs (via queueIndexByPrefix) now join one in-flight read (src/shared-read.ts). Only the read is shared: a settled promise is dropped immediately, so there is no cache and no time window.
  • Parallel reads in the UI. loadTasks uses Promise.allSettled for tasks / headless-runs / dead-letters. After a button press the list and the task detail refresh in parallel. Results apply last-started-wins (Latest in src/refresh-rules.ts).
  • No second refresh after a button press. The watcher's tasks event for the tab's own write used to trigger another full refresh about 3 s later. coveredByRefresh skips it when a refresh whose list read succeeded started after the change the event reports.
  • Per-task double-click guard (Start included). A second click is dropped before confirm(). Mutations are never sent in parallel or twice. The server's accepted unpark race assumes that clients don't do this.
  • v0.12.0 is cut from main, so it includes 0.11.1's redirect fix.

Why: the list read grows linearly with the active queue, and each refresh read it twice, sequentially.

Deliberately not done

  • No time-based result reuse. The coalescer serves only callers that arrive while a read is on the wire. The UI fires the two routes together, which is exactly that case, so a TTL would add staleness for no measured gain.
  • Filtered /tasks and the dead-letters read are not shared. They are different queries.
  • No route-level integration test. server.ts calls listen() at import, as before. The invalidation behaviour is tested through the real invalidatingWrite + callControlApi + queueGet against an injected fetch, and source-level pins assert that server.ts calls callControlApi only inside mutate() and that the watcher invalidates before its debounce. This follows the existing union/regex drift-gate precedent.
  • No change to auth, the token file or manifest grants.

Look hardest at

  • src/shared-read.ts get()/invalidate(): a read that began before a mutation must never be joined by a request made after the mutation returned, and an old read settling must not detach a newer one.
  • src/server.ts mutate(): invalidation runs in finally, i.e. also on refused and transport-failed writes.
  • src/refresh-rules.ts coveredByRefresh: the argument that timer lateness and WS latency can only err toward refreshing.
  • src/index.ts mutateThenRefresh / pendingActions: the guard is released after the mutation returns, not after the refresh.

Test plan

  • npm run build, npm test (221 pass, 17 new), gate:vocabulary, gate:corpus
  • New tests mutation-checked: removing the invalidation, the coalescing, the "only clear if current" check or the generation check each turns tests red
  • Built backend run locally against a live task-queue-mcp through a counting proxy (POSTs stubbed at the proxy). Tab load: 3 upstream reads / 0.372 s → 2 / 0.250 s. Park cycle: 4 reads / 0.382 s → 3 / 0.268 s.
  • After deploy: in CloudCLI, Park/Unpark refresh once, and an agent-side change still refreshes live

No .coderabbit.yaml in this repo (noted, not added here).

Tracked internally as vikunja#1003.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Task lists, run information, and dead-letter data now refresh in parallel, making updates faster.
    • Duplicate actions on a task are ignored while an earlier action is still pending.
  • Bug Fixes
    • Older refreshes and task-detail responses no longer overwrite results from newer requests.
    • Redundant refreshes triggered by a completed action are skipped, while updates from later changes still appear.
    • Task-list errors are kept separate from failures loading secondary data.

… refresh (v0.12.0)

- /tasks (unfiltered) and /headless-runs join one in-flight upstream GET /tasks
  (SharedRead). Only the read is shared; nothing is reused once it settles.
  Every mutation goes through mutate() -> invalidatingWrite, which invalidates
  before the route responds; the watcher invalidates on the raw fs event.
- The UI's three reads, and a button's list + detail refresh, run in parallel.
  Results apply last-started-wins (Latest).
- A watcher event already covered by a successful refresh that started after
  the change is skipped (coveredByRefresh), removing the second full refresh
  after every button press.
- Per-task in-flight guard: a second click (Start included) is dropped before
  confirm(); mutations are never parallel or duplicated (task-queue-mcp F-01).
- Includes 0.11.1's redirect fix. Measured vs live API: tab load 0.372 s / 3
  upstream reads -> 0.250 s / 2; Park cycle 0.382 s / 4 -> 0.268 s / 3.

Programme task-queue-read-perf-2026-09 part 3; vikunja#1003.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 44a68280-e993-48d3-a91f-22d5d5796d43

📥 Commits

Reviewing files that changed from the base of the PR and between a2562b9 and 52bfb64.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • manifest.json
  • package.json
  • src/index.ts
  • src/refresh-rules.ts
  • src/server.ts
  • src/shared-read.ts
  • src/tests/refresh-rules.test.ts
  • src/tests/shared-read.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change adds shared in-flight reads for eligible task lists and invalidates them after writes or matching watcher events. The UI coordinates parallel reads, prevents stale refresh results from applying, skips covered watcher events, and serializes actions per task.

Changes

Task queue coordination

Layer / File(s) Summary
Shared backend reads and invalidation
src/shared-read.ts, src/server.ts, src/tests/shared-read.test.ts, AGENTS.md, README.md, CHANGELOG.md
The backend shares in-flight unfiltered task reads and invalidates them after mutations and matching watcher events. Tests cover sharing, invalidation, failures, and routing. Documentation describes the read and invalidation behavior.
UI refresh ordering and watcher coverage
src/refresh-rules.ts, src/index.ts, src/tests/refresh-rules.test.ts, CHANGELOG.md
The UI runs task, headless-run, and dead-letter reads in parallel. It applies only the newest load and skips a watcher refresh when a successful task read covers the event.
Serialized task actions and refreshes
src/index.ts, CHANGELOG.md, manifest.json, package.json
Task actions use a per-task in-flight guard and shared refresh handling. The package and manifest versions change to 0.12.0.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Other

Sequence Diagram(s)

sequenceDiagram
  participant TaskQueueUI
  participant TaskQueueServer
  participant SharedRead
  participant ControlAPI
  TaskQueueUI->>TaskQueueServer: Submit task mutation
  TaskQueueServer->>ControlAPI: Execute mutation
  TaskQueueServer->>SharedRead: Invalidate shared task read
  TaskQueueUI->>TaskQueueServer: Request refreshed task data
  TaskQueueServer->>SharedRead: Get shared in-flight task read
  SharedRead->>ControlAPI: GET /tasks
Loading

Merge Risk: ⚪ Minimal · up to 52bfb

This change reduces redundant task reads and duplicate refreshes. No actionable merge-blocking issue was identified. The watcher-skip behavior has not been tested after deployment, so it is worth a quick check once released.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 52bfb

Existing privileges and access boundaries appear unchanged. However, partial refresh failures can hide failed work, and a stalled shared read can delay subsequent refreshes across the configured queue. Deployment-specific access controls were not verified.

Retained concerns

  • Low · reliability · observed: A successful task-list read records watcher coverage even when the simultaneous dead-letter or headless-run read fails. Failed secondary data is rendered empty, and the covering watcher event then skips its recovery refresh. The base always performed that watcher refresh. This can prolong a misleading healthy dead-letter display until another event or manual refresh, weakening failed-work visibility and operator recovery.
  • Medium · reliability · inferred: While an upstream read remains pending, every subsequent unfiltered task or headless-status reader joins the same promise, including manual refreshes. The application supplies no read deadline or cancellation signal. Unlike the base's independent requests, a stalled response can therefore hold later readers behind the same request until settlement, explicit invalidation, or process restart. This expands failure coupling within one plugin process; production transport limits may bound its duration.
Security review details

Security Blast Radius

  • inferred — Read sharing is scoped to one server process and its configured queue credential, not a caller-selected tenant or token. Its new failure coupling affects unfiltered task lists and headless-run status lookup; filtered task and dead-letter reads remain independent upstream queries.

Trust Boundaries and Controls

  • observed — Requests continue using the process-owned queue token. The existing client rejects insecure upstream base URLs and missing tokens, and refuses redirects rather than forwarding the credential to another destination. SharedRead does not add credential selection or authorization policy.

Resilience and Maintainability Implications

  • observed — Normal rejection and post-write recovery preserve shared-read ownership, but watcher suppression tracks only task-list success. Manual refresh remains available to recover failed secondary displays; it cannot detach a still-pending shared upstream read.

Hardening Proposals

  • proposed — Bound the shared read with an application deadline and identity-safe cleanup. Track secondary-read recovery separately from task-list coverage, and distinguish unavailable dead-letter data from a verified empty result.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main performance changes: shared list reads, parallel refresh loads, and skipped duplicate watcher refreshes. It is concise and specific.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@TadMSTR

TadMSTR commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@TadMSTR

TadMSTR commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

forge agent report — composed by the security agent (model claude-sonnet-5) for build task-queue-read-perf-2026-09-p3-plugin-refresh.
Posted through a shared automation account, so the author of this post is not a per-agent identity.
Full report, internal: host-forge/build-reports/task-queue-read-perf-2026-09-p3-plugin-refresh/audit.md

CodeRabbit round 1 complete — 0 findings passed to the audit for verification. Security audit started.

Scope Count
Repositories 1
Files in scope 6

Findings will be posted here when the audit completes.

@TadMSTR

TadMSTR commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

forge agent report — composed by the security agent (model claude-sonnet-5) for build task-queue-read-perf-2026-09-p3-plugin-refresh.
Posted through a shared automation account, so the author of this post is not a per-agent identity.
Full report, internal: host-forge/build-reports/task-queue-read-perf-2026-09-p3-plugin-refresh/audit.md

Security audit complete (round 1) — 0 findings.

Severity Count
Critical 0
High 0
Medium 0
Low 0
Info 0
Disposition Count
Resolved 0
Accepted 0
Deferred 0

Finding detail is in the internal report referenced above.

@TadMSTR
TadMSTR merged commit 56302b5 into main Sep 30, 2026
3 checks passed
@TadMSTR
TadMSTR deleted the feat/parallel-refresh-shared-list branch September 30, 2026 18:50
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