Skip to content

feat(service-job): leader-elect scheduled cron/interval jobs across the cluster - #2219

Merged
xuyushun441-sys merged 1 commit into
mainfrom
seam/scheduler-leader-election
Jun 22, 2026
Merged

feat(service-job): leader-elect scheduled cron/interval jobs across the cluster#2219
xuyushun441-sys merged 1 commit into
mainfrom
seam/scheduler-leader-election

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Why

CronJobAdapter fired every scheduled tick on every replica → a cron job runs N times in an N-node cluster (duplicate invoices / emails / charges). This is the core multi-node correctness gap for scheduled work (cloud ADR-0018).

What

  • Scheduled fires (cron / interval / once timers) now go through runScheduled(name), which acquires a per-job cluster lock (job:<name>, waitMs:0 fail-fast) before running; only the winner runs the handler, peers skip.
  • Single-node unchanged: with no cluster wired or the in-memory driver, the lock always succeeds → fires normally. Cross-node dedup only activates with a remote driver (EE).
  • Manual trigger() bypasses the gate (explicit, node-local invocation).
  • JobServicePlugin injects the cluster service (getService('cluster'), undefined → single-node).
  • 5 unit tests (leader runs / non-leader skips / no-cluster runs / manual-trigger bypasses / lock released). Verified locally: strict DTS build + @objectstack/service-job tests green (36).

Scope / notes

Open-core mechanism; EE remote driver activates cross-node election. Best-effort per-fire leader election suited to timer-scheduled jobs; for strict exactly-once use the DB job queue (sys_job_queue, transactional CAS). Tightens cluster-semantics.mdx §7.4 (the deferred leader-elected scheduler).

🤖 Generated with Claude Code

…he cluster
CronJobAdapter fired every scheduled tick on EVERY replica, so a cron job ran
N times in an N-node cluster (duplicate side effects). Scheduled fires now
acquire a per-job cluster lock (fail-fast) before running — only the node that
wins runs the handler; peers skip. With no cluster wired or the in-memory
driver (single process) the lock always succeeds, so single-node behaviour is
unchanged. Manual trigger() bypasses the gate (explicit, node-local).
JobServicePlugin injects the cluster service into the adapter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 22, 2026 3:20pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): packages/services.

5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/guides/packages.mdx(via packages/services)
  • content/docs/guides/runtime-services/audit-service.mdx(via packages/services)
  • content/docs/guides/runtime-services/index.mdx(via packages/services)
  • content/docs/guides/runtime-services/settings-service.mdx(via packages/services)
  • content/docs/protocol/objectos/i18n-standard.mdx(via packages/services)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@xuyushun441-sys
xuyushun441-sys merged commit 45f4967 into mainJun 22, 2026
16 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the seam/scheduler-leader-election branch June 22, 2026 15:29
xuyushun441-sys added a commit that referenced this pull request Jun 22, 2026
…rve` (#2226)
`os serve` constructed the Runtime without a cluster config, so it always used
the in-memory cluster driver — a multi-replica deployment could not coordinate
(and the split-brain guard blocked it). `os serve` now reads OS_CLUSTER_DRIVER
(+ OS_REDIS_URL), dynamically imports the matching remote driver package (so it
works in both config-boot and compiled-artifact mode), and forwards the cluster
config to the Runtime. Open-core ships only the in-memory driver; remote drivers
(e.g. @objectstack/service-cluster-redis) are provided by the EE distribution —
absent ⇒ graceful fallback to in-memory.
Verified via A/B boot (local redis): with OS_CLUSTER_DRIVER=redis + replicas=2
the server boots (guard passes = redis active); without it, replicas=2 trips the
split-brain guard (memory). Enables the EE multi-node activation (cloud ADR-0018)
of the cache-invalidation (#2213) and scheduler-leader-election (#2219) seams.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 4, 2026
…ase 4) (#2583)
* feat(spec,lint): reject userFilters on object list views (ADR-0053 phase 4)
ADR-0053 reserves userFilters/quickFilters for page lists ("filters" mode); on
an object list view ("views" mode, where the ViewTabBar is the only nav
control) they are silently dropped. Land the phase-4 guardrail as a layered
defence so the wrong-context authoring mistake is caught without breaking
existing metadata:
- Type (author time): new ObjectListViewSchema = ListViewSchema minus
userFilters. Object built-in listViews + defineView list/listViews use it, so
userFilters on an object list view is a tsc error. Full ListViewSchema (page
filters mode) untouched.
- Runtime (back-compat): field STRIPPED at parse (default strip, no throw) —
existing metadata keeps loading; ObjectSchema.parse never fails on a stray
userFilters.
- Author/CI (actionable): new @objectstack/lint validateListViewMode, wired
into `os validate`, reports the wrong-context field PRE-parse (before the
schema strips it) with a fix hint.
Verified: spec 6673 tests + lint 122 tests green, turbo build (54 pkgs, tsc
typecheck) green, e2e normalize+lint chain catches both array/map forms.
Closes the schema half of objectui #2219; supersedes the interim runtime warn
in objectui #2220.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(spec): approve ObjectListViewSchema in public API surface
New export from the ADR-0053 phase-4 guardrail; the api-surface check flagged it
as 1 added / 0 breaking. Regenerated the snapshot via
`pnpm --filter @objectstack/spec gen:api-surface`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@xuyushun441-sys@os-zhuang