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

[NativeAot] List<T> in options pattern not work in nativeaot mode #74436

Closed
loyldg opened this issue Aug 23, 2022 · 11 comments
Closed

[NativeAot] List<T> in options pattern not work in nativeaot mode #74436

loyldg opened this issue Aug 23, 2022 · 11 comments

Comments

@loyldg
Copy link

loyldg commented Aug 23, 2022

Description

If the option class have List<T> property,I can not get the value of the List<T> property from IOptions<TOption>

Reproduction Steps

Use powershell to execute the following command
> dotnet new console -o OptionsPatternTestApp
> cd OptionsPatternTestApp
> dotnet add package Microsoft.Extensions.Hosting --version 6.0.1
> dotnet add package Microsoft.DotNet.ILCompiler --version 7.0.0-preview.7.22375.6

Replace Program.cs with the following code:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices((context, services) =>
{
    services.Configure<TestOption>(context.Configuration.GetRequiredSection("App"));
});
builder.ConfigureAppConfiguration(configuration =>
{
    configuration.AddCommandLine(args);
});

var app = builder.Build();

var option = app.Services.GetRequiredService<IOptions<TestOption>>();
Console.WriteLine($"Items.Count:{option.Value.Items.Count}");

await app.RunAsync();

public class TestOption
{
    public List<TestItem> Items { get; set; }
}

public class TestItem
{
    public int Id { get; set; }
}

> dotnet publish -c Release -r win-x64
> ./bin/Release/net6.0/win-x64/native/OptionsPatternTestApp.exe App__Items__0__Id=1
The console will output Items.Count:0

Expected behavior

The console output Items.Count:1

Actual behavior

The console output Items.Count:0

Regression?

If I change the the ilcompiler version to 7.0.0-preview.5.22301.12,it works.

Known Workarounds

No response

Configuration

No response

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 23, 2022
@ghost
Copy link

ghost commented Aug 23, 2022

Tagging subscribers to this area: @dotnet/area-extensions-options
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

If the option class have List<T> property,I can not get the value of the List<T> property from IOptions<TOption>

Reproduction Steps

Use powershell to execute the following command
> dotnet new console -o OptionsPatternTestApp
> cd OptionsPatternTestApp
> dotnet add package Microsoft.Extensions.Hosting --version 6.0.1
> dotnet add package Microsoft.DotNet.ILCompiler --version 7.0.0-preview.7.22375.6

Replace Program.cs with the following code:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices((context, services) =>
{
    services.Configure<TestOption>(context.Configuration.GetRequiredSection("App"));
});
builder.ConfigureAppConfiguration(configuration =>
{
    configuration.AddCommandLine(args);
});

var app = builder.Build();

var option = app.Services.GetRequiredService<IOptions<TestOption>>();
Console.WriteLine($"Items.Count:{option.Value.Items.Count}");

await app.RunAsync();

public class TestOption
{
    public List<TestItem> Items { get; set; }
}

public class TestItem
{
    public int Id { get; set; }
}

> dotnet publish -c Release -r win-x64
> ./bin/Release/net6.0/win-x64/native/OptionsPatternTestApp.exe App__Items__0__Id=1
The console will output Items.Count:0

Expected behavior

The console output Items.Count:1

Actual behavior

The console output Items.Count:0

Regression?

If I change the the ilcompiler version to 7.0.0-preview.5.22301.12,it works.

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: loyldg
Assignees: -
Labels:

area-Extensions-Options

Milestone: -

@eerhardt
Copy link
Member

Note that in 7.0 you will get a warning that this line

services.Configure<TestOption>(context.Configuration.GetRequiredSection("App"));

Is not supported in NativeAOT. Note the RequiresDynamicCode attribute on:

[RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config);

Regression?
If I change the the ilcompiler version to 7.0.0-preview.5.22301.12,it works.

Moving to the area-NativeAOT-coreclr to investigate the regression.

@MichalStrehovsky
Copy link
Member

I doubt this is AOT issue. It's probably a trimming issue. AOT issue pretty much always have a runtime exception unless there's code somewhere that catches and swallows it. Trimming issues typically have this failure mode where something is just missing without an exception.

If I run the repro, I'm getting Unhandled exception. System.InvalidOperationException: Section 'App' not found in configuration.. Is this missing some repro steps?

I do see a build time warning saying "TOptions's dependent types may have their members trimmed." which would explain why TestOption's dependent type TestItem has no members.

@eerhardt
Copy link
Member

It's probably a trimming issue.

Ah that's probably it. We are trimming the parameterless ctor on TestItem now. I can repro the same issue with just -p:PublishTrimmed=true.

An exception is getting thrown:

System.InvalidOperationException
  HResult=0x80131509
  Message=Cannot create instance of type 'TestItem' because it is missing a public parameterless constructor.
  Source=Microsoft.Extensions.Configuration.Binder
  StackTrace:
   at Microsoft.Extensions.Configuration.ConfigurationBinder.CreateInstance(Type )

But that is getting swallowed here:

try
{
BindingPoint itemBindingPoint = new();
BindInstance(
type: itemType,
bindingPoint: itemBindingPoint,
config: section,
options: options);
if (itemBindingPoint.HasNewValue)
{
addMethod?.Invoke(collection, new[] { itemBindingPoint.Value });
}
}
catch
{
}

