Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amazon.Util.Internal.PlatformServices is not trimmable. MissingMethodException No parameterless constructor defined for type 'Amazon.Util.Internal.PlatformServices.NetworkReachability' #2531

Closed
Beau-Gosse-dev opened this issue Feb 2, 2023 · 5 comments
Labels
bug This issue is a bug. module/sdk-generated p2 This is a standard priority issue queued response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.

Comments

@Beau-Gosse-dev
Copy link

Describe the bug

When publishing a .NET Lambda with native AOT that uses Secrets Manager, the below exception is thrown at runtime.

Expected Behavior

No runtime exception

Current Behavior

{
  "errorType": "AggregateException",
  "errorMessage": "One or more errors occurred. (A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.)",
  "stackTrace": [
    "at System.Threading.Tasks.Task`1.GetResultCore(Boolean) + 0x73",
    "at Amazon.Lambda.RuntimeSupport.HandlerWrapper.<>c__DisplayClass44_0`2.<GetHandlerWrapper>b__0(InvocationRequest invocation) + 0x64",
    "at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.<InvokeOnceAsync>d__17.MoveNext() + 0x169"
  ],
  "cause": {
    "errorType": "TypeInitializationException",
    "errorMessage": "A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.",
    "stackTrace": [
      "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x153",
      "at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9",
      "at Amazon.SecretsManager.AmazonSecretsManagerConfig..ctor() + 0x11",
      "at Amazon.SecretsManager.AmazonSecretsManagerClient..ctor(RegionEndpoint) + 0x39",
      "at HelloWorldDotnetAot.Function.<GetSecret>d__2.MoveNext() + 0x56"
    ],
    "cause": {
      "errorType": "TypeInitializationException",
      "errorMessage": "A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.",
      "stackTrace": [
        "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x153",
        "at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9",
        "at Amazon.Util.Internal.InternalSDKUtils.BuildUserAgentString(String) + 0x38",
        "at Amazon.SecretsManager.AmazonSecretsManagerConfig..cctor() + 0x20",
        "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb9"
      ],
      "cause": {
        "errorType": "MissingMethodException",
        "errorMessage": "No parameterless constructor defined for type 'Amazon.Util.Internal.PlatformServices.NetworkReachability'.",
        "stackTrace": [
          "at System.ActivatorImplementation.CreateInstance(Type, Boolean) + 0x120",
          "at Amazon.Util.Internal.PlatformServices.ServiceFactory..ctor() + 0x144",
          "at Amazon.Util.Internal.PlatformServices.ServiceFactory..cctor() + 0x12d",
          "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb9"
        ]
      }
    }
  },
  "causes": [
    {
      "errorType": "TypeInitializationException",
      "errorMessage": "A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.",
      "stackTrace": [
        "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x153",
        "at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9",
        "at Amazon.SecretsManager.AmazonSecretsManagerConfig..ctor() + 0x11",
        "at Amazon.SecretsManager.AmazonSecretsManagerClient..ctor(RegionEndpoint) + 0x39",
        "at HelloWorldDotnetAot.Function.<GetSecret>d__2.MoveNext() + 0x56"
      ],
      "cause": {
        "errorType": "TypeInitializationException",
        "errorMessage": "A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.",
        "stackTrace": [
          "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x153",
          "at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9",
          "at Amazon.Util.Internal.InternalSDKUtils.BuildUserAgentString(String) + 0x38",
          "at Amazon.SecretsManager.AmazonSecretsManagerConfig..cctor() + 0x20",
          "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb9"
        ],
        "cause": {
          "errorType": "MissingMethodException",
          "errorMessage": "No parameterless constructor defined for type 'Amazon.Util.Internal.PlatformServices.NetworkReachability'.",
          "stackTrace": [
            "at System.ActivatorImplementation.CreateInstance(Type, Boolean) + 0x120",
            "at Amazon.Util.Internal.PlatformServices.ServiceFactory..ctor() + 0x144",
            "at Amazon.Util.Internal.PlatformServices.ServiceFactory..cctor() + 0x12d",
            "at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb9"
          ]
        }
      }
    }
  ]
}

