Merge pull request #191 from Sonarr/signalr-1-2-2

Upgraded SignalR to 1.2.2
This commit is contained in:
Keivan Beigi 2015-02-07 08:20:11 -08:00
commit e4a93ded28
33 changed files with 367 additions and 239 deletions

View File

@ -122,7 +122,7 @@ namespace Microsoft.AspNet.SignalR
{ {
if (user == null) if (user == null)
{ {
throw new ArgumentNullException("user"); return false;
} }
if (!user.Identity.IsAuthenticated) if (!user.Identity.IsAuthenticated)

View File

@ -20,8 +20,12 @@ namespace Microsoft.AspNet.SignalR.Hosting
throw new ArgumentNullException("instanceName"); throw new ArgumentNullException("instanceName");
} }
// Initialize the performance counters // Performance counters are broken on mono so just skip this step
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken); if (!MonoUtility.IsRunningMono)
{
// Initialize the performance counters
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
}
// Dispose the dependency resolver on host shut down (cleanly) // Dispose the dependency resolver on host shut down (cleanly)
resolver.InitializeResolverDispose(hostShutdownToken); resolver.InitializeResolverDispose(hostShutdownToken);
@ -41,12 +45,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
// TODO: Guard against multiple calls to this // TODO: Guard against multiple calls to this
// When the host triggers the shutdown token, dispose the resolver // When the host triggers the shutdown token, dispose the resolver
hostShutdownToken.Register(state => hostShutdownToken.SafeRegister(state =>
{ {
((IDependencyResolver)state).Dispose(); ((IDependencyResolver)state).Dispose();
}, },
resolver, resolver);
useSynchronizationContext: false);
} }
} }
} }

View File

@ -17,6 +17,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
/// </summary> /// </summary>
CancellationToken CancellationToken { get; } CancellationToken CancellationToken { get; }
/// <summary>
/// Gets or sets the status code of the response.
/// </summary>
int StatusCode { get; set; }
/// <summary> /// <summary>
/// Gets or sets the content type of the response. /// Gets or sets the content type of the response.
/// </summary> /// </summary>

View File

@ -16,9 +16,9 @@ namespace Microsoft.AspNet.SignalR.Hosting
Action<string> OnMessage { get; set; } Action<string> OnMessage { get; set; }
/// <summary> /// <summary>
/// Invoked when the websocket gracefully closes /// Invoked when the websocket closes
/// </summary> /// </summary>
Action<bool> OnClose { get; set; } Action OnClose { get; set; }
/// <summary> /// <summary>
/// Invoked when there is an error /// Invoked when there is an error

View File

@ -11,6 +11,7 @@ namespace Microsoft.AspNet.SignalR.Hosting
/// Accepts an websocket request using the specified user function. /// Accepts an websocket request using the specified user function.
/// </summary> /// </summary>
/// <param name="callback">The callback that fires when the websocket is ready.</param> /// <param name="callback">The callback that fires when the websocket is ready.</param>
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback); /// <param name="initTask">The task that completes when the websocket transport is ready.</param>
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask);
} }
} }

View File

@ -358,7 +358,7 @@ namespace Microsoft.AspNet.SignalR.Hubs
private Task ExecuteHubEvent(IRequest request, string connectionId, Func<IHub, Task> action) private Task ExecuteHubEvent(IRequest request, string connectionId, Func<IHub, Task> action)
{ {
var hubs = GetHubs(request, connectionId).ToList(); 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) if (operations.Length == 0)
{ {

View File

@ -0,0 +1,34 @@
using System;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// A buffering text writer that supports writing binary directly as well
/// </summary>
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<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize) :
base(write, state, reuseBuffers, bufferSize)
{
}
public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
}
}

View File

