// 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.Collections.Generic; using System.IO; using System.IO.Pipelines; using System.Reactive.Linq; using System.Threading.Channels; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; 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)), 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 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, Stream output) { } } public class NoErrorHubConnectionContext : HubConnectionContext { public NoErrorHubConnectionContext(ConnectionContext connectionContext, TimeSpan keepAliveInterval, ILoggerFactory loggerFactory) : base(connectionContext, keepAliveInterval, loggerFactory) { } public override ValueTask WriteAsync(HubMessage message) { 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 { private static readonly IObservable ObservableInstance = Observable.Empty(); 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 IObservable StreamObservable() { return ObservableInstance; } public Task> StreamObservableAsync() { return Task.FromResult(ObservableInstance); } public ValueTask> StreamObservableValueTaskAsync() { return new ValueTask>(ObservableInstance); } 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); } } [Benchmark] public Task Invocation() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "Invocation", null)); } [Benchmark] public Task InvocationAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationAsync", null)); } [Benchmark] public Task InvocationReturnValue() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnValue", null)); } [Benchmark] public Task InvocationReturnAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationReturnAsync", null)); } [Benchmark] public Task InvocationValueTaskAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new InvocationMessage("123", "InvocationValueTaskAsync", null)); } [Benchmark] public Task StreamObservable() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamObservable", null)); } [Benchmark] public Task StreamObservableAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamObservableAsync", null)); } [Benchmark] public Task StreamObservableValueTaskAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamObservableValueTaskAsync", null)); } [Benchmark] public Task StreamChannelReader() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReader", null)); } [Benchmark] public Task StreamChannelReaderAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderAsync", null)); } [Benchmark] public Task StreamChannelReaderValueTaskAsync() { return _dispatcher.DispatchMessageAsync(_connectionContext, new StreamInvocationMessage("123", "StreamChannelReaderValueTaskAsync", null)); } } }