diff --git a/.autover/changes/5d462e9b-d94a-4005-9b7c-ddc43b5ab0d9.json b/.autover/changes/5d462e9b-d94a-4005-9b7c-ddc43b5ab0d9.json
new file mode 100644
index 000000000..4a85a6c5a
--- /dev/null
+++ b/.autover/changes/5d462e9b-d94a-4005-9b7c-ddc43b5ab0d9.json
@@ -0,0 +1,11 @@
+{
+ "Projects": [
+ {
+ "Name": "Amazon.Lambda.RuntimeSupport",
+ "Type": "Patch",
+ "ChangelogMessages": [
+ "Fixed ReflectionTypeLoadException when customer bundles Amazon.Lambda.Core \u003C 3.0.0 and runtime scans types. Replaced direct ILambdaResponseStream implementation with DispatchProxy to avoid exposing the type in assembly metadata."
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs
index 2cb46e3ce..fff3d06e5 100644
--- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs
+++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/ResponseStreaming/ResponseStreamLambdaCoreInitializerIsolated.cs
@@ -2,58 +2,109 @@
// SPDX-License-Identifier: Apache-2.0
using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-using Amazon.Lambda.Core.ResponseStreaming;
using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming;
#pragma warning disable CA2252
namespace Amazon.Lambda.RuntimeSupport
{
///
- /// This class is used to connect the created by to Amazon.Lambda.Core with it's public interfaces.
- /// The deployed Lambda function might be referencing an older version of Amazon.Lambda.Core that does not have the public interfaces for response streaming,
- /// so this class is used to avoid a direct dependency on Amazon.Lambda.Core in the rest of the response streaming implementation.
+ /// This class connects the created by
+ /// to Amazon.Lambda.Core's public response streaming interfaces.
///
- /// Any code referencing this class must wrap the code around a try/catch for to allow for the case where the Lambda function
- /// is deployed with an older version of Amazon.Lambda.Core that does not have the response streaming interfaces.
+ /// The deployed Lambda function might reference an older Amazon.Lambda.Core that does not have
+ /// the response streaming interfaces. To prevent ReflectionTypeLoadException when customer code
+ /// calls GetTypes() on this assembly, NO type in this class (or this assembly) directly implements
+ /// ILambdaResponseStream. Instead, we use DispatchProxy to generate the implementation dynamically
+ /// at runtime, only when the interface type is confirmed to exist in the loaded Amazon.Lambda.Core.
+ /// See: https://github.com/aws/aws-lambda-dotnet/issues/2430
///
///
internal class ResponseStreamLambdaCoreInitializerIsolated
{
///
- /// Initalize Amazon.Lambda.Core with a factory method for creating that wraps the internal implementation.
+ /// Initialize Amazon.Lambda.Core with a factory method for creating response streams.
+ /// All type references to ILambdaResponseStream are made via reflection to avoid embedding
+ /// the type dependency in this assembly's metadata.
///
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Response streaming types are preserved by the runtime and only loaded when available")]
+ [UnconditionalSuppressMessage("Trimming", "IL2055", Justification = "Response streaming types are preserved by the runtime")]
+ [UnconditionalSuppressMessage("Trimming", "IL2060", Justification = "Response streaming types are preserved by the runtime")]
+ [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Response streaming types are preserved by the runtime")]
+ [UnconditionalSuppressMessage("Trimming", "IL2080", Justification = "Response streaming types are preserved by the runtime")]
internal static void InitializeCore()
{
-#if !ANALYZER_UNIT_TESTS // This precompiler directive is used to avoid the unit tests from needing a dependency on Amazon.Lambda.Core.
- Func factory = (byte[] prelude) => new ImplLambdaResponseStream(ResponseStreamFactory.CreateStream(prelude));
- LambdaResponseStreamFactory.SetLambdaResponseStream(factory);
+#if !ANALYZER_UNIT_TESTS
+ var coreAssembly = typeof(Amazon.Lambda.Core.ILambdaContext).Assembly;
+
+ // Check if the loaded Amazon.Lambda.Core has the response streaming types.
+ // If not (older version loaded from /var/task), bail out gracefully — no exception.
+ var interfaceType = coreAssembly.GetType("Amazon.Lambda.Core.ResponseStreaming.ILambdaResponseStream");
+ if (interfaceType == null) return;
+
+ var factoryType = coreAssembly.GetType("Amazon.Lambda.Core.ResponseStreaming.LambdaResponseStreamFactory");
+ if (factoryType == null) return;
+
+ var setMethod = factoryType.GetMethod("SetLambdaResponseStream", BindingFlags.NonPublic | BindingFlags.Static);
+ if (setMethod == null) return;
+
+ // Create a Func using DispatchProxy.
+ // ResponseStreamProxy generates a runtime type implementing T (ILambdaResponseStream)
+ // that forwards calls to a ResponseStream instance.
+ var proxyFactoryMethod = typeof(ResponseStreamLambdaCoreInitializerIsolated)
+ .GetMethod(nameof(BuildFactory), BindingFlags.NonPublic | BindingFlags.Static)
+ .MakeGenericMethod(interfaceType);
+
+ var factory = proxyFactoryMethod.Invoke(null, null);
+ setMethod.Invoke(null, new object[] { factory });
#endif
}
- ///
- /// Implements the interface by wrapping a . This is used to connect the internal response streaming implementation to the public interfaces in Amazon.Lambda.Core.
- ///
- internal class ImplLambdaResponseStream : ILambdaResponseStream
+ private static Func BuildFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : class
{
- private readonly ResponseStream _innerStream;
-
- internal ImplLambdaResponseStream(ResponseStream innerStream)
+ return (byte[] prelude) =>
{
- _innerStream = innerStream;
- }
+ var stream = ResponseStreamFactory.CreateStream(prelude);
+ var proxy = DispatchProxy.Create();
+ ((ResponseStreamProxy)(object)proxy).SetInner(stream);
+ return proxy;
+ };
+ }
+ }
- ///
- public long BytesWritten => _innerStream.BytesWritten;
+ ///
+ /// A DispatchProxy that forwards ILambdaResponseStream calls to a ResponseStream instance.
+ /// This class does NOT implement ILambdaResponseStream at compile time — DispatchProxy generates
+ /// the interface implementation dynamically at runtime, avoiding the ReflectionTypeLoadException.
+ ///
+ internal class ResponseStreamProxy : DispatchProxy
+ {
+ private ResponseStream _inner;
- ///
- public bool HasError => _innerStream.HasError;
+ internal void SetInner(ResponseStream inner)
+ {
+ _inner = inner;
+ }
- ///
- public void Dispose() => _innerStream.Dispose();
+ protected override object Invoke(MethodInfo targetMethod, object[] args)
+ {
+ return targetMethod.Name switch
+ {
+ "WriteAsync" => _inner.WriteAsync((byte[])args[0], (int)args[1], (int)args[2],
+ args.Length > 3 ? (CancellationToken)args[3] : default),
+ "Dispose" => DoDispose(),
+ "get_BytesWritten" => _inner.BytesWritten,
+ "get_HasError" => _inner.HasError,
+ _ => null
+ };
+ }
- ///
- public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) => _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
+ private object DoDispose()
+ {
+ _inner.Dispose();
+ return null;
}
}
}
diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs
index 5da3d5e8b..3908020f9 100644
--- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs
+++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs
@@ -1,3 +1,4 @@
+using Amazon.Lambda.RuntimeSupport.UnitTests;
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#pragma warning disable CA2252
@@ -167,7 +168,7 @@ public class LambdaResponseStreamTests
var output = new MemoryStream();
await inner.SetHttpOutputStreamAsync(output);
- var implStream = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var implStream = new TestImplLambdaResponseStream(inner);
var lambdaStream = new LambdaResponseStream(implStream);
return (lambdaStream, output);
}
@@ -176,7 +177,7 @@ public class LambdaResponseStreamTests
public void LambdaResponseStream_IsStreamSubclass()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.IsAssignableFrom(stream);
@@ -186,7 +187,7 @@ public void LambdaResponseStream_IsStreamSubclass()
public void CanWrite_IsTrue()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.True(stream.CanWrite);
@@ -196,7 +197,7 @@ public void CanWrite_IsTrue()
public void CanRead_IsFalse()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.False(stream.CanRead);
@@ -206,7 +207,7 @@ public void CanRead_IsFalse()
public void CanSeek_IsFalse()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.False(stream.CanSeek);
@@ -216,7 +217,7 @@ public void CanSeek_IsFalse()
public void Read_ThrowsNotImplementedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.Throws(() => stream.Read(new byte[1], 0, 1));
@@ -226,7 +227,7 @@ public void Read_ThrowsNotImplementedException()
public void ReadAsync_ThrowsNotImplementedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
// ReadAsync throws synchronously (not async) — capture the thrown task
@@ -239,7 +240,7 @@ public void ReadAsync_ThrowsNotImplementedException()
public void Seek_ThrowsNotImplementedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.Throws(() => stream.Seek(0, SeekOrigin.Begin));
@@ -249,7 +250,7 @@ public void Seek_ThrowsNotImplementedException()
public void Position_Get_ThrowsNotSupportedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.Throws(() => _ = stream.Position);
@@ -259,7 +260,7 @@ public void Position_Get_ThrowsNotSupportedException()
public void Position_Set_ThrowsNotSupportedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.Throws(() => stream.Position = 0);
@@ -269,7 +270,7 @@ public void Position_Set_ThrowsNotSupportedException()
public void SetLength_ThrowsNotSupportedException()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var stream = new LambdaResponseStream(impl);
Assert.Throws(() => stream.SetLength(100));
@@ -342,7 +343,7 @@ public async Task WriteAsync_DelegatesToInnerResponseStream()
var output = new MemoryStream();
await inner.SetHttpOutputStreamAsync(output);
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
var data = new byte[] { 1, 2, 3 };
await impl.WriteAsync(data, 0, data.Length);
@@ -357,7 +358,7 @@ public async Task BytesWritten_ReflectsInnerStreamBytesWritten()
var output = new MemoryStream();
await inner.SetHttpOutputStreamAsync(output);
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
await impl.WriteAsync(new byte[7], 0, 7);
Assert.Equal(7, impl.BytesWritten);
@@ -367,7 +368,7 @@ public async Task BytesWritten_ReflectsInnerStreamBytesWritten()
public void HasError_InitiallyFalse()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
Assert.False(impl.HasError);
}
@@ -378,7 +379,7 @@ public void HasError_TrueAfterReportError()
var inner = new ResponseStream(Array.Empty());
inner.ReportError(new Exception("test"));
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
Assert.True(impl.HasError);
}
@@ -387,7 +388,7 @@ public void HasError_TrueAfterReportError()
public void Dispose_DisposesInnerStream()
{
var inner = new ResponseStream(Array.Empty());
- var impl = new ResponseStreamLambdaCoreInitializerIsolated.ImplLambdaResponseStream(inner);
+ var impl = new TestImplLambdaResponseStream(inner);
// Should not throw
impl.Dispose();
diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestImplLambdaResponseStream.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestImplLambdaResponseStream.cs
new file mode 100644
index 000000000..00c736b70
--- /dev/null
+++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestImplLambdaResponseStream.cs
@@ -0,0 +1,26 @@
+// Test-only implementation of ILambdaResponseStream for unit tests.
+// In production, DispatchProxy is used instead to avoid the type reference in assembly metadata.
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amazon.Lambda.Core.ResponseStreaming;
+using Amazon.Lambda.RuntimeSupport.Client.ResponseStreaming;
+
+namespace Amazon.Lambda.RuntimeSupport.UnitTests
+{
+ internal class TestImplLambdaResponseStream : ILambdaResponseStream
+ {
+ private readonly ResponseStream _innerStream;
+
+ internal TestImplLambdaResponseStream(ResponseStream innerStream)
+ {
+ _innerStream = innerStream;
+ }
+
+ public long BytesWritten => _innerStream.BytesWritten;
+ public bool HasError => _innerStream.HasError;
+ public void Dispose() => _innerStream.Dispose();
+ public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
+ => _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
+ }
+}