Reproduction Steps

Create a secret in Secrets Manager called 'MyTestSecret' in us-east-1.

Create a native AOT function with dotnet new lambda.NativeAOT

Add the Amazon.SecretsManager NuGet package to the project.

Update the function code to be like below:

using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using System.Text.Json.Serialization;

using Amazon;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;

namespace HelloWorldDotnetAot;

public class Function
{
    /// <summary>
    /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase. It
    /// initializes the .NET Lambda runtime client passing in the function handler to invoke for each Lambda event and
    /// the JSON serializer to use for converting Lambda JSON format to the .NET types. 
    /// </summary>
    private static async Task Main()
    {
        Func<string, ILambdaContext, string> handler = FunctionHandler;
        await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>())
            .Build()
            .RunAsync();
    }

    /// <summary>
    /// A simple function that takes a string and does a ToUpper.
    ///
    /// To use this handler to respond to an AWS event, reference the appropriate package from 
    /// https://github.com/aws/aws-lambda-dotnet#events
    /// and change the string input parameter to the desired event type. When the event type
    /// is changed, the handler type registered in the main method needs to be updated and the LambdaFunctionJsonSerializerContext 
    /// defined below will need the JsonSerializable updated. If the return type and event type are different then the 
    /// LambdaFunctionJsonSerializerContext must have two JsonSerializable attributes, one for each type.
    ///
    /// When using Native AOT, libraries used with your Lambda function might not be compatible with trimming that
    /// happens as part of the Native AOT compilation. If you find when testing your Native AOT Lambda function that 
    /// you get runtime errors about missing types, methods or constructors then add the assembly that contains the
    /// types into the rd.xml file. This will tell the Native AOT compiler to not trim those assemblies. Currently the 
    /// AWS SDK for .NET does not support trimming and when used should be added to the rd.xml file.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    public static string FunctionHandler(string input, ILambdaContext context)
    {
        return GetSecret().Result;
    }

    /*
 *	Use this code snippet in your app.
 *	If you need more information about configurations or implementing the sample code, visit the AWS docs:
 *	https://aws.amazon.com/developer/language/net/getting-started
 */


static async Task<string> GetSecret()
{
    string secretName = "MyTestSecret";
    string region = "us-east-1";

    IAmazonSecretsManager client = new AmazonSecretsManagerClient(RegionEndpoint.GetBySystemName(region));

    GetSecretValueRequest request = new GetSecretValueRequest
    {
        SecretId = secretName,
        VersionStage = "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified.
    };

    GetSecretValueResponse response;

    try
    {
        response = await client.GetSecretValueAsync(request);
    }
    catch (Exception e)
    {
        // For a list of the exceptions thrown, see
        // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        throw e;
    }

    return response.SecretString;


 
    // Your code goes here
}
}



/// <summary>
/// This class is used to register the input event and return type for the FunctionHandler method with the System.Text.Json source generator.
/// There must be a JsonSerializable attribute for each type used as the input and return type or a runtime error will occur 
/// from the JSON serializer unable to find the serialization information for unknown types.
/// </summary>
[JsonSerializable(typeof(string))]
public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
{
    // By using this partial class derived from JsonSerializerContext, we can generate reflection free JSON Serializer code at compile time
    // which can deserialize our class and properties. However, we must attribute this class to tell it what types to generate serialization code for.
    // See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-source-generation
}

Possible Solution

Attribute the NetworkReachability class (along with potentially others) with the DynamicallyAccessedMembers attribute.

Similar to how we fix a similar issue here: aws/aws-lambda-dotnet#1382

Or remove the dynamic runtime loading of classes by updating the logic.

A work around is to use <TrimMode>partial</TrimMode>, however this increase the binary size from 5Mb to 11Mb.

Additional Information/Context

