Skip to content

Commit 403fa8b

Browse files
raballewclaude
andcommitted
fix: release lease on unused timeout when hooks are configured
When a lease times out without any client connection and hooks are configured, _cleanup_after_lease skipped the afterLease hook (correct) but also skipped calling _request_lease_release (bug). This left the exporter permanently stuck in LeaseReady status because the controller was never notified that the lease should be freed. Add _request_lease_release() call in the else branch of _cleanup_after_lease so the controller always frees the lease, regardless of whether the afterLease hook ran. Fixes: #237 Generated-By: Forge/20260416_202053_681470_86fec9bd_i237 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b19d632 commit 403fa8b

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

python/packages/jumpstarter/jumpstarter/exporter/exporter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ async def _cleanup_after_lease(self, lease_scope: LeaseContext) -> None:
643643
await self._report_status(ExporterStatus.AVAILABLE, "Available for new lease")
644644
else:
645645
logger.debug("Exporter is shutting down, skipping AVAILABLE status report")
646+
await self._request_lease_release()
646647
if not lease_scope.after_lease_hook_done.is_set():
647648
lease_scope.after_lease_hook_done.set()
648649
else:

python/packages/jumpstarter/jumpstarter/exporter/exporter_test.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,62 @@ async def track_status(status, message=""):
187187
assert ExporterStatus.AVAILABLE in statuses
188188
assert lease_ctx.after_lease_hook_done.is_set()
189189

190+
async def test_unused_lease_with_hooks_calls_request_lease_release(self):
191+
"""When a lease ends with no client and hooks are configured,
192+
_request_lease_release must still be called so the controller
193+
frees the lease. This prevents the exporter from getting stuck
194+
in LeaseReady status permanently."""
195+
from jumpstarter.config.exporter import HookConfigV1Alpha1, HookInstanceConfigV1Alpha1
196+
from jumpstarter.exporter.hooks import HookExecutor
197+
198+
lease_ctx = make_lease_context(client_name="")
199+
lease_ctx.before_lease_hook.set()
200+
201+
hook_config = HookConfigV1Alpha1(
202+
after_lease=HookInstanceConfigV1Alpha1(script="echo cleanup", timeout=10),
203+
)
204+
hook_executor = HookExecutor(config=hook_config)
205+
206+
exporter = make_exporter(lease_ctx, hook_executor)
207+
208+
await exporter._cleanup_after_lease(lease_ctx)
209+
210+
exporter._request_lease_release.assert_awaited_once()
211+
212+
async def test_unused_lease_without_hooks_calls_request_lease_release(self):
213+
"""When a lease ends with no client and no hooks configured,
214+
_request_lease_release must be called so the controller frees
215+
the lease."""
216+
lease_ctx = make_lease_context(client_name="")
217+
lease_ctx.before_lease_hook.set()
218+
219+
exporter = make_exporter(lease_ctx)
220+
221+
await exporter._cleanup_after_lease(lease_ctx)
222+
223+
exporter._request_lease_release.assert_awaited_once()
224+
225+
async def test_unused_lease_during_shutdown_still_releases(self):
226+
"""When a lease ends with no client during exporter shutdown,
227+
_request_lease_release must still be called even though AVAILABLE
228+
status is not reported."""
229+
lease_ctx = make_lease_context(client_name="")
230+
lease_ctx.before_lease_hook.set()
231+
232+
statuses = []
233+
234+
async def track_status(status, message=""):
235+
statuses.append(status)
236+
237+
exporter = make_exporter(lease_ctx)
238+
exporter._stop_requested = True
239+
exporter._report_status = AsyncMock(side_effect=track_status)
240+
241+
await exporter._cleanup_after_lease(lease_ctx)
242+
243+
exporter._request_lease_release.assert_awaited_once()
244+
assert ExporterStatus.AVAILABLE not in statuses
245+
190246
async def test_new_lease_after_unused_timeout_recovery(self):
191247
"""After recovering from unused lease timeout, a new lease
192248
can be accepted and processed."""

0 commit comments

Comments
 (0)