Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions packages/aws-durable-execution-sdk-python-extras/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ The code in the MicroVM receives the job and reports its result. The worker pack

The operation picks the delivery from the input size and the config:

1. A job whose `run` hook payload fits in 4,096 Unicode code points goes in the `RunMicrovm` request. Lambda passes it to the worker's `run` lifecycle hook. The payload also holds the callback ID, so an input of about 3,600 code points fits. The MicroVM gets the `NO_INGRESS` connector, so nothing can reach it.
2. A larger job goes over HTTP. The launch request carries no job, and a request step POSTs the job to the MicroVM's endpoint after it starts. The Python worker accepts a request body of at most 6 MiB. The MicroVM gets the `ALL_INGRESS` connector, and every request carries a short-lived auth token that the operation creates.
1. A `microvm` job goes in the `RunMicrovm` request when its `run` hook payload fits in 4,096 Unicode code points and its config sets no `request.path`. The payload holds the input, the callback ID, and a few other fields. Lambda passes the payload to the worker's `run` lifecycle hook. The MicroVM gets the `NO_INGRESS` connector, so nothing can reach it.
2. Every other job goes over HTTP:
- A `microvm` job whose `run` hook payload is over 4,096 code points.
- A `microvm` job whose config sets `request.path`, whatever its size.
- Every session job.

The launch request carries no job, and a request step POSTs the job to the MicroVM's endpoint after it starts. The Python worker accepts a request body of at most 6 MiB. The MicroVM gets the `ALL_INGRESS` connector, and every request carries a short-lived auth token that the operation creates.

The worker package runs its `handler` for both deliveries. So the code in the MicroVM does not change with the input size.

Expand Down Expand Up @@ -85,20 +90,25 @@ The terminate step runs only when the operation sees the job's outcome. In these

- A `map` or `parallel` completes early, for example with `min_successful`, while an item still waits for its MicroVM. The SDK never runs that item again.
- The execution fails, times out, or is stopped while the operation waits.
- Every launch attempt timed out or lost its connection, but the service created the MicroVM anyway. The operation never learned the MicroVM's ID.
- A launch attempt that timed out or lost its connection can still have created a MicroVM. When the launch step still fails, the operation never learns that MicroVM's ID. So it cannot terminate that MicroVM.

### Errors

Every MicroVM failure is a `MicrovmError`. Catch the base class for all of them, or a subclass for one kind:

