// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Buffers; using System.IO; using System.IO.Pipelines; using System.Reactive.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { public class DefaultHubDispatcherBenchmark { private DefaultHubDispatcher _dispatcher; private HubConnectionContext _connectionContext; [GlobalSetup] public void GlobalSetup() { var serviceCollection = new ServiceCollection(); serviceCollection.AddSignalRCore(); var provider = serviceCollection.BuildServiceProvider(); var serviceScopeFactory = provider.GetService(); _dispatcher = new DefaultHubDispatcher( serviceScopeFactory, new HubContext(new DefaultHubLifetimeManager(NullLogger>.Instance)), Options.Create(new HubOptions()), Options.Create(new HubOptions()), new Logger>(NullLoggerFactory.Instance)); var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default); var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), pair.Application, pair.Transport); _connectionContext = new NoErrorHubConnectionContext(connection, TimeSpan.Zero, NullLoggerFactory.Instance); _connectionContext.Protocol = new FakeHubProtocol(); } public class FakeHubProtocol : IHubProtocol { public string Name { get; } public int Version => 1; public int MinorVersion => 0; public TransferFormat TransferFormat { get; } public bool IsVersionSupported(int version) { return true; } public bool TryParseMessage(ref ReadOnlySequence input, IInvocationBinder binder, out HubMessage message) { message = null; return false; } public void WriteMessage(HubMessage message, IBufferWriter output) { } public ReadOnlyMemory GetMessageBytes(HubMessage message) { return HubProtocolExtensions.GetMessageBytes(this, message); } } public class NoErrorHubConnectionContext : HubConnectionContext { public NoErrorHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) : base(connectionContext, keepAliveInterval, loggerFactory) { } public override ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken) { if (message is CompletionMessage completionMessage) { if (!string.IsNullOrEmpty(completionMessage.Error)) { throw new Exception("Error invoking hub method: " + completionMessage.Error); } } return default; } } public class TestHub : Hub { public void Invocation() { } public Task InvocationAsync() { return Task.CompletedTask; } public int InvocationReturnValue() { return 1; } public Task InvocationReturnAsync() { return Task.FromResult(1); } public ValueTask InvocationValueTaskAsync() { return new ValueTask(1); } public ChannelReader StreamChannelReader() { var channel = Channel.CreateUnbounded(); channel.Writer.Complete(); return channel; } public Task> StreamChannelReaderAsync() { var channel = Channel.CreateUnbounded(); channel.Writer.Complete(); return Task.FromResult>(channel); } public ValueTask> StreamChannelReaderValueTaskAsync() { var channel = Channel.CreateUnbounded(); channel.Writer.Complete(); return new ValueTask>(channel); } public ChannelReader StreamChannelReaderCount(int count) { var channel = Channel.CreateUnbounded(); _ = Task.Run(async () => { for (var i = 0; i < count; i++) { await channel.Writer.WriteAsync(i); } channel.Writer.Complete(); }); return channel.Reader; } } [Benchmark] public Task Invocation() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "Invocation", Array.Empty())); } [Benchmark] public Task InvocationAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationAsync", Array.Empty())); } [Benchmark] public Task InvocationReturnValue() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnValue", Array.Empty())); } [Benchmark] public Task InvocationReturnAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnAsync", Array.Empty())); } [Benchmark] public Task InvocationValueTaskAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationValueTaskAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReader() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReader", Array.Empty())); } [Benchmark] public Task StreamChannelReaderAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReaderValueTaskAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderValueTaskAsync", Array.Empty())); } [Benchmark] public Task StreamChannelReaderCount_Zero() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 0 })); } [Benchmark] public Task StreamChannelReaderCount_One() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 1 })); } [Benchmark] public Task StreamChannelReaderCount_Thousand() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderCount", new object[] { 1000 })); } } }