Skip to content

Implement JEP-0012: explicit lease lifecycle FSM for exporter#618

Closed
mangelajo wants to merge 1 commit intomainfrom
impl/jep-0012-lease-lifecycle-fsm
Closed

Implement JEP-0012: explicit lease lifecycle FSM for exporter#618
mangelajo wants to merge 1 commit intomainfrom
impl/jep-0012-lease-lifecycle-fsm

Conversation

@mangelajo
Copy link
Copy Markdown
Member

Summary

Implements JEP-0012 — replaces the ad-hoc event-based lease coordination (before_lease_hook, after_lease_hook_started/done, lease_ended) with an explicit finite state machine (LeaseLifecycle).

What changed

  • New lease_lifecycle.py: LeasePhase enum with 9 states (CREATEDSTARTINGBEFORE_LEASEREADYENDINGAFTER_LEASERELEASINGDONE / FAILED), transition validation, wait points (wait_ready, wait_complete, wait_end_requested), and end-request tracking.

  • LeaseContext now holds a LeaseLifecycle instead of 5 raw anyio.Event objects. drivers_ready() / wait_for_drivers() delegate to the lifecycle. skip_after_lease_hook is a property backed by the lifecycle.

  • exporter.py drives all transitions:

    • _run_before_hook_lifecycle() wraps hook execution with BEFORE_LEASE → READY (or → ENDING if end was requested during hook)
    • _run_ending_phase() is shared by _cleanup_after_lease and _handle_end_session — the FSM transition acts as a mutex (first caller to transition to AFTER_LEASE wins, second waits for completion)
    • _cleanup_after_lease() waits for lifecycle to reach READY (with safety timeout), then runs the ending phase
    • serve() / serve_standalone_tcp() use lifecycle.request_end() / lifecycle.wait_complete() instead of raw events
  • hooks.py: Removed before_lease_hook.set() from run_before_lease_hook's finally — the exporter now owns all lifecycle transitions.

Why

The previous event-based coordination had semantic overloading (before_lease_hook meant both "hook finished" and "connections may proceed") that led to deadlocks and race conditions (#567, #569, #598, #614). The FSM makes each state and transition explicit, eliminates the class of bugs where an event is set/checked from the wrong context, and provides a single point of truth for "where is this lease in its lifecycle?"

Net code change

  • -428 / +845 lines (net -65 in production code after removing event gymnastics; the growth is from the FSM module + comprehensive tests)
  • Removed: before_lease_hook, after_lease_hook_started, after_lease_hook_done, lease_ended events from LeaseContext
  • All 397 existing tests pass, lint clean

Test plan

  • All lease_lifecycle_test.py tests pass (29 tests covering transitions, wait points, end-request semantics)
  • All exporter_test.py tests pass (16 tests covering cleanup, consecutive leases, safety timeout, idempotent cleanup, lifecycle wrapper)
  • All lease_context_test.py tests pass (18 tests for lifecycle-backed LeaseContext)
  • All hooks_test.py tests pass (46 tests, updated to remove before_lease_hook assertions)
  • Full make pkg-test-jumpstarter passes (397 tests)
  • make lint passes
  • Manual integration test with controller + exporter + hooks

Made with Cursor

Replace the ad-hoc event-based coordination (before_lease_hook,
after_lease_hook_started/done, lease_ended) with a proper finite
state machine (LeaseLifecycle) that owns all phase transitions.

Key changes:
- New lease_lifecycle.py with LeasePhase enum, transition table, and
  LeaseLifecycle controller (wait points, end-request tracking)
- LeaseContext now holds a LeaseLifecycle instead of raw anyio.Events;
  drivers_ready/wait_for_drivers delegate to lifecycle
- Exporter drives transitions: CREATED → STARTING → BEFORE_LEASE →
  READY → ENDING → AFTER_LEASE → RELEASING → DONE
- _run_before_hook_lifecycle wraps hook execution with transitions
- _run_ending_phase shared by _cleanup_after_lease and
  _handle_end_session (FSM acts as mutex - first to transition wins)
- hooks.py no longer sets before_lease_hook; exporter owns all
  lifecycle transitions
- Comprehensive tests for FSM, lifecycle integration, and exporter
  state machine edge cases

Ref: JEP-0012
Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

Warning

Rate limit exceeded

@mangelajo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 55 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 21 minutes and 55 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31f0c206-4c5b-441b-8dc9-5cf0cf35718d

📥 Commits

Reviewing files that changed from the base of the PR and between bdb69e9 and 5d744ac.

📒 Files selected for processing (8)
  • python/packages/jumpstarter/jumpstarter/exporter/exporter.py
  • python/packages/jumpstarter/jumpstarter/exporter/exporter_test.py
  • python/packages/jumpstarter/jumpstarter/exporter/hooks.py
  • python/packages/jumpstarter/jumpstarter/exporter/hooks_test.py
  • python/packages/jumpstarter/jumpstarter/exporter/lease_context.py
  • python/packages/jumpstarter/jumpstarter/exporter/lease_context_test.py
  • python/packages/jumpstarter/jumpstarter/exporter/lease_lifecycle.py
  • python/packages/jumpstarter/jumpstarter/exporter/lease_lifecycle_test.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch impl/jep-0012-lease-lifecycle-fsm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mangelajo
Copy link
Copy Markdown
Member Author

Just wanted to see how this looked, and can be done much better than this :D

@mangelajo mangelajo closed this Apr 17, 2026
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.

1 participant