Back to Cherry Studio

Job

src/main/core/job/README.md

2.0.03.3 KB
Original Source

Job

Unified background-job system: typed handlers, DB-driven dispatch, 6-state machine, restart recovery, retry backoff, schedule registry. Built on SchedulerService for time triggers.

Full documentation is at docs/references/job-and-scheduler/. This file is a quick-reference pointer.

TopicReference Doc
Architecture, two-service separation, DB-driven dispatchOverview
Startup recovery (60 s quiet window, mid-flight shutdown safety)Startup Recovery
pause() / drainInFlight() write quiesce for backup restorePause and drain
Four-layer lock model + business-level resource locksConcurrency & Locks
How to write a JobHandler (recovery / retry / catch-up / progress)Handler Authoring
Migrating existing servicesMigration Checklist
SchedulerService vs BaseService.registerInterval vs setIntervalScheduler Usage

File Structure

job/
├── JobManager.ts        # @Injectable lifecycle service: enqueue/enqueueTx, dispatch, schedule registry, GC
├── jobRegistry.ts       # Compile-time `interface JobRegistry` — business modules extend via declaration merging
├── types.ts             # JobHandler, JobContext, EnqueueOptions, JobHandle, cache key prefixes
├── runtime/
│   ├── DispatchQueue.ts # Per-queue mutex + concurrency cap (Layer 1)
│   ├── recovery.ts      # Per-processor declarative recovery (abandon / retry / singleton)
│   └── catchUp.ts       # Schedule miss-detection (skip-missed / after-startup policies)
└── __tests__/           # Unit + integration + smoke tests

Type Registration (consumers)

ts
// In your business module:
declare module '@main/core/job/jobRegistry' {
  interface JobRegistry {
    'my.task': { itemId: string }
  }
}

jobManager.registerHandler('my.task', {
  recovery: 'retry',
  async execute(ctx) { /* ... */ }
})

await jobManager.enqueue('my.task', { itemId: '42' })

// Atomic with a business write — inside a DbService.withWriteTx callback:
// jobManager.enqueueTx(tx, 'my.task', { itemId: '42' })

See Handler Authoring for the full handler contract.

Renderer Boundary

The renderer observes job state read-only via useJob / useJobProgress (shared cache + GET /jobs/:id). Triggering a job is decided in main by the owning business service, which calls jobManager.enqueue(...) directly; renderer-initiated triggering goes through a dedicated IPC route (e.g. the knowledge.add_items IpcApi route, whose main handler enqueues the index job). See overview.md — Renderer-side consumers.