diff --git a/src/Microsoft.AspNet.SignalR.Core/AuthorizeAttribute.cs b/src/Microsoft.AspNet.SignalR.Core/AuthorizeAttribute.cs index 82ef77896..05caef0a0 100644 --- a/src/Microsoft.AspNet.SignalR.Core/AuthorizeAttribute.cs +++ b/src/Microsoft.AspNet.SignalR.Core/AuthorizeAttribute.cs @@ -122,7 +122,7 @@ namespace Microsoft.AspNet.SignalR { if (user == null) { - throw new ArgumentNullException("user"); + return false; } if (!user.Identity.IsAuthenticated) diff --git a/src/Microsoft.AspNet.SignalR.Core/Hosting/HostDependencyResolverExtensions.cs b/src/Microsoft.AspNet.SignalR.Core/Hosting/HostDependencyResolverExtensions.cs index ce4f239f1..c674b317b 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Hosting/HostDependencyResolverExtensions.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Hosting/HostDependencyResolverExtensions.cs @@ -20,8 +20,12 @@ namespace Microsoft.AspNet.SignalR.Hosting throw new ArgumentNullException("instanceName"); } - // Initialize the performance counters - resolver.InitializePerformanceCounters(instanceName, hostShutdownToken); + // Performance counters are broken on mono so just skip this step + if (!MonoUtility.IsRunningMono) + { + // Initialize the performance counters + resolver.InitializePerformanceCounters(instanceName, hostShutdownToken); + } // Dispose the dependency resolver on host shut down (cleanly) resolver.InitializeResolverDispose(hostShutdownToken); @@ -41,12 +45,11 @@ namespace Microsoft.AspNet.SignalR.Hosting // TODO: Guard against multiple calls to this // When the host triggers the shutdown token, dispose the resolver - hostShutdownToken.Register(state => + hostShutdownToken.SafeRegister(state => { ((IDependencyResolver)state).Dispose(); }, - resolver, - useSynchronizationContext: false); + resolver); } } } diff --git a/src/Microsoft.AspNet.SignalR.Core/Hosting/IResponse.cs b/src/Microsoft.AspNet.SignalR.Core/Hosting/IResponse.cs index f6dd60434..3d26f0e61 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Hosting/IResponse.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Hosting/IResponse.cs @@ -17,6 +17,11 @@ namespace Microsoft.AspNet.SignalR.Hosting /// CancellationToken CancellationToken { get; } + /// + /// Gets or sets the status code of the response. + /// + int StatusCode { get; set; } + /// /// Gets or sets the content type of the response. /// diff --git a/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocket.cs b/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocket.cs index 8db4362cc..60c448f2a 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocket.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocket.cs @@ -16,9 +16,9 @@ namespace Microsoft.AspNet.SignalR.Hosting Action OnMessage { get; set; } /// - /// Invoked when the websocket gracefully closes + /// Invoked when the websocket closes /// - Action OnClose { get; set; } + Action OnClose { get; set; } /// /// Invoked when there is an error diff --git a/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocketRequest.cs b/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocketRequest.cs index ce7109af0..95f5438f2 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocketRequest.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Hosting/IWebSocketRequest.cs @@ -11,6 +11,7 @@ namespace Microsoft.AspNet.SignalR.Hosting /// Accepts an websocket request using the specified user function. /// /// The callback that fires when the websocket is ready. - Task AcceptWebSocketRequest(Func callback); + /// The task that completes when the websocket transport is ready. + Task AcceptWebSocketRequest(Func callback, Task initTask); } } diff --git a/src/Microsoft.AspNet.SignalR.Core/Hubs/HubDispatcher.cs b/src/Microsoft.AspNet.SignalR.Core/Hubs/HubDispatcher.cs index 8dc80894c..b97aa74c5 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Hubs/HubDispatcher.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Hubs/HubDispatcher.cs @@ -358,7 +358,7 @@ namespace Microsoft.AspNet.SignalR.Hubs private Task ExecuteHubEvent(IRequest request, string connectionId, Func action) { var hubs = GetHubs(request, connectionId).ToList(); - var operations = hubs.Select(instance => action(instance).Catch().OrEmpty()).ToArray(); + var operations = hubs.Select(instance => action(instance).OrEmpty().Catch()).ToArray(); if (operations.Length == 0) { diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BinaryTextWriter.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BinaryTextWriter.cs new file mode 100644 index 000000000..a5f958b47 --- /dev/null +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BinaryTextWriter.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.AspNet.SignalR.Hosting; + +namespace Microsoft.AspNet.SignalR.Infrastructure +{ + /// + /// A buffering text writer that supports writing binary directly as well + /// + internal unsafe class BinaryTextWriter : BufferTextWriter, IBinaryWriter + { + public BinaryTextWriter(IResponse response) : + base((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128) + { + + } + + public BinaryTextWriter(IWebSocket socket) : + base((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024) + { + + } + + + public BinaryTextWriter(Action, object> write, object state, bool reuseBuffers, int bufferSize) : + base(write, state, reuseBuffers, bufferSize) + { + } + + public void Write(ArraySegment data) + { + Writer.Write(data); + } + } +} diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BufferTextWriter.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BufferTextWriter.cs index 2cf39d536..6334f3533 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BufferTextWriter.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/BufferTextWriter.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure /// we don't need to write to a long lived buffer. This saves massive amounts of memory /// as the number of connections grows. /// - internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter + internal abstract unsafe class BufferTextWriter : TextWriter { private readonly Encoding _encoding; @@ -31,13 +31,13 @@ namespace Microsoft.AspNet.SignalR.Infrastructure } public BufferTextWriter(IWebSocket socket) : - this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 128) + this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024 * 4) { } [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")] - public BufferTextWriter(Action, object> write, object state, bool reuseBuffers, int bufferSize) + protected BufferTextWriter(Action, object> write, object state, bool reuseBuffers, int bufferSize) { _write = write; _writeState = state; @@ -46,7 +46,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure _bufferSize = bufferSize; } - private ChunkedWriter Writer + protected internal ChunkedWriter Writer { get { @@ -79,17 +79,12 @@ namespace Microsoft.AspNet.SignalR.Infrastructure Writer.Write(value); } - public void Write(ArraySegment data) - { - Writer.Write(data); - } - public override void Flush() { Writer.Flush(); } - private class ChunkedWriter + internal class ChunkedWriter { private int _charPos; private int _charLen; diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/CancellationTokenExtensions.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/CancellationTokenExtensions.cs index 71736b3df..6dec33dac 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/CancellationTokenExtensions.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/CancellationTokenExtensions.cs @@ -1,20 +1,25 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; using System.Threading; namespace Microsoft.AspNet.SignalR.Infrastructure { internal static class CancellationTokenExtensions { + private delegate CancellationTokenRegistration RegisterDelegate(ref CancellationToken token, Action callback, object state); + + private static readonly RegisterDelegate _tokenRegister = ResolveRegisterDelegate(); + public static IDisposable SafeRegister(this CancellationToken cancellationToken, Action callback, object state) { var callbackWrapper = new CancellationCallbackWrapper(callback, state); // Ensure delegate continues to use the C# Compiler static delegate caching optimization. - CancellationTokenRegistration registration = cancellationToken.Register(s => Cancel(s), - callbackWrapper, - useSynchronizationContext: false); + CancellationTokenRegistration registration = _tokenRegister(ref cancellationToken, s => InvokeCallback(s), callbackWrapper); var disposeCancellationState = new DiposeCancellationState(callbackWrapper, registration); @@ -22,7 +27,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure return new DisposableAction(s => Dispose(s), disposeCancellationState); } - private static void Cancel(object state) + private static void InvokeCallback(object state) { ((CancellationCallbackWrapper)state).TryInvoke(); } @@ -32,6 +37,56 @@ namespace Microsoft.AspNet.SignalR.Infrastructure ((DiposeCancellationState)state).TryDispose(); } + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method should never throw since it runs as part of field initialzation")] + private static RegisterDelegate ResolveRegisterDelegate() + { + // The fallback is just a normal register that capatures the execution context. + RegisterDelegate fallback = (ref CancellationToken token, Action callback, object state) => + { + return token.Register(callback, state); + }; + +#if NETFX_CORE || PORTABLE + return fallback; +#else + + MethodInfo methodInfo = null; + + try + { + // By default we don't want to capture the execution context, + // since this is internal we need to create a delegate to this up front + methodInfo = typeof(CancellationToken).GetMethod("InternalRegisterWithoutEC", + BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + types: new[] { typeof(Action), typeof(object) }, + modifiers: null); + } + catch + { + // Swallow this exception. Being extra paranoid, we don't want anything to break in case this dirty + // reflection hack fails for any reason + } + + // If the method was removed then fallback to the regular method + if (methodInfo == null) + { + return fallback; + } + + try + { + + return (RegisterDelegate)Delegate.CreateDelegate(typeof(RegisterDelegate), null, methodInfo); + } + catch + { + // If this fails for whatever reason just fallback to normal register + return fallback; + } +#endif + } + private class DiposeCancellationState { private readonly CancellationCallbackWrapper _callbackWrapper; @@ -48,8 +103,14 @@ namespace Microsoft.AspNet.SignalR.Infrastructure // This normally waits until the callback is finished invoked but we don't care if (_callbackWrapper.TrySetInvoked()) { - // Bug #1549, .NET 4.0 has a bug where this throws if the CTS - _registration.Dispose(); + try + { + _registration.Dispose(); + } + catch (ObjectDisposedException) + { + // Bug #1549, .NET 4.0 has a bug where this throws if the CTS is disposed. + } } } } diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs index 092dcebdb..a938c87f0 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/Connection.cs @@ -144,7 +144,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure { using (var stream = new MemoryStream(128)) { - var bufferWriter = new BufferTextWriter((buffer, state) => + var bufferWriter = new BinaryTextWriter((buffer, state) => { ((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count); }, @@ -236,8 +236,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure if (command == null) { - var platform = (int)Environment.OSVersion.Platform; - if (platform == 4 || platform == 6 || platform == 128) + if (MonoUtility.IsRunningMono) { return; } diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/MonoUtility.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/MonoUtility.cs new file mode 100644 index 000000000..ea1d80127 --- /dev/null +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/MonoUtility.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.SignalR.Infrastructure +{ + internal static class MonoUtility + { + private static readonly Lazy _isRunningMono = new Lazy(() => CheckRunningOnMono()); + + internal static bool IsRunningMono + { + get + { + return _isRunningMono.Value; + } + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This should never fail")] + private static bool CheckRunningOnMono() + { + try + { + return Type.GetType("Mono.Runtime") != null; + } + catch + { + return false; + } + } + } +} diff --git a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/TaskQueue.cs b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/TaskQueue.cs index f12743b75..1c3d63489 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Infrastructure/TaskQueue.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Infrastructure/TaskQueue.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure _maxSize = maxSize; } -#if !CLIENT_NET45 +#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")] public IPerformanceCounter QueueSizeCounter { get; set; } #endif @@ -62,19 +62,16 @@ namespace Microsoft.AspNet.SignalR.Infrastructure if (_maxSize != null) { - if (Interlocked.Read(ref _size) == _maxSize) + // Increment the size if the queue + if (Interlocked.Increment(ref _size) > _maxSize) { - // REVIEW: Do we need to make the contract more clear between the - // queue full case and the queue drained case? Should we throw an exeception instead? - + Interlocked.Decrement(ref _size); + // We failed to enqueue because the size limit was reached return null; } - // Increment the size if the queue - Interlocked.Increment(ref _size); - -#if !CLIENT_NET45 +#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT var counter = QueueSizeCounter; if (counter != null) { @@ -93,7 +90,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure // Decrement the number of items left in the queue Interlocked.Decrement(ref queue._size); -#if !CLIENT_NET45 +#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT var counter = QueueSizeCounter; if (counter != null) { diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs index 8099e9a99..ad915b562 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs @@ -33,8 +33,10 @@ namespace Microsoft.AspNet.SignalR.Messaging _escapedKey = minifiedKey; } - public static void WriteCursors(TextWriter textWriter, IList cursors) + public static void WriteCursors(TextWriter textWriter, IList cursors, string prefix) { + textWriter.Write(prefix); + for (int i = 0; i < cursors.Count; i++) { if (i > 0) @@ -48,7 +50,7 @@ namespace Microsoft.AspNet.SignalR.Messaging } } - private static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter) + internal static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter) { // This tracks the length of the output and serves as the index for the next character to be written into the pBuffer. // The length could reach up to 16 characters, so at least that much space should remain in the pBuffer. @@ -114,17 +116,17 @@ namespace Microsoft.AspNet.SignalR.Messaging return sb.ToString(); } - public static List GetCursors(string cursor) + public static List GetCursors(string cursor, string prefix) { - return GetCursors(cursor, s => s); + return GetCursors(cursor, prefix, s => s); } - public static List GetCursors(string cursor, Func keyMaximizer) + public static List GetCursors(string cursor, string prefix, Func keyMaximizer) { - return GetCursors(cursor, (key, state) => ((Func)state).Invoke(key), keyMaximizer); + return GetCursors(cursor, prefix, (key, state) => ((Func)state).Invoke(key), keyMaximizer); } - public static List GetCursors(string cursor, Func keyMaximizer, object state) + public static List GetCursors(string cursor, string prefix, Func keyMaximizer, object state) { // Technically GetCursors should never be called with a null value, so this is extra cautious if (String.IsNullOrEmpty(cursor)) @@ -132,6 +134,14 @@ namespace Microsoft.AspNet.SignalR.Messaging throw new FormatException(Resources.Error_InvalidCursorFormat); } + // If the cursor does not begin with the prefix stream, it isn't necessarily a formatting problem. + // The cursor with a different prefix might have had different, but also valid, formatting. + // Null should be returned so new cursors will be generated + if (!cursor.StartsWith(prefix, StringComparison.Ordinal)) + { + return null; + } + var signals = new HashSet(); var cursors = new List(); string currentKey = null; @@ -143,8 +153,10 @@ namespace Microsoft.AspNet.SignalR.Messaging var sbEscaped = new StringBuilder(); Cursor parsedCursor; - foreach (var ch in cursor) + for (int i = prefix.Length; i < cursor.Length; i++) { + var ch = cursor[i]; + // escape can only be true if we are consuming the key if (escape) { diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/DefaultSubscription.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/DefaultSubscription.cs index 17c3e0e2d..08e4b8049 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/DefaultSubscription.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/DefaultSubscription.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; +using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Infrastructure; @@ -11,6 +13,8 @@ namespace Microsoft.AspNet.SignalR.Messaging { internal class DefaultSubscription : Subscription { + internal static string _defaultCursorPrefix = GetCursorPrefix(); + private List _cursors; private List _cursorTopics; @@ -36,7 +40,7 @@ namespace Microsoft.AspNet.SignalR.Messaging else { // Ensure delegate continues to use the C# Compiler static delegate caching optimization. - _cursors = Cursor.GetCursors(cursor, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics); + _cursors = Cursor.GetCursors(cursor, _defaultCursorPrefix, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics); } _cursorTopics = new List(); @@ -126,7 +130,7 @@ namespace Microsoft.AspNet.SignalR.Messaging { lock (_cursors) { - Cursor.WriteCursors(textWriter, _cursors); + Cursor.WriteCursors(textWriter, _cursors, _defaultCursorPrefix); } } @@ -196,6 +200,22 @@ namespace Microsoft.AspNet.SignalR.Messaging return list; } + private static string GetCursorPrefix() + { + using (var rng = new RNGCryptoServiceProvider()) + { + var data = new byte[4]; + rng.GetBytes(data); + + using (var writer = new StringWriter(CultureInfo.InvariantCulture)) + { + var randomValue = (ulong)BitConverter.ToUInt32(data, 0); + Cursor.WriteUlongAsHexToBuffer(randomValue, writer); + return "d-" + writer.ToString() + "-"; + } + } + } + private static ulong GetMessageId(TopicLookup topics, string key) { Topic topic; diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageBroker.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageBroker.cs index 8995f96bc..a87b90a33 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageBroker.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageBroker.cs @@ -233,7 +233,7 @@ namespace Microsoft.AspNet.SignalR.Messaging } finally { - if (!subscription.UnsetQueued() || workTask.IsFaulted) + if (!subscription.UnsetQueued() || workTask.IsFaulted || workTask.IsCanceled) { // If we don't have more work to do just make the subscription null subscription = null; @@ -271,7 +271,7 @@ namespace Microsoft.AspNet.SignalR.Messaging Trace.TraceEvent(TraceEventType.Error, 0, "Work failed for " + subscription.Identity + ": " + task.Exception.GetBaseException()); } - if (moreWork && !task.IsFaulted) + if (moreWork && !task.IsFaulted && !task.IsCanceled) { PumpImpl(taskCompletionSource, subscription); } @@ -295,10 +295,7 @@ namespace Microsoft.AspNet.SignalR.Messaging Trace.TraceEvent(TraceEventType.Verbose, 0, "Dispoing the broker"); - //Check if OS is not Windows and exit - var platform = (int)Environment.OSVersion.Platform; - - if ((platform == 4) || (platform == 6) || (platform == 128)) + if (MonoUtility.IsRunningMono) { return; } diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageResult.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageResult.cs index e1d605d4a..f29f86097 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageResult.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/MessageResult.cs @@ -15,6 +15,9 @@ namespace Microsoft.AspNet.SignalR.Messaging private static readonly List> _emptyList = new List>(); public readonly static MessageResult TerminalMessage = new MessageResult(terminal: true); + /// + /// Gets an associated with the result. + /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an optimization to avoid allocations.")] public IList> Messages { get; private set; } diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/ScaleoutSubscription.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/ScaleoutSubscription.cs index a8c34ade4..d426f7a37 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/ScaleoutSubscription.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/ScaleoutSubscription.cs @@ -12,6 +12,8 @@ namespace Microsoft.AspNet.SignalR.Messaging { public class ScaleoutSubscription : Subscription { + private const string _scaleoutCursorPrefix = "s-"; + private readonly IList _streams; private readonly List _cursors; @@ -40,10 +42,15 @@ namespace Microsoft.AspNet.SignalR.Messaging } else { - cursors = Cursor.GetCursors(cursor); + cursors = Cursor.GetCursors(cursor, _scaleoutCursorPrefix); + // If the cursor had a default prefix, "d-", cursors might be null + if (cursors == null) + { + cursors = new List(); + } // If the streams don't match the cursors then throw it out - if (cursors.Count != _streams.Count) + else if (cursors.Count != _streams.Count) { cursors.Clear(); } @@ -63,7 +70,7 @@ namespace Microsoft.AspNet.SignalR.Messaging public override void WriteCursor(TextWriter textWriter) { - Cursor.WriteCursors(textWriter, _cursors); + Cursor.WriteCursors(textWriter, _cursors, _scaleoutCursorPrefix); } [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "The list needs to be populated")] diff --git a/src/Microsoft.AspNet.SignalR.Core/Messaging/Subscription.cs b/src/Microsoft.AspNet.SignalR.Core/Messaging/Subscription.cs index 0417beab2..774ccbddc 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Messaging/Subscription.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Messaging/Subscription.cs @@ -120,13 +120,7 @@ namespace Microsoft.AspNet.SignalR.Messaging WorkImpl(tcs); - // Fast Path - if (tcs.Task.IsCompleted) - { - return tcs.Task; - } - - return FinishAsync(tcs); + return tcs.Task; } public bool SetQueued() @@ -140,19 +134,6 @@ namespace Microsoft.AspNet.SignalR.Messaging return Interlocked.CompareExchange(ref _state, State.Idle, State.Working) != State.Working; } - private static Task FinishAsync(TaskCompletionSource tcs) - { - return tcs.Task.ContinueWith(task => - { - if (task.IsFaulted) - { - return TaskAsyncHelper.FromError(task.Exception); - } - - return TaskAsyncHelper.Empty; - }).FastUnwrap(); - } - [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "We have a sync and async code path.")] [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to avoid user code taking the process down.")] private void WorkImpl(TaskCompletionSource taskCompletionSource) @@ -200,7 +181,14 @@ namespace Microsoft.AspNet.SignalR.Messaging } catch (Exception ex) { - taskCompletionSource.TrySetUnwrappedException(ex); + if (ex.InnerException is TaskCanceledException) + { + taskCompletionSource.TrySetCanceled(); + } + else + { + taskCompletionSource.TrySetUnwrappedException(ex); + } } } else @@ -233,6 +221,10 @@ namespace Microsoft.AspNet.SignalR.Messaging { taskCompletionSource.TrySetUnwrappedException(task.Exception); } + else if (task.IsCanceled) + { + taskCompletionSource.TrySetCanceled(); + } else if (task.Result) { WorkImpl(taskCompletionSource); diff --git a/src/Microsoft.AspNet.SignalR.Core/Microsoft.AspNet.SignalR.Core.csproj b/src/Microsoft.AspNet.SignalR.Core/Microsoft.AspNet.SignalR.Core.csproj index a02815cb8..6eee5bb9c 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Microsoft.AspNet.SignalR.Core.csproj +++ b/src/Microsoft.AspNet.SignalR.Core/Microsoft.AspNet.SignalR.Core.csproj @@ -71,9 +71,11 @@ + + @@ -280,4 +282,4 @@ --> - \ No newline at end of file + diff --git a/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs b/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs index ba9fc4079..108f5c3bf 100644 --- a/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs +++ b/src/Microsoft.AspNet.SignalR.Core/PersistentConnection.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Configuration; using Microsoft.AspNet.SignalR.Hosting; @@ -165,7 +166,7 @@ namespace Microsoft.AspNet.SignalR if (Transport == null) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport)); + return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport)); } string connectionToken = context.Request.QueryString["connectionToken"]; @@ -173,10 +174,17 @@ namespace Microsoft.AspNet.SignalR // If there's no connection id then this is a bad request if (String.IsNullOrEmpty(connectionToken)) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken)); + return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken)); } - string connectionId = GetConnectionId(context, connectionToken); + string connectionId; + string message; + int statusCode; + + if (!TryGetConnectionId(context, connectionToken, out connectionId, out message, out statusCode)) + { + return FailResponse(context.Response, message, statusCode); + } // Set the transport's connection id to the unprotected one Transport.ConnectionId = connectionId; @@ -227,10 +235,21 @@ namespace Microsoft.AspNet.SignalR } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to catch any exception when unprotecting data.")] - internal string GetConnectionId(HostContext context, string connectionToken) + internal bool TryGetConnectionId(HostContext context, + string connectionToken, + out string connectionId, + out string message, + out int statusCode) { string unprotectedConnectionToken = null; + // connectionId is only valid when this method returns true + connectionId = null; + + // message and statusCode are only valid when this method returns false + message = null; + statusCode = 400; + try { unprotectedConnectionToken = ProtectedData.Unprotect(connectionToken, Purposes.ConnectionToken); @@ -242,21 +261,24 @@ namespace Microsoft.AspNet.SignalR if (String.IsNullOrEmpty(unprotectedConnectionToken)) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat)); + message = String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat); + return false; } var tokens = unprotectedConnectionToken.Split(SplitChars, 2); - string connectionId = tokens[0]; + connectionId = tokens[0]; string tokenUserName = tokens.Length > 1 ? tokens[1] : String.Empty; string userName = GetUserIdentity(context); if (!String.Equals(tokenUserName, userName, StringComparison.OrdinalIgnoreCase)) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity)); + message = String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity); + statusCode = 403; + return false; } - return connectionId; + return true; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to prevent any failures in unprotecting")] @@ -477,6 +499,12 @@ namespace Microsoft.AspNet.SignalR return context.Response.End(data); } + private static Task FailResponse(IResponse response, string message, int statusCode = 400) + { + response.StatusCode = statusCode; + return response.End(message); + } + private static bool IsNegotiationRequest(IRequest request) { return request.Url.LocalPath.EndsWith("/negotiate", StringComparison.OrdinalIgnoreCase); diff --git a/src/Microsoft.AspNet.SignalR.Core/Scripts/hubs.js b/src/Microsoft.AspNet.SignalR.Core/Scripts/hubs.js index 74b91d8f8..ca0f7977d 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Scripts/hubs.js +++ b/src/Microsoft.AspNet.SignalR.Core/Scripts/hubs.js @@ -1,5 +1,5 @@ /*! - * ASP.NET SignalR JavaScript Library v1.1.3 + * ASP.NET SignalR JavaScript Library v1.2.2 * http://signalr.net/ * * Copyright Microsoft Open Technologies, Inc. All rights reserved. @@ -10,7 +10,7 @@ /// /// -(function ($, window) { +(function ($, window, undefined) { /// "use strict"; diff --git a/src/Microsoft.AspNet.SignalR.Core/TaskAsyncHelper.cs b/src/Microsoft.AspNet.SignalR.Core/TaskAsyncHelper.cs index fc9e3d57c..ba5235ffb 100644 --- a/src/Microsoft.AspNet.SignalR.Core/TaskAsyncHelper.cs +++ b/src/Microsoft.AspNet.SignalR.Core/TaskAsyncHelper.cs @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Infrastructure; @@ -159,7 +163,7 @@ namespace Microsoft.AspNet.SignalR { // observe Exception #if !WINDOWS_PHONE && !SILVERLIGHT && !NETFX_CORE - Trace.TraceError("SignalR exception thrown by Task: {0}", exception); + Trace.TraceWarning("SignalR exception thrown by Task: {0}", exception); #endif handler(exception, state); } diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs index 8d3d9e9ed..94b464cf4 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Transports/ForeverTransport.cs @@ -162,7 +162,7 @@ namespace Microsoft.AspNet.SignalR.Transports } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller.")] - private Task ProcessReceiveRequest(ITransportConnection connection) + protected Task ProcessReceiveRequest(ITransportConnection connection) { Func initialize = null; @@ -273,7 +273,7 @@ namespace Microsoft.AspNet.SignalR.Transports { var context = (MessageContext)state; - response.TimedOut = context.Transport.IsTimedOut; + response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested; // If we're telling the client to disconnect then clean up the instantiated connection. if (response.Disconnect) @@ -282,7 +282,7 @@ namespace Microsoft.AspNet.SignalR.Transports return context.Transport.Send(response).Then(c => OnDisconnectMessage(c), context) .Then(() => TaskAsyncHelper.False); } - else if (response.TimedOut || response.Aborted) + else if (context.Transport.IsTimedOut || response.Aborted) { context.Registration.Dispose(); diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs index 543d255bd..0e4b141a1 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Transports/LongPollingTransport.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Hosting; using Microsoft.AspNet.SignalR.Infrastructure; @@ -252,7 +253,7 @@ namespace Microsoft.AspNet.SignalR.Transports { var context = (MessageContext)state; - response.TimedOut = context.Transport.IsTimedOut; + response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested; Task task = TaskAsyncHelper.Empty; diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/PersistentResponse.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/PersistentResponse.cs index 17c1bd2d1..ced8d90a3 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Transports/PersistentResponse.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Transports/PersistentResponse.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.SignalR.Transports private readonly Action _writeCursor; public PersistentResponse() - : this(message => true, writer => { }) + : this(message => false, writer => { }) { } @@ -61,9 +61,10 @@ namespace Microsoft.AspNet.SignalR.Transports public bool Aborted { get; set; } /// - /// True if the connection timed out. + /// True if the client should try reconnecting. /// - public bool TimedOut { get; set; } + // This is set when the host is shutting down. + public bool Reconnect { get; set; } /// /// Signed token representing the list of groups. Updates on change. @@ -106,7 +107,7 @@ namespace Microsoft.AspNet.SignalR.Transports jsonWriter.WriteValue(1); } - if (TimedOut) + if (Reconnect) { jsonWriter.WritePropertyName("T"); jsonWriter.WriteValue(1); diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs index 4b2c939c7..45f96e0bd 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Transports/TransportDisconnectBase.cs @@ -130,6 +130,14 @@ namespace Microsoft.AspNet.SignalR.Transports } } + protected CancellationToken HostShutdownToken + { + get + { + return _hostShutdownToken; + } + } + public bool IsTimedOut { get @@ -186,7 +194,7 @@ namespace Microsoft.AspNet.SignalR.Transports protected virtual TextWriter CreateResponseWriter() { - return new BufferTextWriter(Context.Response); + return new BinaryTextWriter(Context.Response); } protected void IncrementErrors() diff --git a/src/Microsoft.AspNet.SignalR.Core/Transports/WebSocketTransport.cs b/src/Microsoft.AspNet.SignalR.Core/Transports/WebSocketTransport.cs index 66b4838e3..8f5db2cec 100644 --- a/src/Microsoft.AspNet.SignalR.Core/Transports/WebSocketTransport.cs +++ b/src/Microsoft.AspNet.SignalR.Core/Transports/WebSocketTransport.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.SignalR.Transports private bool _isAlive = true; private readonly Action _message; - private readonly Action _closed; + private readonly Action _closed; private readonly Action _error; public WebSocketTransport(HostContext context, @@ -74,28 +74,39 @@ namespace Microsoft.AspNet.SignalR.Transports public override Task ProcessRequest(ITransportConnection connection) { - var webSocketRequest = _context.Request as IWebSocketRequest; - - // Throw if the server implementation doesn't support websockets - if (webSocketRequest == null) + if (IsAbortRequest) { - throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported); + return connection.Abort(ConnectionId); } - - return webSocketRequest.AcceptWebSocketRequest(socket => + else { - _socket = socket; - socket.OnClose = _closed; - socket.OnMessage = _message; - socket.OnError = _error; - - return ProcessRequestCore(connection); - }); + var webSocketRequest = _context.Request as IWebSocketRequest; + + // Throw if the server implementation doesn't support websockets + if (webSocketRequest == null) + { + throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported); + } + + Connection = connection; + InitializePersistentState(); + + return webSocketRequest.AcceptWebSocketRequest(socket => + { + _socket = socket; + socket.OnClose = _closed; + socket.OnMessage = _message; + socket.OnError = _error; + + return ProcessReceiveRequest(connection); + }, + InitializeTcs.Task); + } } protected override TextWriter CreateResponseWriter() { - return new BufferTextWriter(_socket); + return new BinaryTextWriter(_socket); } public override Task Send(object value) @@ -113,6 +124,11 @@ namespace Microsoft.AspNet.SignalR.Transports return Send((object)response); } + protected internal override Task InitializeResponse(ITransportConnection connection) + { + return _socket.Send("{}"); + } + private static Task PerformSend(object state) { var context = (WebSocketTransportContext)state; @@ -131,18 +147,11 @@ namespace Microsoft.AspNet.SignalR.Transports } } - private void OnClosed(bool clean) + private void OnClosed() { - Trace.TraceInformation("CloseSocket({0}, {1})", clean, ConnectionId); - - // If we performed a clean disconnect then we go through the normal disconnect routine. However, - // If we performed an unclean disconnect we want to mark the connection as "not alive" and let the - // HeartBeat clean it up. This is to maintain consistency across the transports. - if (clean) - { - Abort(); - } + Trace.TraceInformation("CloseSocket({0})", ConnectionId); + // Require a request to /abort to stop tracking the connection. #2195 _isAlive = false; } diff --git a/src/Microsoft.AspNet.SignalR.Owin/Handlers/CallHandler.cs b/src/Microsoft.AspNet.SignalR.Owin/Handlers/CallHandler.cs index a2234051b..3dd5c6975 100644 --- a/src/Microsoft.AspNet.SignalR.Owin/Handlers/CallHandler.cs +++ b/src/Microsoft.AspNet.SignalR.Owin/Handlers/CallHandler.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Hosting; using Microsoft.AspNet.SignalR.Owin.Infrastructure; @@ -65,9 +66,19 @@ namespace Microsoft.AspNet.SignalR.Owin if (!_connection.Authorize(serverRequest)) { - // If we failed to authorize the request then return a 403 since the request - // can't do anything - return EndResponse(environment, 403, "Forbidden"); + IPrincipal user = hostContext.Request.User; + if (user != null && user.Identity.IsAuthenticated) + { + // If we failed to authorize the request then return a 403 since the request + // can't do anything + return EndResponse(environment, 403, "Forbidden"); + } + else + { + // If we failed to authorize the request and the user is not authenticated + // then return a 401 + return EndResponse(environment, 401, "Unauthorized"); + } } else { diff --git a/src/Microsoft.AspNet.SignalR.Owin/ServerRequest.cs b/src/Microsoft.AspNet.SignalR.Owin/ServerRequest.cs index b2d2ac492..ee07aafbc 100644 --- a/src/Microsoft.AspNet.SignalR.Owin/ServerRequest.cs +++ b/src/Microsoft.AspNet.SignalR.Owin/ServerRequest.cs @@ -7,6 +7,7 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.SignalR.Owin.Infrastructure; +using Microsoft.AspNet.SignalR.Hosting; namespace Microsoft.AspNet.SignalR.Owin { @@ -138,15 +139,17 @@ namespace Microsoft.AspNet.SignalR.Owin } #if NET45 - public Task AcceptWebSocketRequest(Func callback) + public Task AcceptWebSocketRequest(Func callback, Task initTask) { var accept = _environment.Get, WebSocketFunc>>(OwinConstants.WebSocketAccept); if (accept == null) { - throw new InvalidOperationException(Resources.Error_NotWebSocketRequest); + var response = new ServerResponse(_environment); + response.StatusCode = 400; + return response.End(Resources.Error_NotWebSocketRequest); } - var handler = new OwinWebSocketHandler(callback); + var handler = new OwinWebSocketHandler(callback, initTask); accept(null, handler.ProcessRequestAsync); return TaskAsyncHelper.Empty; } diff --git a/src/Microsoft.AspNet.SignalR.Owin/ServerResponse.cs b/src/Microsoft.AspNet.SignalR.Owin/ServerResponse.cs index bdf7a5ba6..c268202b6 100644 --- a/src/Microsoft.AspNet.SignalR.Owin/ServerResponse.cs +++ b/src/Microsoft.AspNet.SignalR.Owin/ServerResponse.cs @@ -27,6 +27,18 @@ namespace Microsoft.AspNet.SignalR.Owin get { return _callCancelled; } } + public int StatusCode + { + get + { + return _environment.Get(OwinConstants.ResponseStatusCode); + } + set + { + _environment[OwinConstants.ResponseStatusCode] = value; + } + } + public string ContentType { get { return ResponseHeaders.GetHeader("Content-Type"); } diff --git a/src/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs b/src/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs deleted file mode 100644 index 2eec1b004..000000000 --- a/src/NzbDrone.SignalR/NoOpPerformanceCounterManager.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Diagnostics; -using System.Threading; -using Microsoft.AspNet.SignalR.Infrastructure; - -namespace NzbDrone.SignalR -{ - public class NoOpPerformanceCounterManager : IPerformanceCounterManager - { - private static readonly IPerformanceCounter noOpCounter = new NoOpPerformanceCounter(); - - public void Initialize(string instanceName, CancellationToken hostShutdownToken) - { - - } - - public IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly) - { - return noOpCounter; - } - - public IPerformanceCounter ConnectionsConnected { get { return noOpCounter; } } - public IPerformanceCounter ConnectionsReconnected { get { return noOpCounter; } } - public IPerformanceCounter ConnectionsDisconnected { get { return noOpCounter; } } - public IPerformanceCounter ConnectionsCurrent { get { return noOpCounter; } } - public IPerformanceCounter ConnectionMessagesReceivedTotal { get { return noOpCounter; } } - public IPerformanceCounter ConnectionMessagesSentTotal { get { return noOpCounter; } } - public IPerformanceCounter ConnectionMessagesReceivedPerSec { get { return noOpCounter; } } - public IPerformanceCounter ConnectionMessagesSentPerSec { get { return noOpCounter; } } - public IPerformanceCounter MessageBusMessagesReceivedTotal { get { return noOpCounter; } } - public IPerformanceCounter MessageBusMessagesReceivedPerSec { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec { get { return noOpCounter; } } - public IPerformanceCounter MessageBusMessagesPublishedTotal { get { return noOpCounter; } } - public IPerformanceCounter MessageBusMessagesPublishedPerSec { get { return noOpCounter; } } - public IPerformanceCounter MessageBusSubscribersCurrent { get { return noOpCounter; } } - public IPerformanceCounter MessageBusSubscribersTotal { get { return noOpCounter; } } - public IPerformanceCounter MessageBusSubscribersPerSec { get { return noOpCounter; } } - public IPerformanceCounter MessageBusAllocatedWorkers { get { return noOpCounter; } } - public IPerformanceCounter MessageBusBusyWorkers { get { return noOpCounter; } } - public IPerformanceCounter MessageBusTopicsCurrent { get { return noOpCounter; } } - public IPerformanceCounter ErrorsAllTotal { get { return noOpCounter; } } - public IPerformanceCounter ErrorsAllPerSec { get { return noOpCounter; } } - public IPerformanceCounter ErrorsHubResolutionTotal { get { return noOpCounter; } } - public IPerformanceCounter ErrorsHubResolutionPerSec { get { return noOpCounter; } } - public IPerformanceCounter ErrorsHubInvocationTotal { get { return noOpCounter; } } - public IPerformanceCounter ErrorsHubInvocationPerSec { get { return noOpCounter; } } - public IPerformanceCounter ErrorsTransportTotal { get { return noOpCounter; } } - public IPerformanceCounter ErrorsTransportPerSec { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutStreamCountTotal { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutStreamCountOpen { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutStreamCountBuffering { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutErrorsTotal { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutErrorsPerSec { get { return noOpCounter; } } - public IPerformanceCounter ScaleoutSendQueueLength { get { return noOpCounter; } } - } - - public class NoOpPerformanceCounter : IPerformanceCounter - { - public string CounterName - { - get - { - return this.GetType().Name; - } - } - - public long RawValue - { - get - { - return 0L; - } - set - { - } - } - - public long Decrement() - { - return 0L; - } - - public long Increment() - { - return 0L; - } - - public long IncrementBy(long value) - { - return 0L; - } - - public void Close() - { - } - - public void RemoveInstance() - { - } - - public CounterSample NextSample() - { - return CounterSample.Empty; - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj b/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj index d1e684b29..08378c629 100644 --- a/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj +++ b/src/NzbDrone.SignalR/NzbDrone.SignalR.csproj @@ -50,7 +50,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs b/src/NzbDrone.SignalR/SignalrDependencyResolver.cs index a3633cbae..8d001d25f 100644 --- a/src/NzbDrone.SignalR/SignalrDependencyResolver.cs +++ b/src/NzbDrone.SignalR/SignalrDependencyResolver.cs @@ -1,6 +1,5 @@ using System; using Microsoft.AspNet.SignalR; -using Microsoft.AspNet.SignalR.Infrastructure; using NzbDrone.Common.Composition; namespace NzbDrone.SignalR @@ -16,7 +15,6 @@ namespace NzbDrone.SignalR private SignalrDependencyResolver(IContainer container) { - container.RegisterSingleton(typeof(IPerformanceCounterManager), typeof(NoOpPerformanceCounterManager)); _container = container; }