aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

Annotate SignalR server for native AOT

Open eerhardt opened this issue 1 year ago • 1 comments

Fix SignalR Server's usage of MakeGenericMethod when using a streaming reader (IAsyncEnumerable or ChannelReader) following the same approach as the client. Add a runtime check and throw an exception when trying to stream a ValueType in native AOT.

Adjust the public annotations:

  • Remove RequiresUnreferencedCode from AddSignalR
  • Add RequiresUnreferencedCode to MessagePack and NewtonsoftJson protocols

Support trimming and AOT in DefaultHubDispatcher by adding a feature switch to turn off custom awaitable support. Update ObjectMethodExecutor to support trimming and AOT by a new method that doesn't look for custom awaitables, and uses reflection instead of Linq.Expressions. This will need a corresponding PR in https://github.com/dotnet/sdk to default the feature switch value when PublishTrimmed or PublishAot is set to true.

FYI - @agocke @MichalStrehovsky @sbomer - in case you want to take a look.

eerhardt avatar Jun 25 '24 23:06 eerhardt

Do you have a size comparison?

Taking a dotnet new webapiaot project and publishing it for win-x64 on my machine, the resulting .exe is 8.73 MB (9,163,776 bytes).

Then adding the following to the app:

Program.cs

var builder = WebApplication.CreateSlimBuilder(args);
...
+builder.Services.AddSignalR();
+builder.Services.Configure<JsonHubProtocolOptions>(o =>
+{
+    o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
+});

var app = builder.Build();
...

+app.MapHub<ChatHub>("/chathub");

app.Run();

ChatHub.cs

using Microsoft.AspNetCore.SignalR;

public class ChatHub(ILogger<ChatHub> logger) : Hub
{
    public async Task SendMessage(string user, string message)
    {
        logger.LogInformation("Received message from {user}: {message}", user, message);

        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Publishing again, the resulting .exe is 10.9 MB (11,445,760 bytes).

So roughly 2.2 MB of additional size to add SignalR server. Doing a really quick sizoscope on the before and after, here are my thoughts on what could be eliminated if we wanted:

  1. System.Text.RegularExpressions is getting pulled in, which is adding a lot of the size we cut out of the "Slim" builder. The reason it gets pulled in is because this calls AddRouting and not AddRoutingCore. https://github.com/dotnet/aspnetcore/blob/0da8ea72b5434cbe8e1207d802f2270ca2f8ad4c/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs#L21-L23

  2. Super minor would be to remove the Linq usage in https://github.com/dotnet/aspnetcore/blob/0da8ea72b5434cbe8e1207d802f2270ca2f8ad4c/src/SignalR/server/Core/src/Internal/HubReflectionHelper.cs#L13 and https://github.com/dotnet/aspnetcore/blob/0da8ea72b5434cbe8e1207d802f2270ca2f8ad4c/src/SignalR/server/Core/src/Internal/HubMethodDescriptor.cs#L63

I think we are in a good spot right now though. I opened an issue for the Regex change, since that would have the biggest benefit.

eerhardt avatar Jul 01 '24 21:07 eerhardt

I was more asking how much this PR saves vs. not trimming SignalR.

BrennanConroy avatar Jul 01 '24 22:07 BrennanConroy

I was more asking how much this PR saves vs. not trimming SignalR.

If I take the same project as above and make the following changes to the .csproj:

    <!-- <PublishAot>true</PublishAot> -->
    <PublishSingleFile>true</PublishSingleFile>

The resulting .exe is 90.1 MB.

eerhardt avatar Jul 01 '24 22:07 eerhardt