@ -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 /// we don't need to write to a long lived buffer. This saves massive amounts of memory
/// as the number of connections grows. /// as the number of connections grows.
/// </summary> /// </summary>
internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter internal abstract unsafe class BufferTextWriter : TextWriter
{ {
private readonly Encoding _encoding; private readonly Encoding _encoding;
@ -31,13 +31,13 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
} }
public BufferTextWriter(IWebSocket socket) : 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")] [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")]
public BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize) protected BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
{ {
_write = write; _write = write;
_writeState = state; _writeState = state;
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
_bufferSize = bufferSize; _bufferSize = bufferSize;
} }
private ChunkedWriter Writer protected internal ChunkedWriter Writer
{ {
get get
{ {
@ -79,17 +79,12 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
Writer.Write(value); Writer.Write(value);
} }
public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
public override void Flush() public override void Flush()
{ {
Writer.Flush(); Writer.Flush();
} }
private class ChunkedWriter internal class ChunkedWriter
{ {
private int _charPos; private int _charPos;
private int _charLen; private int _charLen;

View File

@ -1,20 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading; using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure namespace Microsoft.AspNet.SignalR.Infrastructure
{ {
internal static class CancellationTokenExtensions internal static class CancellationTokenExtensions
{ {
private delegate CancellationTokenRegistration RegisterDelegate(ref CancellationToken token, Action<object> callback, object state);
private static readonly RegisterDelegate _tokenRegister = ResolveRegisterDelegate();
public static IDisposable SafeRegister(this CancellationToken cancellationToken, Action<object> callback, object state) public static IDisposable SafeRegister(this CancellationToken cancellationToken, Action<object> callback, object state)
{ {
var callbackWrapper = new CancellationCallbackWrapper(callback, state); var callbackWrapper = new CancellationCallbackWrapper(callback, state);
// Ensure delegate continues to use the C# Compiler static delegate caching optimization. // Ensure delegate continues to use the C# Compiler static delegate caching optimization.
CancellationTokenRegistration registration = cancellationToken.Register(s => Cancel(s), CancellationTokenRegistration registration = _tokenRegister(ref cancellationToken, s => InvokeCallback(s), callbackWrapper);
callbackWrapper,
useSynchronizationContext: false);
var disposeCancellationState = new DiposeCancellationState(callbackWrapper, registration); var disposeCancellationState = new DiposeCancellationState(callbackWrapper, registration);
@ -22,7 +27,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
return new DisposableAction(s => Dispose(s), disposeCancellationState); return new DisposableAction(s => Dispose(s), disposeCancellationState);
} }
private static void Cancel(object state) private static void InvokeCallback(object state)
{ {
((CancellationCallbackWrapper)state).TryInvoke(); ((CancellationCallbackWrapper)state).TryInvoke();
} }
@ -32,6 +37,56 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
((DiposeCancellationState)state).TryDispose(); ((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<object> 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<object>), 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 class DiposeCancellationState
{ {
private readonly CancellationCallbackWrapper _callbackWrapper; 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 // This normally waits until the callback is finished invoked but we don't care
if (_callbackWrapper.TrySetInvoked()) if (_callbackWrapper.TrySetInvoked())
{ {
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS try
_registration.Dispose(); {
_registration.Dispose();
}
catch (ObjectDisposedException)
{
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS is disposed.
}
} }
} }
} }

View File

@ -144,7 +144,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
{ {
using (var stream = new MemoryStream(128)) 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); ((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count);
}, },
@ -236,8 +236,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
if (command == null) if (command == null)
{ {
var platform = (int)Environment.OSVersion.Platform; if (MonoUtility.IsRunningMono)
if (platform == 4 || platform == 6 || platform == 128)
{ {
return; return;
} }

View File

@ -0,0 +1,31 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal static class MonoUtility
{
private static readonly Lazy<bool> _isRunningMono = new Lazy<bool>(() => 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;
}
}
}
}

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
_maxSize = maxSize; _maxSize = maxSize;
} }
#if !CLIENT_NET45 #if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")]
public IPerformanceCounter QueueSizeCounter { get; set; } public IPerformanceCounter QueueSizeCounter { get; set; }
#endif #endif
@ -62,19 +62,16 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
if (_maxSize != null) 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 Interlocked.Decrement(ref _size);
// queue full case and the queue drained case? Should we throw an exeception instead?
// We failed to enqueue because the size limit was reached // We failed to enqueue because the size limit was reached
return null; return null;
} }
// Increment the size if the queue #if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
Interlocked.Increment(ref _size);
#if !CLIENT_NET45
var counter = QueueSizeCounter; var counter = QueueSizeCounter;
if (counter != null) if (counter != null)
{ {
@ -93,7 +90,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
// Decrement the number of items left in the queue // Decrement the number of items left in the queue
Interlocked.Decrement(ref queue._size); Interlocked.Decrement(ref queue._size);
#if !CLIENT_NET45 #if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
var counter = QueueSizeCounter; var counter = QueueSizeCounter;
if (counter != null) if (counter != null)
{ {

View File

@ -33,8 +33,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
_escapedKey = minifiedKey; _escapedKey = minifiedKey;
} }
public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors) public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors, string prefix)
{ {
textWriter.Write(prefix);
for (int i = 0; i < cursors.Count; i++) for (int i = 0; i < cursors.Count; i++)
{ {
if (i > 0) 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. // 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. // 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(); return sb.ToString();
} }
public static List<Cursor> GetCursors(string cursor) public static List<Cursor> GetCursors(string cursor, string prefix)
{ {
return GetCursors(cursor, s => s); return GetCursors(cursor, prefix, s => s);
} }
public static List<Cursor> GetCursors(string cursor, Func<string, string> keyMaximizer) public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, string> keyMaximizer)
{ {
return GetCursors(cursor, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer); return GetCursors(cursor, prefix, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
} }
public static List<Cursor> GetCursors(string cursor, Func<string, object, string> keyMaximizer, object state) public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, object, string> keyMaximizer, object state)
{ {
// Technically GetCursors should never be called with a null value, so this is extra cautious // Technically GetCursors should never be called with a null value, so this is extra cautious
if (String.IsNullOrEmpty(cursor)) if (String.IsNullOrEmpty(cursor))
@ -132,6 +134,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
throw new FormatException(Resources.Error_InvalidCursorFormat); 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<string>(); var signals = new HashSet<string>();
var cursors = new List<Cursor>(); var cursors = new List<Cursor>();
string currentKey = null; string currentKey = null;
@ -143,8 +153,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
var sbEscaped = new StringBuilder(); var sbEscaped = new StringBuilder();
Cursor parsedCursor; 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 // escape can only be true if we are consuming the key
if (escape) if (escape)
{ {

View File

@ -3,7 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO; using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure; using Microsoft.AspNet.SignalR.Infrastructure;
@ -11,6 +13,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
{ {
internal class DefaultSubscription : Subscription internal class DefaultSubscription : Subscription
{ {
internal static string _defaultCursorPrefix = GetCursorPrefix();
private List<Cursor> _cursors; private List<Cursor> _cursors;
private List<Topic> _cursorTopics; private List<Topic> _cursorTopics;
@ -36,7 +40,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
else else
{ {
// Ensure delegate continues to use the C# Compiler static delegate caching optimization. // 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<Topic>(); _cursorTopics = new List<Topic>();
@ -126,7 +130,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
{ {
lock (_cursors) lock (_cursors)
{ {
Cursor.WriteCursors(textWriter, _cursors); Cursor.WriteCursors(textWriter, _cursors, _defaultCursorPrefix);
} }
} }
@ -196,6 +200,22 @@ namespace Microsoft.AspNet.SignalR.Messaging
return list; 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) private static ulong GetMessageId(TopicLookup topics, string key)
{ {
Topic topic; Topic topic;

View File

@ -233,7 +233,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
} }
finally 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 // If we don't have more work to do just make the subscription null
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()); 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); PumpImpl(taskCompletionSource, subscription);
} }
@ -295,10 +295,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
Trace.TraceEvent(TraceEventType.Verbose, 0, "Dispoing the broker"); Trace.TraceEvent(TraceEventType.Verbose, 0, "Dispoing the broker");
//Check if OS is not Windows and exit if (MonoUtility.IsRunningMono)
var platform = (int)Environment.OSVersion.Platform;
if ((platform == 4) || (platform == 6) || (platform == 128))
{ {
return; return;
} }

View File

@ -15,6 +15,9 @@ namespace Microsoft.AspNet.SignalR.Messaging
private static readonly List<ArraySegment<Message>> _emptyList = new List<ArraySegment<Message>>(); private static readonly List<ArraySegment<Message>> _emptyList = new List<ArraySegment<Message>>();
public readonly static MessageResult TerminalMessage = new MessageResult(terminal: true); public readonly static MessageResult TerminalMessage = new MessageResult(terminal: true);
/// <summary>
/// Gets an <see cref="T:IList{Message}"/> associated with the result.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an optimization to avoid allocations.")] [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an optimization to avoid allocations.")]
public IList<ArraySegment<Message>> Messages { get; private set; } public IList<ArraySegment<Message>> Messages { get; private set; }

View File

@ -12,6 +12,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
{ {
public class ScaleoutSubscription : Subscription public class ScaleoutSubscription : Subscription
{ {
private const string _scaleoutCursorPrefix = "s-";
private readonly IList<ScaleoutMappingStore> _streams; private readonly IList<ScaleoutMappingStore> _streams;
private readonly List<Cursor> _cursors; private readonly List<Cursor> _cursors;
@ -40,10 +42,15 @@ namespace Microsoft.AspNet.SignalR.Messaging
} }
else 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<Cursor>();
}
// If the streams don't match the cursors then throw it out // 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(); cursors.Clear();
} }
@ -63,7 +70,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
public override void WriteCursor(TextWriter textWriter) 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")] [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "The list needs to be populated")]

View File

@ -120,13 +120,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
WorkImpl(tcs); WorkImpl(tcs);
// Fast Path return tcs.Task;
if (tcs.Task.IsCompleted)
{
return tcs.Task;
}
return FinishAsync(tcs);
} }
public bool SetQueued() public bool SetQueued()
@ -140,19 +134,6 @@ namespace Microsoft.AspNet.SignalR.Messaging
return Interlocked.CompareExchange(ref _state, State.Idle, State.Working) != State.Working; return Interlocked.CompareExchange(ref _state, State.Idle, State.Working) != State.Working;
} }
private static Task FinishAsync(TaskCompletionSource<object> 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.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.")] [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to avoid user code taking the process down.")]
private void WorkImpl(TaskCompletionSource<object> taskCompletionSource) private void WorkImpl(TaskCompletionSource<object> taskCompletionSource)
@ -200,7 +181,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
} }
catch (Exception ex) catch (Exception ex)
{ {
taskCompletionSource.TrySetUnwrappedException(ex); if (ex.InnerException is TaskCanceledException)
{
taskCompletionSource.TrySetCanceled();
}
else
{
taskCompletionSource.TrySetUnwrappedException(ex);
}
} }
} }
else else
@ -233,6 +221,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
{ {
taskCompletionSource.TrySetUnwrappedException(task.Exception); taskCompletionSource.TrySetUnwrappedException(task.Exception);
} }
else if (task.IsCanceled)
{
taskCompletionSource.TrySetCanceled();
}
else if (task.Result) else if (task.Result)
{ {
WorkImpl(taskCompletionSource); WorkImpl(taskCompletionSource);

View File

@ -71,9 +71,11 @@
<Compile Include="Infrastructure\AckHandler.cs" /> <Compile Include="Infrastructure\AckHandler.cs" />
<Compile Include="Configuration\DefaultConfigurationManager.cs" /> <Compile Include="Configuration\DefaultConfigurationManager.cs" />
<Compile Include="Infrastructure\ArraySegmentTextReader.cs" /> <Compile Include="Infrastructure\ArraySegmentTextReader.cs" />
<Compile Include="Infrastructure\BinaryTextWriter.cs" />
<Compile Include="Infrastructure\ConnectionManager.cs" /> <Compile Include="Infrastructure\ConnectionManager.cs" />
<Compile Include="ConnectionMessage.cs" /> <Compile Include="ConnectionMessage.cs" />
<Compile Include="Infrastructure\DefaultProtectedData.cs" /> <Compile Include="Infrastructure\DefaultProtectedData.cs" />
<Compile Include="Infrastructure\MonoUtility.cs" />
<Compile Include="Infrastructure\DiffPair.cs" /> <Compile Include="Infrastructure\DiffPair.cs" />
<Compile Include="Infrastructure\DiffSet.cs" /> <Compile Include="Infrastructure\DiffSet.cs" />
<Compile Include="GlobalHost.cs" /> <Compile Include="GlobalHost.cs" />
@ -280,4 +282,4 @@
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Configuration; using Microsoft.AspNet.SignalR.Configuration;
using Microsoft.AspNet.SignalR.Hosting; using Microsoft.AspNet.SignalR.Hosting;
@ -165,7 +166,7 @@ namespace Microsoft.AspNet.SignalR
if (Transport == null) 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"]; 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 there's no connection id then this is a bad request
if (String.IsNullOrEmpty(connectionToken)) 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 // Set the transport's connection id to the unprotected one
Transport.ConnectionId = connectionId; 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.")] [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; 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 try
{ {
unprotectedConnectionToken = ProtectedData.Unprotect(connectionToken, Purposes.ConnectionToken); unprotectedConnectionToken = ProtectedData.Unprotect(connectionToken, Purposes.ConnectionToken);
@ -242,21 +261,24 @@ namespace Microsoft.AspNet.SignalR
if (String.IsNullOrEmpty(unprotectedConnectionToken)) 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); var tokens = unprotectedConnectionToken.Split(SplitChars, 2);
string connectionId = tokens[0]; connectionId = tokens[0];
string tokenUserName = tokens.Length > 1 ? tokens[1] : String.Empty; string tokenUserName = tokens.Length > 1 ? tokens[1] : String.Empty;
string userName = GetUserIdentity(context); string userName = GetUserIdentity(context);
if (!String.Equals(tokenUserName, userName, StringComparison.OrdinalIgnoreCase)) 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")] [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); 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) private static bool IsNegotiationRequest(IRequest request)
{ {
return request.Url.LocalPath.EndsWith("/negotiate", StringComparison.OrdinalIgnoreCase); return request.Url.LocalPath.EndsWith("/negotiate", StringComparison.OrdinalIgnoreCase);

View File

@ -1,5 +1,5 @@
/*! /*!
* ASP.NET SignalR JavaScript Library v1.1.3 * ASP.NET SignalR JavaScript Library v1.2.2
* http://signalr.net/ * http://signalr.net/
* *
* Copyright Microsoft Open Technologies, Inc. All rights reserved. * Copyright Microsoft Open Technologies, Inc. All rights reserved.
@ -10,7 +10,7 @@
/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" /> /// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" /> /// <reference path="jquery.signalR.js" />
(function ($, window) { (function ($, window, undefined) {
/// <param name="$" type="jQuery" /> /// <param name="$" type="jQuery" />
"use strict"; "use strict";

View File

@ -1,9 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure; using Microsoft.AspNet.SignalR.Infrastructure;
@ -159,7 +163,7 @@ namespace Microsoft.AspNet.SignalR
{ {
// observe Exception // observe Exception
#if !WINDOWS_PHONE && !SILVERLIGHT && !NETFX_CORE #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 #endif
handler(exception, state); handler(exception, state);
} }

View File

@ -162,7 +162,7 @@ namespace Microsoft.AspNet.SignalR.Transports
} }
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller.")] [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller.")]
private Task ProcessReceiveRequest(ITransportConnection connection) protected Task ProcessReceiveRequest(ITransportConnection connection)
{ {
Func<Task> initialize = null; Func<Task> initialize = null;
@ -273,7 +273,7 @@ namespace Microsoft.AspNet.SignalR.Transports
{ {
var context = (MessageContext)state; 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 we're telling the client to disconnect then clean up the instantiated connection.
if (response.Disconnect) if (response.Disconnect)
@ -282,7 +282,7 @@ namespace Microsoft.AspNet.SignalR.Transports
return context.Transport.Send(response).Then(c => OnDisconnectMessage(c), context) return context.Transport.Send(response).Then(c => OnDisconnectMessage(c), context)
.Then(() => TaskAsyncHelper.False); .Then(() => TaskAsyncHelper.False);
} }
else if (response.TimedOut || response.Aborted) else if (context.Transport.IsTimedOut || response.Aborted)
{ {
context.Registration.Dispose(); context.Registration.Dispose();

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting; using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Infrastructure; using Microsoft.AspNet.SignalR.Infrastructure;
@ -252,7 +253,7 @@ namespace Microsoft.AspNet.SignalR.Transports
{ {
var context = (MessageContext)state; var context = (MessageContext)state;
response.TimedOut = context.Transport.IsTimedOut; response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested;
Task task = TaskAsyncHelper.Empty; Task task = TaskAsyncHelper.Empty;

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.SignalR.Transports
private readonly Action<TextWriter> _writeCursor; private readonly Action<TextWriter> _writeCursor;
public PersistentResponse() public PersistentResponse()
: this(message => true, writer => { }) : this(message => false, writer => { })
{ {
} }
@ -61,9 +61,10 @@ namespace Microsoft.AspNet.SignalR.Transports
public bool Aborted { get; set; } public bool Aborted { get; set; }
/// <summary> /// <summary>
/// True if the connection timed out. /// True if the client should try reconnecting.
/// </summary> /// </summary>
public bool TimedOut { get; set; } // This is set when the host is shutting down.
public bool Reconnect { get; set; }
/// <summary> /// <summary>
/// Signed token representing the list of groups. Updates on change. /// Signed token representing the list of groups. Updates on change.
@ -106,7 +107,7 @@ namespace Microsoft.AspNet.SignalR.Transports
jsonWriter.WriteValue(1); jsonWriter.WriteValue(1);
} }
if (TimedOut) if (Reconnect)
{ {
jsonWriter.WritePropertyName("T"); jsonWriter.WritePropertyName("T");
jsonWriter.WriteValue(1); jsonWriter.WriteValue(1);

View File

@ -130,6 +130,14 @@ namespace Microsoft.AspNet.SignalR.Transports
} }
} }
protected CancellationToken HostShutdownToken
{
get
{
return _hostShutdownToken;
}
}
public bool IsTimedOut public bool IsTimedOut
{ {
get get
@ -186,7 +194,7 @@ namespace Microsoft.AspNet.SignalR.Transports
protected virtual TextWriter CreateResponseWriter() protected virtual TextWriter CreateResponseWriter()
{ {
return new BufferTextWriter(Context.Response); return new BinaryTextWriter(Context.Response);
} }
protected void IncrementErrors() protected void IncrementErrors()

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNet.SignalR.Transports
private bool _isAlive = true; private bool _isAlive = true;
private readonly Action<string> _message; private readonly Action<string> _message;
private readonly Action<bool> _closed; private readonly Action _closed;
private readonly Action<Exception> _error; private readonly Action<Exception> _error;
public WebSocketTransport(HostContext context, public WebSocketTransport(HostContext context,
@ -74,28 +74,39 @@ namespace Microsoft.AspNet.SignalR.Transports
public override Task ProcessRequest(ITransportConnection connection) public override Task ProcessRequest(ITransportConnection connection)
{ {
var webSocketRequest = _context.Request as IWebSocketRequest; if (IsAbortRequest)
// Throw if the server implementation doesn't support websockets
if (webSocketRequest == null)
{ {
throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported); return connection.Abort(ConnectionId);
} }
else
return webSocketRequest.AcceptWebSocketRequest(socket =>
{ {
_socket = socket; var webSocketRequest = _context.Request as IWebSocketRequest;
socket.OnClose = _closed;
socket.OnMessage = _message;
socket.OnError = _error;
return ProcessRequestCore(connection); // 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() protected override TextWriter CreateResponseWriter()
{ {
return new BufferTextWriter(_socket); return new BinaryTextWriter(_socket);
} }
public override Task Send(object value) public override Task Send(object value)
@ -113,6 +124,11 @@ namespace Microsoft.AspNet.SignalR.Transports
return Send((object)response); return Send((object)response);
} }
protected internal override Task InitializeResponse(ITransportConnection connection)
{
return _socket.Send("{}");
}
private static Task PerformSend(object state) private static Task PerformSend(object state)
{ {
var context = (WebSocketTransportContext)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); Trace.TraceInformation("CloseSocket({0})", 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();
}
// Require a request to /abort to stop tracking the connection. #2195
_isAlive = false; _isAlive = false;
} }

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting; using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Owin.Infrastructure; using Microsoft.AspNet.SignalR.Owin.Infrastructure;
@ -65,9 +66,19 @@ namespace Microsoft.AspNet.SignalR.Owin
if (!_connection.Authorize(serverRequest)) if (!_connection.Authorize(serverRequest))
{ {
// If we failed to authorize the request then return a 403 since the request IPrincipal user = hostContext.Request.User;
// can't do anything if (user != null && user.Identity.IsAuthenticated)
return EndResponse(environment, 403, "Forbidden"); {
// 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 else
{ {

View File

@ -7,6 +7,7 @@ using System.Security.Principal;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Owin.Infrastructure; using Microsoft.AspNet.SignalR.Owin.Infrastructure;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Owin namespace Microsoft.AspNet.SignalR.Owin
{ {
@ -138,15 +139,17 @@ namespace Microsoft.AspNet.SignalR.Owin
} }
#if NET45 #if NET45
public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback) public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask)
{ {
var accept = _environment.Get<Action<IDictionary<string, object>, WebSocketFunc>>(OwinConstants.WebSocketAccept); var accept = _environment.Get<Action<IDictionary<string, object>, WebSocketFunc>>(OwinConstants.WebSocketAccept);
if (accept == null) 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); accept(null, handler.ProcessRequestAsync);
return TaskAsyncHelper.Empty; return TaskAsyncHelper.Empty;
} }

View File

@ -27,6 +27,18 @@ namespace Microsoft.AspNet.SignalR.Owin
get { return _callCancelled; } get { return _callCancelled; }
} }
public int StatusCode
{
get
{
return _environment.Get<int>(OwinConstants.ResponseStatusCode);
}
set
{
_environment[OwinConstants.ResponseStatusCode] = value;
}
}
public string ContentType public string ContentType
{ {
get { return ResponseHeaders.GetHeader("Content-Type"); } get { return ResponseHeaders.GetHeader("Content-Type"); }

View File

@ -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;
}
}
}

View File

@ -50,7 +50,6 @@
<Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs"> <Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="NoOpPerformanceCounterManager.cs" />
<Compile Include="NzbDronePersistentConnection.cs" /> <Compile Include="NzbDronePersistentConnection.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Serializer.cs" /> <Compile Include="Serializer.cs" />

View File

@ -1,6 +1,5 @@
using System; using System;
using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
namespace NzbDrone.SignalR namespace NzbDrone.SignalR
@ -16,7 +15,6 @@ namespace NzbDrone.SignalR
private SignalrDependencyResolver(IContainer container) private SignalrDependencyResolver(IContainer container)
{ {
container.RegisterSingleton(typeof(IPerformanceCounterManager), typeof(NoOpPerformanceCounterManager));
_container = container; _container = container;
} }