Skip to content

refactor: switch worker timers from setInterval to recursive setTimeout#2807

Open
andsai wants to merge 1 commit into
junobuild:mainfrom
andsai:refactor/2522-settimeout-workers
Open

refactor: switch worker timers from setInterval to recursive setTimeout#2807
andsai wants to merge 1 commit into
junobuild:mainfrom
andsai:refactor/2522-settimeout-workers

Conversation

@andsai
Copy link
Copy Markdown

@andsai andsai commented May 29, 2026

Closes #2522.

Follows the pattern from oisy-wallet#9706 — applied across all 8 worker timers in the Console (not just auth.worker.ts).

Why

With setInterval, a callback slower than the configured interval queues overlapping calls. The workers tried to defend against this with manual let syncing = false guards (5 of 8 files) or per-key Record<…, boolean> maps (wallet + workflows). Recursive setTimeout makes that guarantee structural: the next tick is only scheduled after the previous async callback resolves.

What

  • 8 worker files migrated:
    • auth.worker.ts, icp-cycles-rate.worker.ts, cycles.worker.ts, exchange.worker.ts, hosting.worker.ts, wallet.worker.ts, workflows.worker.ts, monitoring.worker.ts.
  • Each startXxxTimer now early-returns when nonNullish(timer) is true (matches OISY) so the timer can't be double-started.
  • clearIntervalclearTimeout.
  • Now-redundant overlap guards removed:
    • let syncing = false plus its guard/assignments in icp-cycles-rate, cycles, exchange, hosting, monitoring.
    • syncing: Record<IcrcAccountText, boolean> in wallet.worker.ts.
    • syncing: Record<WorkflowsIdbKey, boolean> in workflows.worker.ts. Its companion WorkflowsIdbKey import is auto-removed by ESLint as unused.

Verification

  • npm run check:all — green.
  • npx vitest --config ./vitest.frontend.config.ts --dir src/frontend run — 65 tests pass across 8 files.
  • grep -rn 'setInterval' src/frontend/src/lib/workers/ returns only the docstring references in the new // Recursive setTimeout (not setInterval) comments.

One subtle side effect worth flagging

For wallet.worker.ts, each account currently calls startTimerWithAccount and stomps on the module-level timer — so before this PR, the stopTimer() call only cleared the last account's interval and earlier ones leaked. With recursive setTimeout, the nonNullish(timer) check inside each closure naturally lets stopTimer() drain all account lineages on the next tick (they each see timer === undefined and don't reschedule). Behavior change is positive but worth a maintainer eyeball — happy to revert that side of the change if you'd prefer to keep the existing semantics and address per-account timer tracking in a separate PR.

…ut (junobuild#2522)

Following the pattern from oisy-wallet#9706: each worker now schedules
the next tick only after the previous async callback resolves, so the
sync cannot overlap itself. With setInterval, a callback slower than
the interval would queue overlapping calls (race conditions, duplicated
network requests).

Applied across all 8 worker timers:
  - workers/auth.worker.ts
  - workers/icp-cycles-rate.worker.ts
  - workers/cycles.worker.ts
  - workers/exchange.worker.ts
  - workers/hosting.worker.ts
  - workers/wallet.worker.ts
  - workers/workflows.worker.ts
  - workers/monitoring.worker.ts

Each start function now early-returns if a timer is already running
(`nonNullish(timer)`), matching the OISY pattern. clearInterval is
swapped for clearTimeout.

Now-redundant overlap guards are removed:
  - `let syncing = false` plus its guard/assignments in
    icp-cycles-rate, cycles, exchange, hosting, monitoring workers.
  - `syncing: Record<...,boolean>` plus per-key guard/assignments in
    wallet and workflows workers.

Recursive setTimeout guarantees serial execution by construction, so
the manual flags become dead code.
@andsai
Copy link
Copy Markdown
Author

andsai commented May 29, 2026

Hi @peterpeterparker 👋

Picked this one off the open list — the OISY reference made it a clean apply across all eight workers. Mirrored the exact pattern from #9706 there. The syncing flags became dead code so I removed them in the same pass; happy to put them back if you want the defensive layer.

Buon vento ⛵
— Andrea, Italy

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.

setTimeout instead of setInterval

1 participant