Project file to reproduce:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AWSProjectType>Lambda</AWSProjectType>
    <AssemblyName>bootstrap</AssemblyName>
    <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <!-- Generate Native AOT image during publishing to improve cold start time. -->
    <PublishAot>true</PublishAot>
    <!-- StripSymbols tells the compiler to strip debugging symbols from the final executable if we're on Linux and put them into their own file. 
    This will greatly reduce the final executable's size.-->
    <StripSymbols>true</StripSymbols>
	  <!--<TrimMode>partial</TrimMode>-->
  </PropertyGroup>
  <!-- 
  When publishing Lambda functions for ARM64 to the provided.al2 runtime a newer version of libicu needs to be included
  in the deployment bundle because .NET requires a newer version of libicu then is preinstalled with Amazon Linux 2.
  -->
  <ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">
    <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" />
    <PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.8.2" />
    <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.0" />
    <PackageReference Include="AWSSDK.SecretsManager" Version="3.7.101.18" />
  </ItemGroup>
  
  <ItemGroup>
    <!--The runtime directives file allows the compiler to know what types and assemblies to not trim out of the final binary, even if they don't appear to be used.-->
    <RdXmlFile Include="rd.xml" />
  </ItemGroup>
</Project>

AWS .NET SDK and/or Package version used

.NET 7 with native AOT

<PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.8.2" />
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.0" />
<PackageReference Include="AWSSDK.SecretsManager" Version="3.7.101.18" />

Targeted .NET Platform

.NET 7

Operating System and version

Built on Docker on Windows 10 Host, Deployed to AL2

@Beau-Gosse-dev Beau-Gosse-dev added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Feb 2, 2023
@ashishdhingra
Copy link
Contributor

@Beau-Gosse-dev Thanks for opening the issue. Just wanted to check if you referred the blog post Building serverless .NET applications on AWS Lambda using .NET 7 for the Native AOT support. As mentioned, currently, the AWSSDK.Core library used by all the .NET AWS SDKs must also be excluded from trimming.

Thanks,
Ashish

@ashishdhingra ashishdhingra added response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. and removed needs-triage This issue or PR still needs to be triaged. labels Feb 3, 2023
@Beau-Gosse-dev
Copy link
Author

Beau-Gosse-dev commented Feb 3, 2023

Hi @ashishdhingra , thanks for checking! Yup, I actually helped James review that blog before publishing. I'm opening this and maybe more issue in the future so that once we can get around to fixing trimming in the SDK it will be easier to identify problem areas.

I also mentioned a work around in the Possible Solution section so that customer who find this through search know how to work around it for now. We talked to a customer who ran into this issue and got stuck, so I wanted to document it and show a workaround.

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Feb 4, 2023
@ashishdhingra ashishdhingra added p2 This is a standard priority issue queued and removed needs-review labels Feb 15, 2023
gord5500 added a commit to Brightspace/bmx that referenced this issue May 2, 2023
### Why

The published build of bmx right now will crash with the error `No
parameterless constructor defined for type
'Amazon.Util.Internal.PlatformServices.NetworkReachability'`


https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained

