Expose ILambdaSerializer on ILambdaContext#2378
Draft
GarrettBeatty wants to merge 2 commits into
Draft
Conversation
1080325 to
3971b6f
Compare
Adds an optional `Serializer` property to `ILambdaContext` (default-implemented to `null` on net8.0+, matching the existing TenantId/TraceId pattern), and has RuntimeSupport propagate the serializer registered with `HandlerWrapper` / `LambdaBootstrapBuilder.Create` through `RuntimeApiClient` to the per-invocation `LambdaContext`. User code can now reuse the configured serializer for ad-hoc serialization without re-instantiating it.
3971b6f to
69f9619
Compare
normj
reviewed
May 14, 2026
Member
normj
left a comment
There was a problem hiding this comment.
Additional things that have to be done to make this work:
- UserCodeLoader is used for the class library programming model and it takes care of grabbing the ILambdaSerializer via the
LambdaSerializerAttributeassembly attribute. Some of that instance needs to be communicate back to your code setting the serializer on the context. - Because in class library mode you might have an old Amazon.Lambda.Core that doesn't have the code changes to hold on to the ILambdaSerializer you have to make the code extra defensive. Don't change the constructor of ILambdaContext and make an internal setter. RuntimeSupport has access to internal methods. Then put the code setting the serializer in a separate method and make the calling method do a try/catch around the RuntimeSupport method for setting. You can see examples in RuntimeSupport with classes that have the "Isolated" suffix.
- Mark the property in Amazon.Lambda.Core for getting the ILambdaSerializer as preview because it won't work correctly for class library mode till the changes are deployed to the managed runtime.
- Doc wording: clarify that the Lambda function (not "the runtime") registers
the serializer, and call out both registration paths (LambdaBootstrapBuilder
for custom runtimes, [assembly: LambdaSerializer] for class-library mode).
- Mark ILambdaContext.Serializer as preview ([Experimental("AWSLAMBDA001")])
since class-library mode requires an updated managed runtime to populate it.
- Plumb [assembly: LambdaSerializer] for class-library mode: UserCodeLoader
exposes the constructed serializer; RuntimeSupportInitializer pushes it onto
LambdaBootstrap inside the wrapped initializer callback.
- Defensive forward-compat: replace constructor injection with an internal
setter on LambdaContext, and route the assignment through a new
LambdaContextSerializerIsolated shim. The Isolated pattern (mirroring
TraceProviderIsolated and the SnapStart helpers) defers JIT resolution of
the new ILambdaContext.Serializer member to a single one-line method, so a
TypeLoadException from a stale Amazon.Lambda.Core in the user's function
can be caught at the call site without crashing the invoke loop. After the
first failure, _disableSerializerOnContext short-circuits subsequent attempts.
There was a problem hiding this comment.
Pull request overview
Adds a preview serializer surface to Lambda context plumbing so handlers and downstream libraries can reuse the runtime-registered ILambdaSerializer without creating a separate serializer instance.
Changes:
- Adds
ILambdaContext.Serializeras a preview API inAmazon.Lambda.Core. - Propagates registered serializers through
HandlerWrapper,LambdaBootstrap, and class-library initialization. - Adds
TestLambdaContext.Serializerand unit coverage for the new serializer behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
Libraries/src/Amazon.Lambda.Core/ILambdaContext.cs |
Adds preview Serializer property to the context interface. |
Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs |
Stores serializers on serializer-based handler wrappers. |
Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs |
Carries the serializer into each invocation context. |
Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/UserCodeLoader.cs |
Exposes the class-library serializer instance after initialization. |
Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs |
Adds serializer storage on the runtime context implementation. |
Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/LambdaContextSerializerIsolated.cs |
Adds isolated helper for setting the serializer on context. |
Libraries/src/Amazon.Lambda.RuntimeSupport/RuntimeSupportInitializer.cs |
Wires class-library serializer initialization into bootstrap setup. |
Libraries/src/Amazon.Lambda.TestUtilities/TestLambdaContext.cs |
Adds writable serializer property for tests. |
Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaContextSerializerTests.cs |
Adds RuntimeSupport serializer propagation tests. |
Libraries/test/Amazon.Lambda.Core.Tests/TestLambdaContextSerializerTest.cs |
Adds TestLambdaContext serializer tests. |
.autover/changes/6e13a012-1f93-4e55-90b5-d2dd480d086c.json |
Records minor-version changelog entries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
142
to
+143
| var customerSerializerInstance = GetSerializerObject(customerAssembly); | ||
| CustomerSerializerInstance = customerSerializerInstance; |
| /// to mirror the serializer that the Lambda runtime support library would attach | ||
| /// in production. | ||
| /// </summary> | ||
| public ILambdaSerializer Serializer { get; set; } |
Comment on lines
+104
to
+108
| public void HandlerWrapper_AllSerializerOverloads_PropagateSerializer() | ||
| { | ||
| // One sample per overload family (Func/Action × Task/non-Task × in/out × ILambdaContext) | ||
| // is enough — they share the same field-assignment line. This guards against future | ||
| // overloads being added without setting Serializer. |
Comment on lines
+54
to
+56
| /// references — the cross-version <see cref="ILambdaSerializer"/> identity is | ||
| /// not guaranteed. The Isolated shim that exposes the value via | ||
| /// <see cref="ILambdaContext.Serializer"/> handles the cross-version cast. |
| { | ||
| /// <summary> | ||
| /// Wrapper around the call that sets <see cref="LambdaContext.Serializer"/>. | ||
| /// The user's Lambda function may reference an older <see cref="Amazon.Lambda.Core"/> |
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
Adds a preview
Serializerproperty toILambdaContextso user code can reuse theILambdaSerializerthat the Lambda function registered with the runtime — either viaLambdaBootstrapBuilder.Create(handler, serializer)(custom runtime) or[assembly: LambdaSerializer(typeof(...))](class-library mode) — without re-instantiating it.This is a small additive change to enable downstream features — most immediately, simplifying the AOT story for
Amazon.Lambda.DurableExecution(follow-up PR), but the property is generally useful for any user-side library that wants to honor the function's configured serialization choice.Motivation
Today the
ILambdaSerializerregistered by the function lives entirely insideHandlerWrapper's closure (custom runtime) or insideUserCodeLoader._invokeDelegate(class-library mode) — neither path makes it discoverable to downstream code. Libraries that want to (de)serialize within a handler are forced to either:ILambdaSerializeras an explicit constructor/method argument, duplicating registration.new DefaultLambdaJsonSerializer()), which diverges from the function's actual configuration and silently breaks AOT scenarios.Exposing the registered serializer via
ILambdaContextremoves the duplication and lets libraries inherit the function's AOT-aware serializer choice automatically.Design
Amazon.Lambda.CoreAdds
ILambdaSerializer Serializer { get { return null; } }toILambdaContext, inside the existing#if NET8_0_OR_GREATERblock, matching the precedent set byTenantIdandTraceId.ILambdaContext.netstandard2.0consumers see no change.[Experimental("AWSLAMBDA001")]so callers explicitly opt in (#pragma warning disable AWSLAMBDA001).Amazon.Lambda.RuntimeSupportTwo registration paths funnel into the same per-invocation setter:
Path 1 — custom runtime:
HandlerWrapper.Serializerexposes the serializer the user passed toGetHandlerWrapper(handler, serializer). All 20 serializer-taking overloads populate this field.LambdaBootstrapconstructors that take aHandlerWrapperforwardhandlerWrapper.Serializerinto a private_serializerfield.Path 2 — class-library mode:
UserCodeLoader.CustomerSerializerInstance(new) exposes the serializer constructed from[assembly: LambdaSerializer].RuntimeSupportInitializerwrapsUserCodeInitializer.InitializeAsyncso that, after init runs, it callsbootstrap.SetSerializer(userCodeLoader.CustomerSerializerInstance). This is the only place the new internalLambdaBootstrap.SetSerializersetter is intended to be used.Common per-invocation hookup:
LambdaBootstrap.InvokeOnceAsynccallsSetSerializerOnContext(impl)afterGetNextInvocationAsyncreturns the per-invocationLambdaContext.LambdaContextSerializerIsolated.TrySetSerializer— a one-line static shim that mirrors the existingTraceProviderIsolated,SnapstartHelperCopySnapshotCallbacksIsolated, etc. patterns.try { ... } catch (TypeLoadException) { ... } catch (MissingMethodException) { ... }. If the user's function references an olderAmazon.Lambda.Corethat doesn't declareILambdaContext.Serializer, the JIT throws when the Isolated method is JIT'd; the catch logs once and sets a_disableSerializerOnContextflag so subsequent invocations short-circuit instead of repeatedly throwing.LambdaContext.Serializeris a public read-only property with aninternalsetter ({ get; internal set; }). Constructor injection was deliberately avoided soLambdaContextcan still be constructed by older RuntimeSupport callers without signature breakage.Amazon.Lambda.TestUtilitiesTestLambdaContext.Serializeris a writable property so tests can mirror the production wiring.Usage
From an annotated function
From a custom runtime
From a downstream library
Compatibility
Amazon.Lambda.Core— Minor version bump. Additive; default-implemented interface member only available on net8.0+.Amazon.Lambda.RuntimeSupport— Minor version bump. New publicHandlerWrapper.Serializerproperty; new internalLambdaBootstrap.SetSerializer; newLambdaContextSerializerIsolatedshim. No removed or changed signatures.Amazon.Lambda.TestUtilities— Minor version bump. New publicTestLambdaContext.Serializerproperty.No removed APIs. No changed signatures.
netstandard2.0consumers see no change. Functions referencing an olderAmazon.Lambda.Corecontinue to run; they just seeILambdaContext.Serializer == nullbecause the Isolated shim catches theTypeLoadExceptionfrom the missing interface member.Tests
Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/.../LambdaContextSerializerTests.cs— 8 tests covering:LambdaContext.Serializerdefaults tonullwhen not set.LambdaContextSerializerIsolated.TrySetSerializerpopulates the property.LambdaContextSerializerIsolated.TrySetSerializeris null-tolerant on the context argument.HandlerWrapper.Serializeris populated for typed-input/output overloads.HandlerWrapper.Serializerisnullfor raw-stream overloads.LambdaBootstrap.InvokeOnceAsync: a handler invoked through the wrapper seescontext.Serializer == registered serializer.context.Serializerstaysnull.Libraries/test/Amazon.Lambda.Core.Tests/TestLambdaContextSerializerTest.cs— 2 tests covering theTestLambdaContext.Serializerround-trip.All 275 existing
Amazon.Lambda.RuntimeSupport.UnitTestscontinue to pass, includingNativeAOTTests.EnsureNoTrimWarningsDuringPublishwhichdotnet publishesNativeAOTFunctionand asserts zero trim/AOT warnings.Related
This is the first of two PRs. The follow-up will use
ILambdaContext.Serializerto deleteICheckpointSerializer<T>,ReflectionJsonCheckpointSerializer<T>, and theJsonSerializerContextoverloads inAmazon.Lambda.DurableExecution— collapsing 8WrapAsyncoverloads into 4 and removing all[RequiresUnreferencedCode]/[RequiresDynamicCode]attributes from that package.Test plan
Amazon.Lambda.RuntimeSupport.UnitTestspassAmazon.Lambda.Core.TestspassNativeAOTTests.EnsureNoTrimWarningsDuringPublishpasses (no new trim warnings)NativeAOTFunctioncompletes with zero IL2026/IL3050 warnings