diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/BroadcastBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/BroadcastBenchmark.cs new file mode 100644 index 0000000000..2e0596cae2 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/BroadcastBenchmark.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Channels; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.SignalR.Internal.Protocol; +using Microsoft.AspNetCore.Sockets; +using Microsoft.AspNetCore.Sockets.Internal; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + [ParameterizedJobConfig(typeof(CoreConfig))] + public class BroadcastBenchmark + { + private DefaultHubLifetimeManager _hubLifetimeManager; + private HubContext _hubContext; + + [Params(1, 10, 1000)] + public int Connections; + + [GlobalSetup] + public void GlobalSetup() + { + _hubLifetimeManager = new DefaultHubLifetimeManager(); + var options = new UnboundedChannelOptions { AllowSynchronousContinuations = true }; + + for (var i = 0; i < Connections; ++i) + { + var transportToApplication = Channel.CreateUnbounded(options); + var applicationToTransport = Channel.CreateUnbounded(options); + + var application = ChannelConnection.Create(input: applicationToTransport, output: transportToApplication); + var transport = ChannelConnection.Create(input: transportToApplication, output: applicationToTransport); + var connection = new DefaultConnectionContext(Guid.NewGuid().ToString(), transport, application); + + _hubLifetimeManager.OnConnectedAsync(new HubConnectionContext(Channel.CreateUnbounded(), connection)).Wait(); + } + + _hubContext = new HubContext(_hubLifetimeManager); + } + + [Benchmark] + public Task InvokeAsyncAll() + { + return _hubContext.All.InvokeAsync("Method"); + } + } +} diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs index 9cbaa000c2..e9d55de02e 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/CoreConfig.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Validators; @@ -13,17 +9,23 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { public class CoreConfig : ManualConfig { - public CoreConfig() + public CoreConfig() : this(Job.Core) { + // Here because build.cmd calls the other constructor + // and this setting will complain about non-release builds Add(JitOptimizationsValidator.FailOnError); + } + + public CoreConfig(Job job) + { + Add(DefaultConfig.Instance); Add(MemoryDiagnoser.Default); Add(StatisticColumn.OperationsPerSecond); - Add(Job.Default - .With(BenchmarkDotNet.Environments.Runtime.Core) + Add(job + .With(RunStrategy.Throughput) .WithRemoveOutliers(false) .With(new GcMode() { Server = true }) - .With(RunStrategy.Throughput) .WithLaunchCount(3) .WithWarmupCount(5) .WithTargetCount(10)); diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs new file mode 100644 index 0000000000..63dd977f70 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubProtocolBenchmark.cs @@ -0,0 +1,93 @@ +using System; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.SignalR.Internal.Encoders; +using Microsoft.AspNetCore.SignalR.Internal.Protocol; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + [ParameterizedJobConfig(typeof(CoreConfig))] + public class HubProtocolBenchmark + { + private HubProtocolReaderWriter _hubProtocolReaderWriter; + private byte[] _binaryInput; + private TestBinder _binder; + private HubMessage _hubMessage; + + [Params(Message.NoArguments, Message.FewArguments, Message.ManyArguments, Message.LargeArguments)] + public Message Input { get; set; } + + [Params(Protocol.MsgPack, Protocol.Json)] + public Protocol HubProtocol { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + switch (HubProtocol) + { + case Protocol.MsgPack: + _hubProtocolReaderWriter = new HubProtocolReaderWriter(new MessagePackHubProtocol(), new PassThroughEncoder()); + break; + case Protocol.Json: + _hubProtocolReaderWriter = new HubProtocolReaderWriter(new JsonHubProtocol(), new PassThroughEncoder()); + break; + } + + switch (Input) + { + case Message.NoArguments: + _hubMessage = new InvocationMessage("123", true, "Target", null); + break; + case Message.FewArguments: + _hubMessage = new InvocationMessage("123", true, "Target", null, 1, "Foo", 2.0f); + break; + case Message.ManyArguments: + _hubMessage = new InvocationMessage("123", true, "Target", null, 1, "string", 2.0f, true, (byte)9, new byte[] { 5, 4, 3, 2, 1 }, 'c', 123456789101112L); + break; + case Message.LargeArguments: + _hubMessage = new InvocationMessage("123", true, "Target", null, new string('F', 10240), new byte[10240]); + break; + } + + _binaryInput = GetBytes(_hubMessage); + _binder = new TestBinder(_hubMessage); + } + + [Benchmark] + public void ReadSingleMessage() + { + if (!_hubProtocolReaderWriter.ReadMessages(_binaryInput, _binder, out var _)) + { + throw new InvalidOperationException("Failed to read message"); + } + } + + [Benchmark] + public void WriteSingleMessage() + { + if (_hubProtocolReaderWriter.WriteMessage(_hubMessage).Length != _binaryInput.Length) + { + throw new InvalidOperationException("Failed to write message"); + } + } + + public enum Protocol + { + MsgPack = 0, + Json = 1 + } + + public enum Message + { + NoArguments = 0, + FewArguments = 1, + ManyArguments = 2, + LargeArguments = 3 + } + + private byte[] GetBytes(HubMessage hubMessage) + { + return _hubProtocolReaderWriter.WriteMessage(_hubMessage); + } + } +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs index e608892e65..babafe1717 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.SignalR.Internal.Formatters; namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { - [Config(typeof(CoreConfig))] + [ParameterizedJobConfig(typeof(CoreConfig))] public class MessageParserBenchmark { private static readonly Random Random = new Random(); diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj index b9ad8ab3b8..3706e8d2eb 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -2,12 +2,16 @@ Exe - netcoreapp2.0;net461 + netcoreapp2.0 + + + + diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs deleted file mode 100644 index 69d514a523..0000000000 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Reflection; -using BenchmarkDotNet.Running; - -namespace Microsoft.AspNetCore.SignalR.Microbenchmarks -{ - class Program - { - static void Main(string[] args) - { - BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); - } - } -} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/TestBinder.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/TestBinder.cs new file mode 100644 index 0000000000..ca6241a40c --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/TestBinder.cs @@ -0,0 +1,62 @@ +// 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.Linq; +using Microsoft.AspNetCore.SignalR.Internal; +using Microsoft.AspNetCore.SignalR.Internal.Protocol; + +namespace Microsoft.AspNetCore.SignalR.Microbenchmarks +{ + public class TestBinder : IInvocationBinder + { + private readonly Type[] _paramTypes; + private readonly Type _returnType; + + public TestBinder(HubMessage expectedMessage) + { + switch (expectedMessage) + { + case StreamInvocationMessage i: + _paramTypes = i.Arguments?.Select(a => a?.GetType() ?? typeof(object))?.ToArray(); + break; + case InvocationMessage i: + _paramTypes = i.Arguments?.Select(a => a?.GetType() ?? typeof(object))?.ToArray(); + break; + case StreamItemMessage s: + _returnType = s.Item?.GetType() ?? typeof(object); + break; + case CompletionMessage c: + _returnType = c.Result?.GetType() ?? typeof(object); + break; + } + } + + public TestBinder() : this(null, null) { } + public TestBinder(Type[] paramTypes) : this(paramTypes, null) { } + public TestBinder(Type returnType) : this(null, returnType) { } + public TestBinder(Type[] paramTypes, Type returnType) + { + _paramTypes = paramTypes; + _returnType = returnType; + } + + public Type[] GetParameterTypes(string methodName) + { + if (_paramTypes != null) + { + return _paramTypes; + } + throw new InvalidOperationException("Unexpected binder call"); + } + + public Type GetReturnType(string invocationId) + { + if (_returnType != null) + { + return _returnType; + } + throw new InvalidOperationException("Unexpected binder call"); + } + } +} \ No newline at end of file diff --git a/build/dependencies.props b/build/dependencies.props index 6f5e1830a5..1d1cdeab74 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -4,6 +4,7 @@ 0.10.9 + 2.1.0-preview1-27579 2.4.337 3.1.0 2.1.0-preview1-15549 diff --git a/build/repo.props b/build/repo.props index 53eb29bb1d..e9bb4e0d84 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,4 +1,7 @@  + + true + diff --git a/samples/SocketsSample/wwwroot/hubs.html b/samples/SocketsSample/wwwroot/hubs.html index 0efc8924f6..ce3678b319 100644 --- a/samples/SocketsSample/wwwroot/hubs.html +++ b/samples/SocketsSample/wwwroot/hubs.html @@ -104,7 +104,7 @@ click('connect', function(event) { connectButton.disabled = true; disconnectButton.disabled = false; console.log('http://' + document.location.host + '/' + hubRoute); - connection = new signalR.HubConnection(hubRoute, logger, { transport: transportType, logger: logger }); + connection = new signalR.HubConnection(hubRoute, { transport: transportType, logging: logger }); connection.on('Send', function(msg) { addLine('message-list', msg); }); diff --git a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs index 47bfd7e7e1..dbaab03e65 100644 --- a/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs @@ -69,7 +69,13 @@ namespace Microsoft.AspNetCore.SignalR private Task InvokeAllWhere(string methodName, object[] args, Func include) { - var tasks = new List(_connections.Count); + var count = _connections.Count; + if (count == 0) + { + return Task.CompletedTask; + } + + var tasks = new List(count); var message = CreateInvocationMessage(methodName, args); // TODO: serialize once per format by providing a different stream? diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs index 0d7e47c2dd..6edf5c7eb7 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new object[] { new[] { new StreamInvocationMessage("xyz", "method", null, new[] { new CustomObject(), new CustomObject() }) } }, new object[] { new[] { new CancelInvocationMessage("xyz") } }, - + new object[] { new[] { PingMessage.Instance } }, new object[]