| Error | When |
|---|---|
| `MicrovmLaunchError` | `RunMicrovm` failed after all retries. When the last attempt timed out or lost its connection, the service may have created the MicroVM anyway, and it then runs the job. So running the job again after this error can run it twice. |
| `MicrovmLaunchError` | `RunMicrovm` failed after all retries. A MicroVM can still run the job. The list below the table says when. |
| `MicrovmDeliveryError` | HTTP delivery failed after all retries, or the route answered a 4xx status such as 404, or a redirect. |
| `MicrovmNotRunningError` | A `MicrovmDeliveryError`: the MicroVM is terminating, terminated, or removed. It is not retried. A session that checks the MicroVM state before each job reports it. Any job over HTTP also reports it when `CreateMicrovmAuthToken` reports the MicroVM removed. See [Suspending an idle MicroVM](#suspending-an-idle-microvm). |
| `MicrovmJobFailedError` | The job handler in the MicroVM failed, or the MicroVM sent a result that is not valid JSON. The message includes the error type that the MicroVM reported. |
| `MicrovmTimeoutError` | No result arrived within `timeout`, or no heartbeat within `heartbeat_timeout`. A MicroVM that crashed ends this way. |

After `MicrovmLaunchError`, the delivery decides whether a MicroVM runs the job:

1. With `run` hook delivery, the launch request contains the job. A launch attempt that timed out or lost its connection can still have created a MicroVM. That MicroVM then runs the job. So running the job again after this error can run it twice.
2. With HTTP delivery, the launch request contains no job. The request step starts only after a successful launch. So when you invoke through a version or an alias, no MicroVM runs the job after an HTTP launch fails.

```python
from aws_durable_execution_sdk_python_extras.microvm import (
MicrovmError,
Expand Down Expand Up @@ -136,7 +146,7 @@ The 10-second reserve keeps time for the step to record its failure. A Lambda ti

- `timeout` must be between 1 second and 8 hours, because a MicroVM lives at most 8 hours. The MicroVM's `maximumDurationInSeconds` is `timeout` plus 5 minutes, at most 8 hours. So a timeout above 7 hours 55 minutes leaves the MicroVM less than 5 minutes beyond the callback's timeout.
- `job_input` is sent as JSON. So the MicroVM receives what `json.dumps` writes: a tuple arrives as a list, and a dict key that is not a string arrives as a string.
- The MicroVM gets the `INTERNET_EGRESS` connector, because it needs egress to reach the Lambda callback APIs. For ingress, it gets `NO_INGRESS` with `run` hook delivery and `ALL_INGRESS` with HTTP delivery. If you set `ingress_network_connectors`, allow ingress whenever an input can exceed the `run` hook limit. An empty tuple sends no connector.
- The MicroVM gets the `INTERNET_EGRESS` connector, because it needs egress to reach the Lambda callback APIs. For ingress, it gets `NO_INGRESS` with `run` hook delivery and `ALL_INGRESS` with HTTP delivery. If you set `ingress_network_connectors`, allow ingress for every job that goes over HTTP. A `microvm` job goes over HTTP when its `run` hook payload is over 4,096 code points, or when its config sets `request.path`. Every session job goes over HTTP. An empty tuple sends no connector.
- `heartbeat_timeout` starts when the callback is created, before the launch. The worker sends its first heartbeat when the job arrives. So the first period must cover the launch, the boot, and, for HTTP delivery, the request step, each with its retries. A throttled launch can take several minutes, and the request step retries for up to `retry_window` in each attempt. With a shorter heartbeat timeout, the operation fails with `MicrovmTimeoutError` before the MicroVM sends its first heartbeat.
- The request step needs at least 11 seconds left in the invocation when it starts. It keeps a 10-second reserve before the invocation times out, and it starts a call only with at least 1 second left before the reserve. The launch step can run earlier in the same invocation, so the function timeout must cover it too. A function timeout of `retry_window` plus 11 seconds, 71 seconds by default, leaves the first retry tier about its whole window. With less than 11 seconds left, every attempt fails with `MicrovmEndpointUnavailableError`, and the message of the final `MicrovmDeliveryError` names the function timeout. The Lambda default timeout is 3 seconds. With it, `microvm` runs small jobs in the `run` hook and fails every job that goes over HTTP: a job too large for the hook, and a job with `request.path`.
- The Region comes from the durable execution ARN, or from `AWS_REGION`.
Expand Down Expand Up @@ -278,8 +288,8 @@ The MicroVM operations handle the network, the service, and the worker lifecycle
1. Invoke the durable function through a published version or an alias, not `$LATEST`.
- An execution started through a version or an alias is pinned to that version. Every replay runs the same code and the same configuration, including the environment variables.
- An execution started through `$LATEST` is not pinned. After a deploy, it resumes on the new code. See [Invoking durable Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html).
- So under `$LATEST`, a deploy can land between two attempts of a session's launch step. The retry calls `RunMicrovm` with the new settings and the same client token. Assume that the first attempt created the MicroVM with suspending on, and that the new settings turn it off. If the service returns the MicroVM of the first attempt, the session records the resume mode of the new settings. So it skips the state check before each job. The MicroVM still suspends itself, and its endpoint then answers 502. So every later job fails with `MicrovmDeliveryError`, after the retries of both tiers. See [Suspending an idle MicroVM](#suspending-an-idle-microvm).
2. Do not read MicroVM settings from a source that can change during an execution, such as an SSM parameter, AppConfig, or an S3 object. Code outside a step runs again on every replay. So it reads the value again, and a changed value causes the same mismatch as in rule 1. Read such a value inside a step. A replay then uses the checkpointed value.
- So under `$LATEST`, a launch step that retries after a deploy can fail with `MicrovmLaunchError`. A MicroVM that an earlier attempt of that step created can then keep running, and be billed, until its maximum duration: the operation timeout plus 5 minutes, at most 8 hours.
2. Do not read MicroVM settings from a source that can change during an execution, such as an SSM parameter, AppConfig, or an S3 object. Code outside a step runs again on every replay. So it reads the value again. A value that changes between two attempts of a launch step can fail the launch, as in rule 1. Read such a value inside a step. A replay then uses the checkpointed value.
3. The MicroVM packages are experimental. A release can change the durable operations that the MicroVM operations record. [Subtypes](#subtypes) describes one such change. An execution that replays its history on such a release fails with `NonDeterministicExecutionError`. So let executions that started on an earlier build end on that build. Under rule 1, this needs no extra step: a new version starts only new executions, and running executions continue on their own version. Under `$LATEST`, let the running executions end before you deploy a new build.

### Function timeout
Expand All @@ -292,20 +302,26 @@ The MicroVM operations handle the network, the service, and the worker lifecycle
2. Keep the serialized job request at or under 6 MiB. The job request holds the input and a few job fields. Pass larger data by reference, for example as an S3 key.
- The Python worker reads a job request body of at most 6 MiB. It answers a larger body with HTTP 400.
- The operation does not check the size before it sends the job. So a larger body reaches the worker, and the job fails with `MicrovmDeliveryError`. It fails at once when the 400 reaches the operation. It fails only after the retries of both tiers when the connection is reset instead, and the MicroVM is billed meanwhile. [What the worker does](../aws-durable-execution-sdk-python-microvm-worker/README.md#what-the-worker-does) says when each case happens.
3. Expect an input of more than about 3,600 code points to go over HTTP. A job over HTTP needs three things that a `run` hook job does not:
- `lambda:CreateMicrovmAuthToken` in the function's role. See [Permissions](#permissions).
- The function timeout of [Function timeout](#function-timeout).
- Ingress to the MicroVM. The operation gives such a MicroVM the `ALL_INGRESS` connector. If you set `ingress_network_connectors`, include a connector that allows ingress.
3. Expect a job to go over HTTP in three cases:
- The `run` hook payload of a `microvm` job is over 4,096 code points. The payload holds the input, the callback ID, and a few other fields.
- The `microvm` config sets `request.path`. The job then goes over HTTP for any input size.
- The job is a session job. Every session job goes over HTTP.

Each missing item fails the job with `MicrovmDeliveryError`. Small inputs need none of them. So a function that passed its tests with small inputs can fail every large input. See [How the job reaches the MicroVM](#how-the-job-reaches-the-microvm).
A job over HTTP needs four things that a `run` hook job does not:
- `lambda:CreateMicrovmAuthToken` in the function's role. Without it, the job fails with `MicrovmDeliveryError`. See [Permissions](#permissions).
- The function timeout of [Function timeout](#function-timeout). With a shorter timeout, the job fails with `MicrovmDeliveryError`.
- Ingress to the MicroVM. The operation gives such a MicroVM the `ALL_INGRESS` connector. If you set `ingress_network_connectors`, include a connector that allows ingress. Without ingress, the request step cannot reach the MicroVM's endpoint. So the job fails.
- `lambda:PassNetworkConnector` on the `ALL_INGRESS` connector in the function's role. Its ARN is `arn:aws:lambda:<region>:aws:network-connector:aws-network-connector:ALL_INGRESS`. `RunMicrovm` authorizes each connector separately. So a role that allows `NO_INGRESS` and `INTERNET_EGRESS`, but not `ALL_INGRESS`, fails every HTTP launch with `MicrovmLaunchError`.

A `microvm` job whose payload fits the `run` hook, and whose config has no `request.path`, needs none of these four. So a function that passed its tests with such jobs can fail every job over HTTP. See [How the job reaches the MicroVM](#how-the-job-reaches-the-microvm).

### Job handlers

1. Follow [Best practices for job handlers](../aws-durable-execution-sdk-python-microvm-worker/README.md#best-practices-for-job-handlers) in the worker README for the code that runs each job in the MicroVM.
2. Make the side effects of a job safe to repeat.
- The worker ignores a second delivery of a callback ID. It remembers the callback IDs of its last 1,000 finished jobs, and it forgets them when its process restarts.
- Each `microvm` call and each `vm.invoke` creates a new callback ID. So when your code runs a job again after a failure, the worker runs it again, also in the same MicroVM.
- A failure can follow a run that already changed something. After `MicrovmLaunchError`, the MicroVM can still run the job. After `MicrovmTimeoutError`, the job can have done part of its work. See [Errors](#errors).
- A failure can follow a run that already changed something. After `MicrovmLaunchError` with `run` hook delivery, the MicroVM can still run the job. An HTTP launch request contains no job. The request step starts only after the launch succeeds. So when you invoke through a version or an alias, no MicroVM runs the job after `MicrovmLaunchError` with HTTP delivery. After `MicrovmTimeoutError`, the job can have done part of its work. See [Errors](#errors).

### The HTTP job route

Expand All @@ -318,7 +334,12 @@ The MicroVM operations handle the network, the service, and the worker lifecycle
### Sessions

1. Pass `vm.invoke` the context that the session handler receives, or a context nested in it. A job created in another context runs, and the next replay fails the execution with `NonDeterministicExecutionError`. `vm.invoke` raises `TypeError` only for the context that `microvm_session` was called with. It does not detect any other wrong context. See [`microvm_session`](#microvm_session-run-several-jobs-in-one-microvm).
2. Set `auto_suspend_idle_time` to at least 10 seconds. A shorter time raises `ValidationError` before any durable operation starts. See [Suspending an idle MicroVM](#suspending-an-idle-microvm).
2. If you set `auto_suspend_idle_time`, meet three conditions:
- `auto_suspend_on_idle` is `True`.
- The idle time is at least 10 seconds.
- The idle time is at most the session `timeout`.

A value that breaks any one condition raises `ValidationError` when `microvm_session` is called, before any durable operation starts. See [Suspending an idle MicroVM](#suspending-an-idle-microvm).
3. Change `auto_suspend_on_idle` or `idle_policy` only through a new version, as in [Deploys and versions](#deploys-and-versions). The launch records the resume mode, and a deploy changes it only for sessions that launch after it. Every job of a session that launched with suspending needs `lambda:GetMicrovm` and `lambda:ResumeMicrovm`, also after a deploy turns suspending off. So remove them from the function's role only as [Suspending an idle MicroVM](#suspending-an-idle-microvm) describes.

### Retry strategies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ class MicrovmBaseConfig:
1. ``NO_INGRESS`` when the job goes in the ``run`` hook. The job then
needs no inbound connection.
2. ``ALL_INGRESS`` when the job goes over HTTP. The request must reach
the MicroVM's endpoint. Every job of a session goes over HTTP.
the MicroVM's endpoint.

An empty tuple sends no ingress connector. Connectors that you set must
allow ingress whenever a job goes over HTTP: for :func:`microvm`,
whenever an input can exceed the ``run`` hook limit, and for a session,
always.
allow ingress for every job that goes over HTTP. A :func:`microvm` job
goes over HTTP when its ``run`` hook payload is over 4,096 code points,
or when its config sets ``request.path``. Every job of a session goes
over HTTP.
"""

egress_network_connectors: tuple[str, ...] | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,18 @@ def from_child_context_error(
class MicrovmLaunchError(MicrovmError):
"""``RunMicrovm`` failed after all retries.

When the last attempt got an answer, such as a ``ValidationException``,
no MicroVM runs the job. When the last attempt timed out or lost its
connection, the service may have created the MicroVM anyway. That MicroVM
then runs the job, and the operation cannot terminate it, because it never
learned the MicroVM's ID. The MicroVM ends at its maximum duration. So a
caller that runs the job again after this error can run it twice.
A launch attempt that timed out or lost its connection can still have
created a MicroVM. When the launch step still fails, the operation never
learns that MicroVM's ID. So it cannot terminate that MicroVM. The
MicroVM ends at its maximum duration.

1. With ``run`` hook delivery, the launch request holds the job. So that
MicroVM runs the job. A caller that runs the job again after this
error can run it twice.
2. With HTTP delivery, the launch request holds no job. The request step
starts only after a launch succeeds. So when you invoke through a
version or an alias, no MicroVM runs the job.
3. When no attempt created a MicroVM, no MicroVM runs the job.

When the service answered, the message ends with
``RunMicrovm request ID: <id>.``.
Expand Down
Loading
Loading