Mayfly implements the Lambda Runtime API
(2018-06-01) so an Elixir release runs on the provided.al2023 runtime.
Lifecycle
- Lambda executes
bootstrap(generated byMayfly.Release.bootstrap/1). For layer builds it first verifies that/opt/erlang/bin/erlexists and that the layer's ERTS version equalsreleases/start_erl.data, then runsbin/<release> eval "Mayfly.Boot.main()". Mayfly.Bootconfigures the Logger fromAWS_LAMBDA_LOG_FORMAT/AWS_LAMBDA_LOG_LEVEL, starts every application in the release, then callsMayfly.start_link/0.Mayfly.Supervisorresolves_HANDLERonce and runs the handler'sinit/1. On failure it posts to/runtime/init/errorand the VM exits with status 1 – Lambda marks the function as failed.- It starts
AWS_LAMBDA_MAX_CONCURRENCY(default 1)Mayfly.Pollerprocesses. Each poller long-polls/runtime/invocation/next, decodes the event, builds aMayfly.Context, callshandle/3and posts the result (/response, buffered or streamed) or the error (/error), then polls again.
Nothing in this chain runs unless bootstrap (or your code) starts it:
mix test and iex -S mix in a project depending on Mayfly are unaffected.
Modules
| Module | Role |
|---|---|
Mayfly | start_link/1 – the public entry point |
Mayfly.Boot | main/0 used by bootstrap; logger setup, app start, exit codes |
Mayfly.Supervisor | resolves + inits the handler, supervises pollers |
Mayfly.Poller | one concurrency slot: poll → invoke → respond, with backoff |
Mayfly.Handler | behaviour, _HANDLER resolution (module or legacy MFA), invocation with rescue/catch |
Mayfly.Context | per-invocation metadata from headers |
Mayfly.Response | buffered/streamed responses, Function URL prelude |
Mayfly.ErrorPayload | error documents, header type normalisation, X-Ray cause |
Mayfly.RuntimeAPI | the four API calls; a behaviour so tests can fake it |
Mayfly.HTTP | HTTP/1.1 over :gen_tcp: content-length, chunked (both directions), trailers |
Mayfly.Telemetry | optional :telemetry events |
Mayfly.LogFormatter | JSON lines for advanced logging controls |
Mayfly.Release | release steps: prepare, bootstrap, zip |
Mayfly.LocalRuntime | Runtime API emulator for mix lambda.invoke and tests |
Mix.Tasks.Lambda.Build / .Invoke | Docker build orchestration; local invoke |
Why no :httpc
:httpc needs :inets and :ssl at boot (slower cold start), returns
charlists (8–16× memory for large payloads), honours proxy settings the
link-local Runtime API must never see, and cannot send chunked request bodies
with trailers – which response streaming requires. A 250-line :gen_tcp
client covers exactly what the Runtime API needs.
Concurrency model
One BEAM process per concurrency slot; no shared mutable state in the runtime.
The only shared value is the handler state from init/1, passed immutably
to every handle/3. On standard Lambda there is one slot; on Managed
Instances there are AWS_LAMBDA_MAX_CONCURRENCY. A poller dies only on a
Runtime API container error (HTTP 500), which terminates the supervisor and,
via Mayfly.Boot, the VM – as the Runtime API contract demands.
Error contract
| Where | errorType | Header |
|---|---|---|
| Handler exception | exception module | Function.<Module> |
{:error, term} | HandlerError or the map's errorType | Function.<Type> |
exit / throw | Exit / Throw | Function.Exit / Function.Throw |
| Bad return / unencodable | Runtime.InvalidResponse | same |
| Bad JSON event | Runtime.InvalidEvent | same |
Bad _HANDLER | Runtime.NoSuchHandler (init) | same |
init/1 failure | Runtime.InitError (init) | same |
| Error after streaming started | trailers Lambda-Runtime-Function-Error-Type/-Body | – |
Lambda-Runtime-Function-Error-Type must look like <Category.Reason> or
Lambda normalises it to Runtime.Unknown/Function.Unknown; ErrorPayload.header_type/1
guarantees the shape. Lambda-Runtime-Invocation-Id is echoed on every
/response and /error call.
Environment variables
| Variable | Set by | Use |
|---|---|---|
AWS_LAMBDA_RUNTIME_API | Lambda | Runtime API host:port |
_HANDLER | Lambda Handler setting, default from bootstrap | Module or Module.function |
AWS_LAMBDA_MAX_CONCURRENCY | Lambda (Managed Instances) | number of pollers; also switches bootstrap from +S 1:1 to all vCPUs |
AWS_LAMBDA_LOG_FORMAT, AWS_LAMBDA_LOG_LEVEL | Lambda logging config | JSON formatter, level |
LOGLEVEL | you | overrides the level |
_X_AMZN_TRACE_ID | Mayfly, per invocation | X-Ray |
RELEASE_TMP, RELEASE_DISTRIBUTION | bootstrap | /tmp, none |
MAYFLY_ERTS | you (layer builds) | ERTS location, default /opt/erlang |