Skip to content

Add a hydration timeout to avoid blocking enqueue for too long - #309

Open
udnay wants to merge 3 commits into
mainfrom
yo/hydrate-start-timeout-enqueue-path
Open

Add a hydration timeout to avoid blocking enqueue for too long#309
udnay wants to merge 3 commits into
mainfrom
yo/hydrate-start-timeout-enqueue-path

Conversation

@udnay

@udnay udnay commented May 14, 2026

Copy link
Copy Markdown
Contributor

We just shipped eager startup hydration: JobStoreShard::open now awaits ConcurrencyCounts::hydrate_all, which scans every concurrency holder in durable storage and populates the in-memory cache before the shard goes live. This guarantees correct grant decisions from the first operation but pays the full scan cost synchronously.

For shards with very large holder sets that scan can take a long time. We want to soft-cap the hydration window so the shard becomes responsive quickly without sacrificing correctness:

  1. Hydration runs in a background task with a configurable timer T.
  2. After T fires (or hydration completes, whichever comes first), the shard starts accepting enqueues.
  3. Until hydration is fully done, the shard defers ticket granting: every concurrency-limited enqueue creates a durable TicketRequest instead of a holder. The grant scanner stays idle.
  4. When hydration finishes, the grant scanner wakes up and drains the backlog in priority order.
  5. Default behavior is unchanged: with no timer configured, open blocks on hydration exactly like it does today.

This preserves the safety property the Alloy spec already documents (omittedQueuesAreSafe) and adds a new operational property: "no holder is created while grants_enabled is false."


Note

High Risk
Changes shard open, concurrency granting, and enqueue/import gating on the critical path; incorrect timing could overcommit queues or reject traffic until hydration completes.

Overview
Adds optional startup_hydration_timeout_ms so shards with eager startup hydration (hydrate_all_at_startup) no longer have to block open on a full holder scan. Unset (default): behavior matches today—open waits for hydration, then enqueues and grants are allowed. Set: hydration runs in the background with a timer; after the timeout (or when hydration finishes, whichever applies for enqueues), the shard accepts work while grants_enabled stays false until hydration succeeds.

Two startup gates: accepting_enqueues blocks enqueue / import_jobs with ShardHydrating (gRPC unavailable); grants_enabled makes try_reserve_internal fail closed so concurrency-limited work becomes durable TicketRequests instead of holders. The grant scanner waits on that flag, then reconciles and drains; wake_grant_scanner runs when grants flip on. Failed hydration retries with grants still off; open failures call stop_background_tasks so timers/tasks don’t leak.

Config is threaded through factory, settings, benches, and test helpers; specs/job_shard.als documents the grant-gate invariant. New integration tests cover gate ordering, backlog drain, and pre-gate rejections.

Reviewed by Cursor Bugbot for commit f5d5e04. Bugbot is set up for automated code reviews on this repo. Configure here.

@udnay

udnay commented May 14, 2026

Copy link
Copy Markdown
Contributor Author

This change is part of the following stack:

Change managed by git-spice.

Comment thread src/concurrency.rs
@udnay
udnay force-pushed the yo/hydrate-at-start-up branch from e2d6ef8 to 405e1c3 Compare May 14, 2026 20:22
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from c36840b to 58fa338 Compare May 14, 2026 20:22
@udnay
udnay force-pushed the yo/hydrate-at-start-up branch from 405e1c3 to 061ab09 Compare May 14, 2026 20:26
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from 58fa338 to 1bcb9ef Compare May 14, 2026 20:26
task_group: &str,
) -> Result<String, JobStoreShardError> {
if !self.is_accepting_enqueues() {
return Err(JobStoreShardError::ShardHydrating);

@kirinrastogi kirinrastogi May 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the typescript client should be updated to retry on this error if we do go this route

@udnay
udnay force-pushed the yo/hydrate-at-start-up branch from 061ab09 to 739dafe Compare May 15, 2026 14:27
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from 1bcb9ef to aa7636c Compare May 15, 2026 15:27
Comment thread src/job_store_shard/mod.rs Outdated
let db = InstrumentedDb::new(Arc::new(db), shard_span);
let concurrency = Arc::new(ConcurrencyManager::new(name.clone(), metrics.clone()));

let accepting_enqueues = Arc::new(std::sync::atomic::AtomicBool::new(false));

@kirinrastogi kirinrastogi May 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should track this, the shard factory will return grpc Unavailable until this open_with_resolved_store completes. We should continue to use that signal. It is redundant to mark the shard as available but then return a new ShardHydrating error. And it complicates the routing clients.

We should measure the hydration time with your other PR, and if it is reasonable then we should do it synchronously on shard open, or let it run for ~5s or so before marking the shard as open. Some downtime here is acceptable because clients will retry their enqueues/leasetasks/report outcome rpcs

because of the dashmap usage hydration should work with enqueues at the same time

@udnay
udnay force-pushed the yo/hydrate-at-start-up branch from a37363b to 2262099 Compare May 15, 2026 20:21
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from aa7636c to f0dbcae Compare May 15, 2026 20:39
@udnay
udnay changed the base branch from yo/hydrate-at-start-up to main June 12, 2026 01:39
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from f0dbcae to 4732445 Compare June 12, 2026 01:39
Comment thread src/job_store_shard/mod.rs
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from 4732445 to 9b4c375 Compare June 12, 2026 10:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b4c375. Configure here.

Comment thread src/job_store_shard/mod.rs
udnay added 3 commits June 12, 2026 10:45
We just shipped eager startup hydration: JobStoreShard::open now awaits ConcurrencyCounts::hydrate_all, which scans every concurrency holder in durable storage and populates the in-memory cache before the shard goes live. This guarantees correct grant decisions from the first operation but pays the full scan cost synchronously.

 For shards with very large holder sets that scan can take a long time. We want to soft-cap the hydration window so the shard becomes responsive quickly without sacrificing correctness:

 1. Hydration runs in a background task with a configurable timer T.
 2. After T fires (or hydration completes, whichever comes first), the shard starts accepting enqueues.
 3. Until hydration is fully done, the shard defers ticket granting: every concurrency-limited enqueue creates a durable TicketRequest instead of a holder. The grant scanner stays idle.
 4. When hydration finishes, the grant scanner wakes up and drains the backlog in priority order.
 5. Default behavior is unchanged: with no timer configured, open blocks on hydration exactly like it does today.

 This preserves the safety property the Alloy spec already documents (omittedQueuesAreSafe) and adds a new operational property: "no holder is created while grants_enabled is false."
@udnay
udnay force-pushed the yo/hydrate-start-timeout-enqueue-path branch from 9b4c375 to f5d5e04 Compare June 12, 2026 14:45
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.

2 participants