When building bmx with aot, we reduce the deployment size by getting rid
of unused parts of libraries. Theres a [problem
](aws/aws-sdk-net#2531) though with the aws
sdk right now where it gets rid of some parts we actually need. I think
it's to do with
[this](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/incompatibilities#dynamic-assembly-loading-and-execution)

### How

The github issue page mentioned a temporary solution to set the trim
mode to partial for now. The build size would be 17.5MB compared to 13
so it seems like not a big issue?
@ashishdhingra
Copy link
Contributor

@Beau-Gosse-dev Good afternoon. I was going through existing open issue and came across this one. The issue doesn't appear to be reproducible using the latest .NET 8 Native AOT blueprint and current latest version 3.7.400.45 of AWSSDK.SecretsManager package. Below is the code used:
.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AWSProjectType>Lambda</AWSProjectType>
    <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <!-- Generate Native AOT image during publishing to improve cold start time. -->
    <PublishAot>true</PublishAot>
    <!-- StripSymbols tells the compiler to strip debugging symbols from the final executable if we're on Linux and put them into their own file. 
    This will greatly reduce the final executable's size.-->
    <StripSymbols>true</StripSymbols>
    <!-- TrimMode partial will only trim assemblies marked as trimmable. To reduce package size make all assemblies trimmable and set TrimMode to full.
    If there are trim warnings during build, you can hit errors at runtime.-->
    <TrimMode>partial</TrimMode>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.11.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="2.3.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
    <PackageReference Include="AWSSDK.SecretsManager" Version="3.7.400.45" />
  </ItemGroup>
</Project

Function.cs

using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using System.Text.Json.Serialization;

using Amazon;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;

namespace NativeAOTTrimming_Issue2531;

public class Function
{
    /// <summary>
    /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase. It
    /// initializes the .NET Lambda runtime client passing in the function handler to invoke for each Lambda event and
    /// the JSON serializer to use for converting Lambda JSON format to the .NET types. 
    /// </summary>
    private static async Task Main()
    {
        Func<string, ILambdaContext, string> handler = FunctionHandler;
        await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>())
            .Build()
            .RunAsync();
    }

    /// <summary>
    /// A simple function that takes a string and does a ToUpper.
    ///
    /// To use this handler to respond to an AWS event, reference the appropriate package from 
    /// https://github.com/aws/aws-lambda-dotnet#events
    /// and change the string input parameter to the desired event type. When the event type
    /// is changed, the handler type registered in the main method needs to be updated and the LambdaFunctionJsonSerializerContext 
    /// defined below will need the JsonSerializable updated. If the return type and event type are different then the 
    /// LambdaFunctionJsonSerializerContext must have two JsonSerializable attributes, one for each type.
    ///
    // When using Native AOT extra testing with the deployed Lambda functions is required to ensure
    // the libraries used in the Lambda function work correctly with Native AOT. If a runtime 
    // error occurs about missing types or methods the most likely solution will be to remove references to trim-unsafe 
    // code or configure trimming options. This sample defaults to partial TrimMode because currently the AWS 
    // SDK for .NET does not support trimming. This will result in a larger executable size, and still does not 
    // guarantee runtime trimming errors won't be hit. 
    /// </summary>
    /// <param name="input">The event for the Lambda function handler to process.</param>
    /// <param name="context">The ILambdaContext that provides methods for logging and describing the Lambda environment.</param>
    /// <returns></returns>
    public static string FunctionHandler(string input, ILambdaContext context)
    {
        return GetSecret().Result;
    }

    static async Task<string> GetSecret()
    {
        string secretName = "MyTestSecret";
        string region = "us-east-2";

        IAmazonSecretsManager client = new AmazonSecretsManagerClient(RegionEndpoint.GetBySystemName(region));

        GetSecretValueRequest request = new GetSecretValueRequest
        {
            SecretId = secretName,
            VersionStage = "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified.
        };

        GetSecretValueResponse response;

        try
        {
            response = await client.GetSecretValueAsync(request);
        }
        catch (Exception e)
        {
            // For a list of the exceptions thrown, see
            // https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
            throw e;
        }

        return response.SecretString;



        // Your code goes here
    }

}

/// <summary>
/// This class is used to register the input event and return type for the FunctionHandler method with the System.Text.Json source generator.
/// There must be a JsonSerializable attribute for each type used as the input and return type or a runtime error will occur 
/// from the JSON serializer unable to find the serialization information for unknown types.
/// </summary>
[JsonSerializable(typeof(string))]
public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
{
    // By using this partial class derived from JsonSerializerContext, we can generate reflection free JSON Serializer code at compile time
    // which can deserialize our class and properties. However, we must attribute this class to tell it what types to generate serialization code for.
    // See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-source-generation
}

Please verify if the issue fixed at your end and confirm if this could be closed.

Thanks,
Ashish

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Nov 4, 2024
@normj
Copy link
Member

normj commented Nov 5, 2024

@ashishdhingra I'm closing this because I know this has been addressed and I know @Beau-Gosse-dev has build and deploy many Native AOT functions since we have addressed the issue.

@normj normj closed this as completed Nov 5, 2024
Copy link

github-actions bot commented Nov 5, 2024

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. module/sdk-generated p2 This is a standard priority issue queued response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.
Projects
None yet
Development

No branches or pull requests

3 participants