Skip to content

Add startInBackground() and automation catch-up barrier - #196

Merged
vanrogu merged 3 commits into
developfrom
claude/startup-readiness-liveness-probes-akazm0
Oct 30, 2026
Merged

vanrogu merged 3 commits into
developfrom
claude/startup-readiness-liveness-probes-akazm0

Conversation

@vanrogu

@vanrogu vanrogu commented Oct 30, 2026

Copy link
Copy Markdown
Member

Summary

This change adds non-blocking startup capability to bounded contexts and prevents automations from acting on incomplete todo lists during startup.

Key Changes

New startInBackground() method:

  • BoundedContextImpl now splits startup into two phases: beginStart() (wiring and processor initialization) and finishStart() (waiting for ephemeral read models)
  • startInBackground() returns immediately in STARTING state while read models project in the background, allowing processes to answer health probes during long startups
  • start() now waits for any in-progress background start rather than re-running startup logic
  • Startup state is tracked with startInProgress future and startGeneration counter to handle concurrent start attempts correctly

Automation catch-up barrier:

  • AutomationProcessor now captures the stream head when it begins working (start, restart, or promotion to leader) as catchUpBarrier
  • Automations wait for their todo list's projector to read up to this barrier before processing any items
  • This prevents automations from acting on items that are resolved later in the history (especially critical for EPHEMERAL todo lists rebuilt from scratch at each start)
  • The barrier is compared using EventReference total order, not just position, ensuring correctness across different storage backends

Read model module refactoring:

  • ReadModelModule.start() now calls startProcessors() then awaitEphemeralReadModelsProjected()
  • startProcessors() is public and can be called independently by BoundedContextImpl.beginStart()
  • awaitEphemeralReadModelsProjected() accepts a BooleanSupplier to allow early termination if the start is cancelled

Implementation Details

  • Virtual threads are used for background starts (Thread.ofVirtual())
  • Synchronization on BoundedContextImpl protects lifecycle state transitions and the start generation counter
  • The catch-up barrier is only read/written by the automation processor's own thread, avoiding synchronization overhead
  • Health checks report inMemoryReadModels status while STARTING, showing which read models are still projecting
  • Tests verify that automations do not act on incomplete todo lists, even with long event histories and background starts

Tests Added

  • AutomationDuringStartupTest: Verifies automations don't act on items resolved later in history during startup
  • BackgroundStartTest: Comprehensive tests for startInBackground() behavior, concurrent starts, and stop/terminate interactions

https://claude.ai/code/session_01PJQ5EHaNK3gUidThN29mrz

…ng up at startup

The automation's catch-up guard only compares the todo list's read position with the
last event the automation produced, so once the EPHEMERAL todo list's projector has
bookmarked its first batch, the automation works a list that does not yet reflect the
rest of the history: an item resolved later by someone else is handled anyway. The
test is disabled until the guard also waits for the todo list to reach the stream head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJQ5EHaNK3gUidThN29mrz
When an automation begins working (started, restarted, promoted to leader) it now reads
the stream head once and runs no round until its todo list's projector has read up to it.
The guard on its own bookmark only covered what the automation itself produced, so an
EPHEMERAL todo list rebuilt at startup was worked part-way through its catch-up, and an
item resolved later in the history was handled anyway. AutomationDuringStartupTest is
enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJQ5EHaNK3gUidThN29mrz
… it projects

startInBackground() does everything start() does except wait for the in-memory read
models: it returns with the context STARTING and completes the returned future once the
context is RUNNING, or once a stop() or terminate() cut the start short. A process can
then register its routes, open its HTTP server and answer liveness, startup and readiness
while a long history is projected. start() keeps its behaviour, and waits for a start
already under way instead of ignoring it. While STARTING, the inMemoryReadModels health
check names the read models still projecting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJQ5EHaNK3gUidThN29mrz
@vanrogu
vanrogu merged commit 59cb5c9 into develop Oct 30, 2026
1 of 2 checks passed
@vanrogu
vanrogu deleted the claude/startup-readiness-liveness-probes-akazm0 branch October 30, 2026 20:55
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