Repository navigation
feat(microvm-worker): act on lifecycle hooks - #782
Draft
ParidelPooya wants to merge 4 commits into
Draft
ParidelPooya wants to merge 4 commits into
ParidelPooya wants to merge 4 commits into
Conversation
The worker now acts on the suspend, resume, and terminate hooks, and suspends its own MicroVM when a session asks for it. - suspend: answer 200, then refuse jobs with 503 until the resume hook or for 30 seconds. A run hook job that arrives then is deferred. - terminate: set each running job's cancelled event, fail its callback with MicrovmTerminatedError, and answer after the reports, at most 5 seconds. Later jobs get 503. - The three hooks act only on Lambda's own calls: a local Host and no x-amzn-requestid. A forwarded request gets 200 and is ignored. - autoSuspendIdleSeconds: after that many idle seconds the worker calls SuspendMicrovm with the run hook's MicroVM ID, and refuses jobs until the resume hook or for 30 seconds. A rejected first attempt ends the refusal at once. An unknown outcome keeps it. The worker now needs boto3 1.43.35 for the lambda-microvms client.
Address the review of the lifecycle hooks. - SuspendMicrovm: a before-send handler stops botocore's retries at the 10-second deadline. Before, retries went on in the call's thread, and an attempt started 22.5 seconds after the first. - A throttle, expired credentials, or a clock offset on the first attempt ends the refusal at once, and the idle time starts again. The service answers these before it acts. - Docs: idle() covers the wait, not the call, and the wait without the resume hook is a little over 30 seconds. - A composed test covers an image without the resume hook.
- Reach a dual-stack worker on ::1. On macOS, the worker's [::] bind with SO_REUSEADDR succeeds on a port that another process holds on 127.0.0.1. A test that connected to 127.0.0.1 then reached that process. Linux refuses such a bind, so a MicroVM is not affected. - finish_job waits for the lock, so the idle time has started when it returns. - The deadline test waits for the call to end, and records attempts on the client side, so a slow accept on the fake service cannot fail it. 80 runs of the worker tests with 8 workers passed. Before, 15 of 262 runs had a failure.
Resolve the conflict with 04daf40, which fails a job whose heartbeats cannot start. The heartbeats start under the lock here. When they cannot start, the job's thread takes the job out of the terminate hook's set and marks it as reporting. So the terminate hook waits for that report, and does not fail the job a second time.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Part 6 of the MicroVM port: the worker acts on the
suspend,resume, andterminatelifecycle hooks, and suspends its own MicroVM when a session asks for it. It follows the JS worker in@aws/durable-execution-sdk-js-microvm-worker(aws/aws-durable-execution-sdk-js#972, #975, and #976).Before this PR, the worker answered these hooks with HTTP 200 and did nothing else. It parsed
autoSuspendIdleSecondsfrom therunhook payload and ignored it. Somicrovm_session(..., auto_suspend_on_idle=True)from #779 had no effect with the Python worker.The
suspendhookresumehook arrives, or for 30 seconds. A job accepted now would freeze with the MicroVM. After a 503, the durable function resumes the MicroVM and delivers the job again.runhook job that arrives during the refusal is deferred, and starts when the refusal ends. The durable function cannot deliver arunhook job again.The
terminatehookcontext.cancelled, stops its heartbeats, and fails its callback with the newMicrovmTerminatedError. Themicrovmoperation then fails at once withMicrovmJobFailedError(MicrovmTerminatedError), instead of at the job's heartbeat timeout or its timeout.runhook requests whose body is still arriving, and for the failure reports of invalid requests. A secondterminatehook waits for the same work.runhook job withMicrovmTerminatedError, and never suspends again.Hooks that do not come from Lambda
The MicroVM endpoint forwards a request to a hook path from any caller that holds an auth token for the MicroVM. Probes from the JS work found that Lambda's own hook calls carry a local
Hostand nox-amzn-requestidheader, and forwarded requests carry both. So the worker acts onsuspend,terminate, andresumeonly when theHostislocalhost,127.0.0.1, or[::1], with any port, and the request has nox-amzn-requestid. Any other request to these hooks gets HTTP 200 and a warning. Therunhook does not use the check, because a false rejection there would lose every job.Suspending when idle
When the
runhook payload setsautoSuspendIdleSeconds:runhook arrives without a job, when a job ends, and when theresumehook arrives. A job that starts cancels it.SuspendMicrovmwith therunhook's MicroVM ID, never one from a job request, and refuses jobs from that moment.resumehook, or 30 seconds after the call returned.before-sendhandler on the client stops botocore's retries at that deadline. Without it, botocore kept retrying in the call's thread, and a probe measured an attempt 22.5 seconds after the first one.AccessDeniedException, or missing credentials. The refusal ends at once. The worker logs thelambda:SuspendMicrovmpermission, and does not retry until a job ends.New options:
create_microvms_clientonstart_microvm_workerandMicrovmWorkerListener.create.MicrovmWorkerListener.close()stops the idle time, andMicrovmWorker.close()calls it.Dependency change
The worker now requires
boto3>=1.43.35, up from1.42.1. botocore 1.43.35 is the first release with thelambda-microvmsclient. The extras package already requires it.Differences from JS
suspendandresumecomes before the 200 answer. In JS the answer and the change run in one event loop turn. In Python another request thread could accept a job between them.before-sendhandler, because botocore has no per-call deadline. JS passes anAbortSignal.Not in this PR
Testing
worker.pyandself_suspend.pyhave 100% line and branch coverage. mypy and ruff are clean.lifecycle_hooks_test.pyruns a real worker over HTTP, with a fake clock in place of the worker's timers. It ports the cases of the JSlifecycle-hooks.test.tsandauto-suspend.test.ts, and adds Python cases: a terminate hook before a job's thread runs, a stale timer that fires after its cancel, threads and timers that cannot start, and the three call outcomes.self_suspend_test.pycovers the outcome classification, and checks against a silent local service that no attempt starts after the deadline. With thebefore-sendhandler removed, that test fails.worker_composed_test.pyruns amicrovm_sessionwhose real worker suspends itself between two jobs, with and without theresumehook. Without it, the second job gets 503 answers until the refusal ends, and the session succeeds.dbe36864.[::]withSO_REUSEADDR, and macOS lets that bind succeed on a port that another process holds on127.0.0.1. Tests now connect to::1. 80 of 80 runs with 8 workers passed, against 15 failures in 262 runs before. Linux refuses such a bind, so a MicroVM is not affected.