Repository navigation
feat(extras): deliver large MicroVM jobs over HTTP - #776
Merged
Merged
Conversation
A job whose run hook payload exceeds 4,096 code points, or any job when the config sets a request path, now goes over HTTP instead of failing with MicrovmDeliveryError. This matches the JavaScript operation. - The launch sends a run hook payload without the job, gives the MicroVM the ALL_INGRESS connector, and checkpoints the endpoint. - A new <name>.request step creates an auth token and POSTs the job to the endpoint. Inside one step attempt it retries connection errors, timeouts, 429, and 5xx from 250 ms to 4 s, and one 401 or 403 with a new token. It stops at the retry window, 60 seconds by default, or 10 seconds before the invocation times out. - boto3 cannot cancel a call. So a CreateMicrovmAuthToken call with a deadline runs in a daemon thread, and the step stops waiting at the reserve. - The default retry strategy retries the end of the first tier, and does not retry a route that rejects the job with another 4xx. - MicrovmRequestConfig sets the path, the port, and the retry window. - Replay follows the delivery that the launch checkpointed.
The heartbeat timeout's first period now covers the request step too, in the config docs and the README. A new test checks that a replay sends no second POST. It fails when the POST runs outside a step.
- Run each POST in a daemon thread, and wait at most 10 seconds or until the invocation reserve. A socket timeout applies to each socket operation, and each byte received starts it again. So a slow endpoint kept the request step running past the end of the invocation. - Do not follow redirects. A 301, 302, or 303 turned the POST into a GET, and its 2xx counted as accepted although the job never started. A redirect to another host received the auth token. A 3xx now raises MicrovmRequestRejectedError with the Location in the message. - Percent-encode the path as fetch does. A space or a non-ASCII character failed every attempt with a retryable error. - Re-raise a BaseException from the deadline thread with its own type. - Add tests: replay keeps the launch's delivery in both directions, the whole POST is bounded, redirects are rejected, the path is encoded, and proxy settings are ignored. Each fails under the mutation it guards against.
yaythomas
reviewed
Oct 15, 2026
- Fail the request step at once, with a message that names the function timeout, when less than 11 seconds remain in the invocation. The error stays retryable. Before, the message said that the MicroVM did not accept the job after 0 attempts. - Send the request path as written, and reject a path that is not an RFC 3986 path with ValidationError. The workers match the raw request path, so an encoded path missed a route registered with the same string. - Add an optional opener to the config, for a network that reaches the endpoint only through a proxy. Reject a redirect that such an opener followed. Build the default opener on first use. - Read the remaining time from the Lambda context directly. A failure of the call now raises, instead of turning off the reserve. - Document that a route can receive the same job again, and must ignore a repeated callback ID.
A caller's opener that follows redirects sent the auth token to the redirect target, also on another host. The POST now adds its headers as unredirected headers, which urllib does not copy to a followed request. Two fixes from the review of the JavaScript port apply here too: - The request path must not have a "." or ".." segment, also with %2e. The JavaScript SDK's fetch resolves such a segment, so both SDKs now accept the same paths. - The short-timeout message rounds the time down. So it never reads 11.0 seconds next to "at least 11 seconds". The README states that the 11 seconds must be left when the request step starts, and which jobs go over HTTP.
yaythomas
approved these changes
Oct 15, 2026
ParidelPooya
added a commit
to aws/aws-durable-execution-sdk-js
that referenced
this pull request
Oct 15, 2026
Closes #1004. ## Summary A review of the Python port's HTTP delivery ([aws-durable-execution-sdk-python#776](aws/aws-durable-execution-sdk-python#776)) found two defects that also exist in the JS request step. This PR fixes both, in the same way as the Python PR. ### A function timeout under 11 seconds `sendJob` keeps a 10-second reserve before the invocation times out. It starts a call only with at least 1 second left before the reserve. So a request step that started with less than 11 seconds left made no call. It failed with "The MicroVM did not accept the job ... after 0 attempts". That message blamed the MicroVM, and the cause was the function timeout. Changes: - `assertTimeToDeliver` reads the time left. Under 11 seconds, it throws `MicrovmEndpointUnavailableError` with this message: "The request step had 2.9 seconds left in the Lambda invocation, and it needs at least 11 seconds to send the job. Raise the function timeout." The time is rounded down, so the message never reads "11.0 seconds" next to "at least 11 seconds". - The request step calls it first, before the GetMicrovm call of a session job that checks the MicroVM state. `sendJob` calls it too. So `microvm` jobs and session jobs get the same message. - The error stays retryable. When the invocation spent its time before the step, a retry in a new invocation has more time. - A compute that reports no deadline skips the check, as before. - The README states that the 11 seconds must be left when the request step starts, and which jobs go over HTTP. ### The request path `fetch` parses the URL with the WHATWG URL parser. That parser percent-encodes a space, non-ASCII characters, and some other characters in the path. It also resolves a `.` or `..` segment, also when the dot is written as `%2e`. The worker matches the raw request path. So `request: { path: "/clone build" }` reached the worker as `/clone%20build`, the worker answered 404, and the operation failed with `MicrovmDeliveryError`. And `/jobs/../aws/lambda-microvms/runtime/v1/run` passed the lifecycle prefix check, and reached the worker as the `run` hook path. Changes: - `validateRequest` accepts only an RFC 3986 path (section 3.3): segments of pchar characters and `%XX` escapes. The regex is the one that the Python port uses. - It also rejects a `.` or `..` segment, also with `%2e`. A segment with other characters next to its dots, such as `/.well-known`, is accepted, and `fetch` leaves it unchanged. - Any other path throws a `TypeError` before any durable operation starts. The check also covers `vm.invoke`. - The README states that the path is sent as written, so the config and the route use the same string. The Python port has the same two rules since aws/aws-durable-execution-sdk-python#776 commit `f2fe5fa5`. So both ports accept the same paths. ## Behavior change These paths reached a route before this PR, and now throw a `TypeError`: - A path with a query, such as `/job?x=1`. The worker matched only the part before `?`. - A path with a fragment, such as `/job#x`. `fetch` did not send the fragment. - A path with a `.` or `..` segment, such as `/jobs/../build`. `fetch` sent `/build`. - A path with a character that `fetch` percent-encoded, such as a space. It reached the route only when the route was registered under the encoded string. To keep such a call working, write the path that the route serves. ## Testing - `npm test` in `packages/aws-durable-execution-sdk-js-extras`: 191 tests pass. `npm run lint` reports no errors. - New tests in `invocation-reserve.test.ts`: the exact short-timeout message with no call made, the "after 0 attempts" path when the reserve is reached after the check, and a compute with no deadline. - New test file `deliver-time-left.test.ts`: it calls `deliverJob` with a stub context and 2,900 ms left, with `resume: false` and `resume: true`. Both get the new message, with no GetMicrovm, token, or request call. A second test checks that 10,960 ms prints 10.9. - New test file `request-path.test.ts`: it sends 11 kinds of accepted path with the real `fetch` to a local HTTP server. The server receives each one as written, including `%20`, `%e2%9c%93`, `!$&'()*+,;=`, `:@`, `//`, `/.well-known/job`, and `/.../job`. - New cases in `microvm.test.ts` reject a space, a query, a fragment, a backslash, a non-ASCII character, a bad escape, an empty path, and six dot-segment paths. - Mutation check: without the check in `deliverJob`, the `resume: true` test fails. Without the dot-segment rule, 6 tests fail. With rounding to the nearest tenth, the rounding test fails. - Not run against the live service.
ParidelPooya
marked this pull request as ready for review
October 15, 2026 18:16
1 of 7 tasks
This branch was successfully 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 4b of the MicroVM port:
microvm()delivers a job over HTTP when it does not fit in therunhook. It follows the JavaScript operation in@aws/durable-execution-sdk-js-extras.Before this change, a job whose
runhook payload exceeded 4,096 code points failed withMicrovmDeliveryErrorbefore the launch. Now the operation picks the delivery:runhook payload fits goes in therunhook, as before.config.request.pathis set, goes over HTTP.The choice runs after the callback exists, because the payload holds the callback ID. Replay follows the delivery that the launch checkpointed, not a new choice.
HTTP delivery
runHookPayloadwith onlyversionandregion, because the worker rejects a payload without them. The MicroVM gets theALL_INGRESSconnector unless the config sets connectors.{"microvmId", "endpoint", "delivery": "http"}, the same document as JS.<name>.requeststep creates an auth token withCreateMicrovmAuthTokenand POSTs the job document to the endpoint. The body has the job fields at the top level, next toversion,region, andmicrovmId.MicrovmDeliveryError.Retries
The request step retries in two tiers, as in JS:
send_jobretries connection errors, timeouts, 429, and 5xx, with a delay from 250 ms to 4 s. It retries one 401 or 403 with a new auth token. Another 4xx, or a 3xx, raisesMicrovmRequestRejectedErrorat once. The tier ends at the retry window (60 s by default, 1 s to 10 min), or 10 s before the invocation times out.default_microvm_retry_strategynow retriesMicrovmEndpointUnavailableErrorandMicrovmRequestUnauthorizedError, and does not retryMicrovmRequestRejectedError.The 10-second reserve keeps time for the step to record its failure. boto3 cannot cancel a call. So a
CreateMicrovmAuthTokencall with a deadline runs in a daemon thread, and the step stops waiting for it at the reserve. Each POST runs in a daemon thread too, and the step waits for it at most 10 seconds, or until the reserve. A socket timeout alone does not bound a POST, because each byte received starts it again.The POST uses
urllibwith no proxy handler, because the request goes to the MicroVM endpoint directly.MicrovmConfig.openerreplaces it, for example for a network that reaches the endpoint only through a proxy. The default opener is built on first use. The path is sent exactly as written. It must be an RFC 3986 path without a.or..segment, because the workers match the raw request path. The JS port has the same rule in aws/aws-durable-execution-sdk-js#1005. The POST adds its headers as unredirected headers. So an opener that follows a redirect sends no auth token on.A request step that starts with less than 11 seconds left in the invocation fails at once with a retryable error that names the function timeout.
Differs from JS: redirects
The operation does not follow redirects. A 3xx fails the delivery at once, with the
Locationin the message. JS follows redirects, and that loses the job for a 301, 302, or 303: the POST becomes a GET without the job, and the GET's 2xx counts as accepted. A redirect to another host also receives the auth token. aws/aws-durable-execution-sdk-js#1001 proposes the same behavior for JS.New public API
MicrovmRequestConfigwithpath,port, andretry_window, andMicrovmConfig.requestandMicrovmConfig.opener.DEFAULT_MICROVM_JOB_PATHandDEFAULT_MICROVM_PORT.MicrovmRequestRejectedError,MicrovmRequestUnauthorizedError, andMicrovmEndpointUnavailableError.Invalid request settings raise the core's
ValidationErrorbefore any durable operation starts.Testing
urlopenwith proxies, and catching onlyExceptionin the deadline thread.request_test.pyrunssend_jobagainst a local HTTP server: the retry statuses, the delays and their cap, the token refresh, the retry window, the reserve, the request timeout, a server that answers one byte at a time, redirects, a caller's opener and a redirect it followed, with no auth token on the followed request, the path sent as written, the time-left check, and proxy settings in the environment.operation_test.pyruns the operation on the local test runner with HTTP delivery: a large job, a configured route and port, a rejected job, a step retry after the first tier, a replay that keeps the launch's delivery in both directions, a replay that sends no second POST, a missing endpoint, and a botocoreStubbercheck ofCreateMicrovmAuthTokenagainst the service model.worker_composed_test.pysends a large job and a route job over HTTP to the real worker from feat(microvm-worker): add HTTP listener and server #767.