I believe we can close this as "by design". You are getting a trimming warning. Using Configuration Binder in a trimmed app today requires you manually preserve the necessary members, or don't use it at all. There isn't anything we can do to make this "safe" short of dotnet/linker#1087 or #44493.

@eerhardt
Copy link
Member

If I run the repro, I'm getting Unhandled exception. System.InvalidOperationException: Section 'App' not found in configuration.. Is this missing some repro steps?

I was able to get the app to run with passing App:Items:0:Id=1 as the command line to the app.

@loyldg
Copy link
Author

loyldg commented Aug 24, 2022

I doubt this is AOT issue. It's probably a trimming issue. AOT issue pretty much always have a runtime exception unless there's code somewhere that catches and swallows it. Trimming issues typically have this failure mode where something is just missing without an exception.

If I run the repro, I'm getting Unhandled exception. System.InvalidOperationException: Section 'App' not found in configuration.. Is this missing some repro steps?

I do see a build time warning saying "TOptions's dependent types may have their members trimmed." which would explain why TestOption's dependent type TestItem has no members.

Sorry,the command ./bin/Release/net6.0/win-x64/native/OptionsPatternTestApp.exe App__Items__0__Id=1 may not work.
You can run the app use this command ./bin/Release/net6.0/win-x64/native/OptionsPatternTestApp.exe App:Items:0:Id=1 or add appsettings.json use the following data

{
  "App": {
    "Items": [
      {
        "Id": 1
      }
    ]
  }
}

If I add another a TestItem property to TestOption ,I can get the value of the TestItem Property,but can't get the List<TestItem>value.
If it's a trimming issue,why does it only trimming List<TestItem>,but not trimming TestItem

You can test it use this code:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices((context, services) =>
{
    services.Configure<TestOption>(context.Configuration.GetRequiredSection("App"));
});
builder.ConfigureAppConfiguration(configuration =>
{
    configuration.AddCommandLine(args);
});

var app = builder.Build();
var option = app.Services.GetRequiredService<IOptions<TestOption>>();
Console.WriteLine($"Items.Count:{option.Value.Items.Count}");
Console.WriteLine($"Item.Id:{option.Value.Item.Id}");

await app.RunAsync();

public class TestOption
{
    public TestItem Item { get; set; }
    public List<TestItem> Items { get; set; }
}

public class TestItem
{
    public int Id { get; set; }
}

rd.xml

<Directives>
    <Application>
        <Assembly Name="OptionsPatternTestApp" Serialize="Required All" Dynamic="Required All">
            <Type Name="TestOption" Serialize="Required All" Dynamic="Required All" />
            <Type Name="TestItem" Serialize="Required All" Dynamic="Required All" />
        </Assembly>
    </Application>
</Directives>

OptionsPatternTestApp.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-preview.7.22375.6" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
  </ItemGroup>

  <ItemGroup>
    <RdXmlFile Include="rd.xml" />
  </ItemGroup>

</Project>

Run app use this command: ./bin/Release/net6.0/win-x64/native/OptionsPatternTestApp.exe App:Items:0:Id=1 App:Item:Id=2

@eerhardt
Copy link
Member

Use this code instead of an rd.xml:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;

class Program
{
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TestOption))] // use this
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TestItem))] // use this
    public static async Task Main(string[] args)
    {
        var builder = Host.CreateDefaultBuilder(args);
        builder.ConfigureServices((context, services) =>
        {
            services.Configure<TestOption>(context.Configuration.GetRequiredSection("App"));
        });
        builder.ConfigureAppConfiguration(configuration =>
        {
            configuration.AddCommandLine(args);
        });

        var app = builder.Build();
        var option = app.Services.GetRequiredService<IOptions<TestOption>>();
        Console.WriteLine($"Items.Count:{option.Value.Items.Count}");
        Console.WriteLine($"Item.Id:{option.Value.Item.Id}");

        await app.RunAsync();
    }
}

public class TestOption
{
    public TestItem Item { get; set; }
    public List<TestItem> Items { get; set; }
}

public class TestItem
{
    public int Id { get; set; }
}

Running that with PublishTrimmed=true works fine.

@eerhardt
Copy link
Member

However, running that same app with PublishAot=true doesn't work correctly:

PublishTrimmed=true:

Items.Count:1
Item.Id:2

PublishAot=true:

Items.Count:0
Item.Id:2

@loyldg
Copy link
Author

loyldg commented Aug 24, 2022

Add [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(List<TestItem>))] ,all will work

@loyldg
Copy link
Author

loyldg commented Aug 24, 2022

Thank you for your help,this is a trimming issue and can be fixed by adding DynamicDependencyAttribute

@loyldg loyldg closed this as completed Aug 24, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Aug 24, 2022
@MichalStrehovsky
Copy link
Member

However, running that same app with PublishAot=true doesn't work correctly:

We're likely not keeping the List<> constructor (or Add methods) because it's not a visible target of reflection. This would work with PublishTrimmed by accident because every app uses a List<> (but not actually reflecting on it). With IL Linker, there's very little distinction between "used" and "used with reflection" right now. There could be a bigger distinction in the future. It's one of those places that are in the1 "if there's a trimming warning, the app may or may not work" category.

@ghost ghost locked as resolved and limited conversation to collaborators Sep 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants