imported signalr 1.1.3 into NzbDrone.

This commit is contained in:
kayone
2013-11-21 21:26:57 -08:00
parent 891443e05d
commit 0e623e7ce4
236 changed files with 20490 additions and 35 deletions
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public class AckHandler : IAckHandler, IDisposable
{
private readonly ConcurrentDictionary<string, AckInfo> _acks = new ConcurrentDictionary<string, AckInfo>();
// REVIEW: Consider making this pluggable
private readonly TimeSpan _ackThreshold;
// REVIEW: Consider moving this logic to the transport heartbeat
private Timer _timer;
public AckHandler()
: this(completeAcksOnTimeout: true,
ackThreshold: TimeSpan.FromSeconds(30),
ackInterval: TimeSpan.FromSeconds(5))
{
}
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Acks", Justification = "Ack is a well known term")]
public AckHandler(bool completeAcksOnTimeout, TimeSpan ackThreshold, TimeSpan ackInterval)
{
if (completeAcksOnTimeout)
{
_timer = new Timer(_ => CheckAcks(), state: null, dueTime: ackInterval, period: ackInterval);
}
_ackThreshold = ackThreshold;
}
public Task CreateAck(string id)
{
return _acks.GetOrAdd(id, _ => new AckInfo()).Tcs.Task;
}
public bool TriggerAck(string id)
{
AckInfo info;
if (_acks.TryRemove(id, out info))
{
info.Tcs.TrySetResult(null);
return true;
}
return false;
}
private void CheckAcks()
{
foreach (var pair in _acks)
{
TimeSpan elapsed = DateTime.UtcNow - pair.Value.Created;
if (elapsed > _ackThreshold)
{
AckInfo info;
if (_acks.TryRemove(pair.Key, out info))
{
// If we have a pending ack for longer than the threshold
// cancel it.
info.Tcs.TrySetCanceled();
}
}
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_timer != null)
{
_timer.Dispose();
}
// Trip all pending acks
foreach (var pair in _acks)
{
AckInfo info;
if (_acks.TryRemove(pair.Key, out info))
{
info.Tcs.TrySetCanceled();
}
}
}
}
public void Dispose()
{
Dispose(true);
}
private class AckInfo
{
public TaskCompletionSource<object> Tcs { get; private set; }
public DateTime Created { get; private set; }
public AckInfo()
{
Tcs = new TaskCompletionSource<object>();
Created = DateTime.UtcNow;
}
}
}
}
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.IO;
using System.Text;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public class ArraySegmentTextReader : TextReader
{
private readonly ArraySegment<byte> _buffer;
private readonly Encoding _encoding;
private int _offset;
public ArraySegmentTextReader(ArraySegment<byte> buffer, Encoding encoding)
{
_buffer = buffer;
_encoding = encoding;
_offset = _buffer.Offset;
}
public override int Read(char[] buffer, int index, int count)
{
int bytesCount = _encoding.GetByteCount(buffer, index, count);
int bytesToRead = Math.Min(_buffer.Count - _offset, bytesCount);
int read = _encoding.GetChars(_buffer.Array, _offset, bytesToRead, buffer, index);
_offset += bytesToRead;
return read;
}
}
}
@@ -0,0 +1,192 @@
// 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.IO;
using System.Text;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// TextWriter implementation over a write delegate optimized for writing in small chunks
/// we don't need to write to a long lived buffer. This saves massive amounts of memory
/// as the number of connections grows.
/// </summary>
internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter
{
private readonly Encoding _encoding;
private readonly Action<ArraySegment<byte>, object> _write;
private readonly object _writeState;
private readonly bool _reuseBuffers;
private ChunkedWriter _writer;
private int _bufferSize;
public BufferTextWriter(IResponse response) :
this((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128)
{
}
public BufferTextWriter(IWebSocket socket) :
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 128)
{
}
[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)
{
_write = write;
_writeState = state;
_encoding = new UTF8Encoding();
_reuseBuffers = reuseBuffers;
_bufferSize = bufferSize;
}
private ChunkedWriter Writer
{
get
{
if (_writer == null)
{
_writer = new ChunkedWriter(_write, _writeState, _bufferSize, _encoding, _reuseBuffers);
}
return _writer;
}
}
public override Encoding Encoding
{
get { return _encoding; }
}
public override void Write(string value)
{
Writer.Write(value);
}
public override void WriteLine(string value)
{
Writer.Write(value);
}
public override void Write(char value)
{
Writer.Write(value);
}
public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
public override void Flush()
{
Writer.Flush();
}
private class ChunkedWriter
{
private int _charPos;
private int _charLen;
private readonly Encoder _encoder;
private readonly char[] _charBuffer;
private readonly byte[] _byteBuffer;
private readonly Action<ArraySegment<byte>, object> _write;
private readonly object _writeState;
public ChunkedWriter(Action<ArraySegment<byte>, object> write, object state, int chunkSize, Encoding encoding, bool reuseBuffers)
{
_charLen = chunkSize;
_charBuffer = new char[chunkSize];
_write = write;
_writeState = state;
_encoder = encoding.GetEncoder();
if (reuseBuffers)
{
_byteBuffer = new byte[encoding.GetMaxByteCount(chunkSize)];
}
}
public void Write(char value)
{
if (_charPos == _charLen)
{
Flush(flushEncoder: false);
}
_charBuffer[_charPos++] = value;
}
public void Write(string value)
{
int length = value.Length;
int sourceIndex = 0;
while (length > 0)
{
if (_charPos == _charLen)
{
Flush(flushEncoder: false);
}
int count = _charLen - _charPos;
if (count > length)
{
count = length;
}
value.CopyTo(sourceIndex, _charBuffer, _charPos, count);
_charPos += count;
sourceIndex += count;
length -= count;
}
}
public void Write(ArraySegment<byte> data)
{
Flush();
_write(data, _writeState);
}
public void Flush()
{
Flush(flushEncoder: true);
}
private void Flush(bool flushEncoder)
{
// If it's safe to reuse the buffer then do so
if (_byteBuffer != null)
{
Flush(_byteBuffer, flushEncoder);
}
else
{
// Allocate a byte array of the right size for this char buffer
int byteCount = _encoder.GetByteCount(_charBuffer, 0, _charPos, flush: false);
var byteBuffer = new byte[byteCount];
Flush(byteBuffer, flushEncoder);
}
}
private void Flush(byte[] byteBuffer, bool flushEncoder)
{
int count = _encoder.GetBytes(_charBuffer, 0, _charPos, byteBuffer, 0, flush: flushEncoder);
_charPos = 0;
if (count > 0)
{
_write(new ArraySegment<byte>(byteBuffer, 0, count), _writeState);
}
}
}
}
}
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal static class CancellationTokenExtensions
{
public static IDisposable SafeRegister(this CancellationToken cancellationToken, Action<object> 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);
var disposeCancellationState = new DiposeCancellationState(callbackWrapper, registration);
// Ensure delegate continues to use the C# Compiler static delegate caching optimization.
return new DisposableAction(s => Dispose(s), disposeCancellationState);
}
private static void Cancel(object state)
{
((CancellationCallbackWrapper)state).TryInvoke();
}
private static void Dispose(object state)
{
((DiposeCancellationState)state).TryDispose();
}
private class DiposeCancellationState
{
private readonly CancellationCallbackWrapper _callbackWrapper;
private readonly CancellationTokenRegistration _registration;
public DiposeCancellationState(CancellationCallbackWrapper callbackWrapper, CancellationTokenRegistration registration)
{
_callbackWrapper = callbackWrapper;
_registration = registration;
}
public void TryDispose()
{
// 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();
}
}
}
private class CancellationCallbackWrapper
{
private readonly Action<object> _callback;
private readonly object _state;
private int _callbackInvoked;
public CancellationCallbackWrapper(Action<object> callback, object state)
{
_callback = callback;
_state = state;
}
public bool TrySetInvoked()
{
return Interlocked.Exchange(ref _callbackInvoked, 1) == 0;
}
public void TryInvoke()
{
if (TrySetInvoked())
{
_callback(_state);
}
}
}
}
}
@@ -0,0 +1,335 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.AspNet.SignalR.Tracing;
using Microsoft.AspNet.SignalR.Transports;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public class Connection : IConnection, ITransportConnection, ISubscriber
{
private readonly IMessageBus _bus;
private readonly IJsonSerializer _serializer;
private readonly string _baseSignal;
private readonly string _connectionId;
private readonly IList<string> _signals;
private readonly DiffSet<string> _groups;
private readonly IPerformanceCounterManager _counters;
private bool _disconnected;
private bool _aborted;
private readonly TraceSource _traceSource;
private readonly IAckHandler _ackHandler;
private readonly IProtectedData _protectedData;
public Connection(IMessageBus newMessageBus,
IJsonSerializer jsonSerializer,
string baseSignal,
string connectionId,
IList<string> signals,
IList<string> groups,
ITraceManager traceManager,
IAckHandler ackHandler,
IPerformanceCounterManager performanceCounterManager,
IProtectedData protectedData)
{
if (traceManager == null)
{
throw new ArgumentNullException("traceManager");
}
_bus = newMessageBus;
_serializer = jsonSerializer;
_baseSignal = baseSignal;
_connectionId = connectionId;
_signals = new List<string>(signals.Concat(groups));
_groups = new DiffSet<string>(groups);
_traceSource = traceManager["SignalR.Connection"];
_ackHandler = ackHandler;
_counters = performanceCounterManager;
_protectedData = protectedData;
}
public string DefaultSignal
{
get
{
return _baseSignal;
}
}
IList<string> ISubscriber.EventKeys
{
get
{
return _signals;
}
}
public event Action<ISubscriber, string> EventKeyAdded;
public event Action<ISubscriber, string> EventKeyRemoved;
public Action<TextWriter> WriteCursor { get; set; }
public string Identity
{
get
{
return _connectionId;
}
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used for debugging purposes.")]
private TraceSource Trace
{
get
{
return _traceSource;
}
}
public Subscription Subscription
{
get;
set;
}
public Task Send(ConnectionMessage message)
{
Message busMessage = CreateMessage(message.Signal, message.Value);
if (message.ExcludedSignals != null)
{
busMessage.Filter = String.Join("|", message.ExcludedSignals);
}
if (busMessage.WaitForAck)
{
Task ackTask = _ackHandler.CreateAck(busMessage.CommandId);
return _bus.Publish(busMessage).Then(task => task, ackTask);
}
return _bus.Publish(busMessage);
}
private Message CreateMessage(string key, object value)
{
var command = value as Command;
ArraySegment<byte> messageBuffer = GetMessageBuffer(value);
var message = new Message(_connectionId, key, messageBuffer);
if (command != null)
{
// Set the command id
message.CommandId = command.Id;
message.WaitForAck = command.WaitForAck;
}
return message;
}
private ArraySegment<byte> GetMessageBuffer(object value)
{
using (var stream = new MemoryStream(128))
{
var bufferWriter = new BufferTextWriter((buffer, state) =>
{
((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count);
},
stream,
reuseBuffers: true,
bufferSize: 1024);
using (bufferWriter)
{
_serializer.Serialize(value, bufferWriter);
bufferWriter.Flush();
return new ArraySegment<byte>(stream.ToArray());
}
}
}
public IDisposable Receive(string messageId, Func<PersistentResponse, object, Task<bool>> callback, int maxMessages, object state)
{
var receiveContext = new ReceiveContext(this, callback, state);
return _bus.Subscribe(this,
messageId,
(result, s) => MessageBusCallback(result, s),
maxMessages,
receiveContext);
}
private static Task<bool> MessageBusCallback(MessageResult result, object state)
{
var context = (ReceiveContext)state;
return context.InvokeCallback(result);
}
private PersistentResponse GetResponse(MessageResult result)
{
// Do a single sweep through the results to process commands and extract values
ProcessResults(result);
Debug.Assert(WriteCursor != null, "Unable to resolve the cursor since the method is null");
var response = new PersistentResponse(ExcludeMessage, WriteCursor);
response.Terminal = result.Terminal;
if (!result.Terminal)
{
// Only set these properties if the message isn't terminal
response.Messages = result.Messages;
response.Disconnect = _disconnected;
response.Aborted = _aborted;
response.TotalCount = result.TotalCount;
}
PopulateResponseState(response);
_counters.ConnectionMessagesReceivedTotal.IncrementBy(result.TotalCount);
_counters.ConnectionMessagesReceivedPerSec.IncrementBy(result.TotalCount);
return response;
}
private bool ExcludeMessage(Message message)
{
if (String.IsNullOrEmpty(message.Filter))
{
return false;
}
string[] exclude = message.Filter.Split('|');
return exclude.Any(signal => Identity.Equals(signal, StringComparison.OrdinalIgnoreCase) ||
_signals.Contains(signal) ||
_groups.Contains(signal));
}
private void ProcessResults(MessageResult result)
{
result.Messages.Enumerate<object>(message => message.IsAck || message.IsCommand,
(state, message) =>
{
if (message.IsAck)
{
_ackHandler.TriggerAck(message.CommandId);
}
else if (message.IsCommand)
{
var command = _serializer.Parse<Command>(message.Value, message.Encoding);
ProcessCommand(command);
// Only send the ack if this command is waiting for it
if (message.WaitForAck)
{
// If we're on the same box and there's a pending ack for this command then
// just trip it
if (!_ackHandler.TriggerAck(message.CommandId))
{
_bus.Ack(_connectionId, message.CommandId).Catch();
}
}
}
}, null);
}
private void ProcessCommand(Command command)
{
switch (command.CommandType)
{
case CommandType.AddToGroup:
{
var name = command.Value;
if (EventKeyAdded != null)
{
_groups.Add(name);
EventKeyAdded(this, name);
}
}
break;
case CommandType.RemoveFromGroup:
{
var name = command.Value;
if (EventKeyRemoved != null)
{
_groups.Remove(name);
EventKeyRemoved(this, name);
}
}
break;
case CommandType.Disconnect:
_disconnected = true;
break;
case CommandType.Abort:
_aborted = true;
break;
}
}
private void PopulateResponseState(PersistentResponse response)
{
PopulateResponseState(response, _groups, _serializer, _protectedData, _connectionId);
}
internal static void PopulateResponseState(PersistentResponse response,
DiffSet<string> groupSet,
IJsonSerializer serializer,
IProtectedData protectedData,
string connectionId)
{
bool anyChanges = groupSet.DetectChanges();
if (anyChanges)
{
// Create a protected payload of the sorted list
IEnumerable<string> groups = groupSet.GetSnapshot();
// Remove group prefixes before any thing goes over the wire
string groupsString = connectionId + ':' + serializer.Stringify(PrefixHelper.RemoveGroupPrefixes(groups)); ;
// The groups token
response.GroupsToken = protectedData.Protect(groupsString, Purposes.Groups);
}
}
private class ReceiveContext
{
private readonly Connection _connection;
private readonly Func<PersistentResponse, object, Task<bool>> _callback;
private readonly object _callbackState;
public ReceiveContext(Connection connection, Func<PersistentResponse, object, Task<bool>> callback, object callbackState)
{
_connection = connection;
_callback = callback;
_callbackState = callbackState;
}
public Task<bool> InvokeCallback(MessageResult result)
{
var response = _connection.GetResponse(result);
return _callback(response, _callbackState);
}
}
}
}
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Messaging;
using Microsoft.AspNet.SignalR.Tracing;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Default <see cref="IConnectionManager"/> implementation.
/// </summary>
public class ConnectionManager : IConnectionManager
{
private readonly IDependencyResolver _resolver;
private readonly IPerformanceCounterManager _counters;
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionManager"/> class.
/// </summary>
/// <param name="resolver">The <see cref="IDependencyResolver"/>.</param>
public ConnectionManager(IDependencyResolver resolver)
{
_resolver = resolver;
_counters = _resolver.Resolve<IPerformanceCounterManager>();
}
/// <summary>
/// Returns a <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.
/// </summary>
/// <typeparam name="T">Type of the <see cref="PersistentConnection"/></typeparam>
/// <returns>A <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.</returns>
public IPersistentConnectionContext GetConnectionContext<T>() where T : PersistentConnection
{
return GetConnection(typeof(T));
}
/// <summary>
/// Returns a <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.
/// </summary>
/// <param name="type">Type of the <see cref="PersistentConnection"/></param>
/// <returns>A <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.</returns>
public IPersistentConnectionContext GetConnection(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
string rawConnectionName = type.FullName;
string connectionName = PrefixHelper.GetPersistentConnectionName(rawConnectionName);
IConnection connection = GetConnectionCore(connectionName);
return new PersistentConnectionContext(connection, new GroupManager(connection, PrefixHelper.GetPersistentConnectionGroupName(rawConnectionName)));
}
/// <summary>
/// Returns a <see cref="IHubContext"/> for the specified <see cref="IHub"/>.
/// </summary>
/// <typeparam name="T">Type of the <see cref="IHub"/></typeparam>
/// <returns>a <see cref="IHubContext"/> for the specified <see cref="IHub"/></returns>
public IHubContext GetHubContext<T>() where T : IHub
{
return GetHubContext(typeof(T).GetHubName());
}
/// <summary>
/// Returns a <see cref="IHubContext"/>for the specified hub.
/// </summary>
/// <param name="hubName">Name of the hub</param>
/// <returns>a <see cref="IHubContext"/> for the specified hub</returns>
public IHubContext GetHubContext(string hubName)
{
var connection = GetConnectionCore(connectionName: null);
var hubManager = _resolver.Resolve<IHubManager>();
var pipelineInvoker = _resolver.Resolve<IHubPipelineInvoker>();
hubManager.EnsureHub(hubName,
_counters.ErrorsHubResolutionTotal,
_counters.ErrorsHubResolutionPerSec,
_counters.ErrorsAllTotal,
_counters.ErrorsAllPerSec);
Func<string, ClientHubInvocation, IList<string>, Task> send = (signal, value, exclude) => pipelineInvoker.Send(new HubOutgoingInvokerContext(connection, signal, value, exclude));
return new HubContext(send, hubName, connection);
}
internal Connection GetConnectionCore(string connectionName)
{
IList<string> signals = connectionName == null ? ListHelper<string>.Empty : new[] { connectionName };
// Give this a unique id
var connectionId = Guid.NewGuid().ToString();
return new Connection(_resolver.Resolve<IMessageBus>(),
_resolver.Resolve<IJsonSerializer>(),
connectionName,
connectionId,
signals,
ListHelper<string>.Empty,
_resolver.Resolve<ITraceManager>(),
_resolver.Resolve<IAckHandler>(),
_resolver.Resolve<IPerformanceCounterManager>(),
_resolver.Resolve<IProtectedData>());
}
}
}
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public class DefaultProtectedData : IProtectedData
{
private static readonly UTF8Encoding _encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
public string Protect(string data, string purpose)
{
byte[] purposeBytes = _encoding.GetBytes(purpose);
byte[] unprotectedBytes = _encoding.GetBytes(data);
byte[] protectedBytes = ProtectedData.Protect(unprotectedBytes, purposeBytes, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(protectedBytes);
}
public string Unprotect(string protectedValue, string purpose)
{
byte[] purposeBytes = _encoding.GetBytes(purpose);
byte[] protectedBytes = Convert.FromBase64String(protectedValue);
byte[] unprotectedBytes = ProtectedData.Unprotect(protectedBytes, purposeBytes, DataProtectionScope.CurrentUser);
return _encoding.GetString(unprotectedBytes);
}
}
}
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal struct DiffPair<T>
{
public ICollection<T> Added;
public ICollection<T> Removed;
public bool AnyChanges
{
get
{
return Added.Count > 0 || Removed.Count > 0;
}
}
}
}
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class DiffSet<T>
{
private readonly HashSet<T> _items;
private readonly HashSet<T> _addedItems;
private readonly HashSet<T> _removedItems;
public DiffSet(IEnumerable<T> items)
{
_addedItems = new HashSet<T>();
_removedItems = new HashSet<T>();
_items = new HashSet<T>(items);
}
public bool Add(T item)
{
if (_items.Add(item))
{
if (!_removedItems.Remove(item))
{
_addedItems.Add(item);
}
return true;
}
return false;
}
public bool Remove(T item)
{
if (_items.Remove(item))
{
if (!_addedItems.Remove(item))
{
_removedItems.Add(item);
}
return true;
}
return false;
}
public bool Contains(T item)
{
return _items.Contains(item);
}
public ICollection<T> GetSnapshot()
{
return _items;
}
public bool DetectChanges()
{
bool anyChanges = _addedItems.Count > 0 || _removedItems.Count > 0;
_addedItems.Clear();
_removedItems.Clear();
return anyChanges;
}
}
}
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class DisposableAction : IDisposable
{
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "The client projects use this.")]
public static readonly DisposableAction Empty = new DisposableAction(() => { });
private Action<object> _action;
private readonly object _state;
public DisposableAction(Action action)
: this(state => ((Action)state).Invoke(), state: action)
{
}
public DisposableAction(Action<object> action, object state)
{
_action = action;
_state = state;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Interlocked.Exchange(ref _action, (state) => { }).Invoke(_state);
}
}
public void Dispose()
{
Dispose(true);
}
}
}
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Helper class to manage disposing a resource at an arbirtary time
/// </summary>
internal class Disposer : IDisposable
{
private static readonly object _disposedSentinel = new object();
private object _disposable;
public void Set(IDisposable disposable)
{
if (disposable == null)
{
throw new ArgumentNullException("disposable");
}
object originalFieldValue = Interlocked.CompareExchange(ref _disposable, disposable, null);
if (originalFieldValue == null)
{
// this is the first call to Set() and Dispose() hasn't yet been called; do nothing
}
else if (originalFieldValue == _disposedSentinel)
{
// Dispose() has already been called, so we need to dispose of the object that was just added
disposable.Dispose();
}
else
{
#if !NET35 && !SILVERLIGHT && !NETFX_CORE
// Set has been called multiple times, fail
Debug.Fail("Multiple calls to Disposer.Set(IDisposable) without calling Disposer.Dispose()");
#endif
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
var disposable = Interlocked.Exchange(ref _disposable, _disposedSentinel) as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}
public void Dispose()
{
Dispose(true);
}
}
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal static class ExceptionsExtensions
{
internal static Exception Unwrap(this Exception ex)
{
if (ex == null)
{
return null;
}
var next = ex.GetBaseException();
while (next.InnerException != null)
{
// On mono GetBaseException() doesn't seem to do anything
// so just walk the inner exception chain.
next = next.InnerException;
}
return next;
}
}
}
@@ -0,0 +1,15 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public interface IAckHandler
{
Task CreateAck(string id);
bool TriggerAck(string id);
}
}
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Implemented on anything that has the ability to write raw binary data
/// </summary>
public interface IBinaryWriter
{
void Write(ArraySegment<byte> data);
}
}
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.SignalR.Hubs;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Provides access to hubs and persistent connections references.
/// </summary>
public interface IConnectionManager
{
/// <summary>
/// Returns a <see cref="IHubContext"/> for the specified <see cref="IHub"/>.
/// </summary>
/// <typeparam name="T">Type of the <see cref="IHub"/></typeparam>
/// <returns>a <see cref="IHubContext"/> for the specified <see cref="IHub"/></returns>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The hub type needs to be specified")]
IHubContext GetHubContext<T>() where T : IHub;
/// <summary>
/// Returns a <see cref="IHubContext"/>for the specified hub.
/// </summary>
/// <param name="hubName">Name of the hub</param>
/// <returns>a <see cref="IHubContext"/> for the specified hub</returns>
IHubContext GetHubContext(string hubName);
/// <summary>
/// Returns a <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.
/// </summary>
/// <typeparam name="T">Type of the <see cref="PersistentConnection"/></typeparam>
/// <returns>A <see cref="IPersistentConnectionContext"/> for the <see cref="PersistentConnection"/>.</returns>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The connection type needs to be specified")]
IPersistentConnectionContext GetConnectionContext<T>() where T : PersistentConnection;
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Diagnostics;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public interface IPerformanceCounter
{
string CounterName { get; }
long Decrement();
long Increment();
long IncrementBy(long value);
CounterSample NextSample();
long RawValue { get; set; }
void Close();
void RemoveInstance();
}
}
@@ -0,0 +1,194 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Diagnostics;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Provides access to performance counters.
/// </summary>
public interface IPerformanceCounterManager
{
/// <summary>
/// Initializes the performance counters.
/// </summary>
/// <param name="instanceName">The host instance name.</param>
/// <param name="hostShutdownToken">The CancellationToken representing the host shutdown.</param>
void Initialize(string instanceName, CancellationToken hostShutdownToken);
/// <summary>
/// Loads a performance counter.
/// </summary>
/// <param name="categoryName">The category name.</param>
/// <param name="counterName">The counter name.</param>
/// <param name="instanceName">The instance name.</param>
/// <param name="isReadOnly">Whether the counter is read-only.</param>
IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly);
/// <summary>
/// Gets the performance counter representing the total number of connection Connect events since the application was started.
/// </summary>
IPerformanceCounter ConnectionsConnected { get; }
/// <summary>
/// Gets the performance counter representing the total number of connection Reconnect events since the application was started.
/// </summary>
IPerformanceCounter ConnectionsReconnected { get; }
/// <summary>
/// Gets the performance counter representing the total number of connection Disconnect events since the application was started.
/// </summary>
IPerformanceCounter ConnectionsDisconnected { get; }
/// <summary>
/// Gets the performance counter representing the number of connections currently connected.
/// </summary>
IPerformanceCounter ConnectionsCurrent { get; }
/// <summary>
/// Gets the performance counter representing the total number of messages received by connections (server to client) since the application was started.
/// </summary>
IPerformanceCounter ConnectionMessagesReceivedTotal { get; }
/// <summary>
/// Gets the performance counter representing the total number of messages received by connections (server to client) since the application was started.
/// </summary>
IPerformanceCounter ConnectionMessagesSentTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of messages received by connections (server to client) per second.
/// </summary>
IPerformanceCounter ConnectionMessagesReceivedPerSec { get; }
/// <summary>
/// Gets the performance counter representing the number of messages sent by connections (client to server) per second.
/// </summary>
IPerformanceCounter ConnectionMessagesSentPerSec { get; }
/// <summary>
/// Gets the performance counter representing the total number of messages received by subscribers since the application was started.
/// </summary>
IPerformanceCounter MessageBusMessagesReceivedTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of messages received by a subscribers per second.
/// </summary>
IPerformanceCounter MessageBusMessagesReceivedPerSec { get; }
/// <summary>
/// Gets the performance counter representing the number of messages received by the scaleout message bus per second.
/// </summary>
IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec { get; }
/// <summary>
/// Gets the performance counter representing the total number of messages published to the message bus since the application was started.
/// </summary>
IPerformanceCounter MessageBusMessagesPublishedTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of messages published to the message bus per second.
/// </summary>
IPerformanceCounter MessageBusMessagesPublishedPerSec { get; }
/// <summary>
/// Gets the performance counter representing the current number of subscribers to the message bus.
/// </summary>
IPerformanceCounter MessageBusSubscribersCurrent { get; }
/// <summary>
/// Gets the performance counter representing the total number of subscribers to the message bus since the application was started.
/// </summary>
IPerformanceCounter MessageBusSubscribersTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of new subscribers to the message bus per second.
/// </summary>
IPerformanceCounter MessageBusSubscribersPerSec { get; }
/// <summary>
/// Gets the performance counter representing the number of workers allocated to deliver messages in the message bus.
/// </summary>
IPerformanceCounter MessageBusAllocatedWorkers { get; }
/// <summary>
/// Gets the performance counter representing the number of workers currently busy delivering messages in the message bus.
/// </summary>
IPerformanceCounter MessageBusBusyWorkers { get; }
/// <summary>
/// Gets the performance counter representing representing the current number of topics in the message bus.
/// </summary>
IPerformanceCounter MessageBusTopicsCurrent { get; }
/// <summary>
/// Gets the performance counter representing the total number of all errors processed since the application was started.
/// </summary>
IPerformanceCounter ErrorsAllTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of all errors processed per second.
/// </summary>
IPerformanceCounter ErrorsAllPerSec { get; }
/// <summary>
/// Gets the performance counter representing the total number of hub resolution errors processed since the application was started.
/// </summary>
IPerformanceCounter ErrorsHubResolutionTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of hub resolution errors per second.
/// </summary>
IPerformanceCounter ErrorsHubResolutionPerSec { get; }
/// <summary>
/// Gets the performance counter representing the total number of hub invocation errors processed since the application was started.
/// </summary>
IPerformanceCounter ErrorsHubInvocationTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of hub invocation errors per second.
/// </summary>
IPerformanceCounter ErrorsHubInvocationPerSec { get; }
/// <summary>
/// Gets the performance counter representing the total number of transport errors processed since the application was started.
/// </summary>
IPerformanceCounter ErrorsTransportTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of transport errors per second.
/// </summary>
IPerformanceCounter ErrorsTransportPerSec { get; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider.
/// </summary>
IPerformanceCounter ScaleoutStreamCountTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider that are in the open state.
/// </summary>
IPerformanceCounter ScaleoutStreamCountOpen { get; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider that are in the buffering state.
/// </summary>
IPerformanceCounter ScaleoutStreamCountBuffering { get; }
/// <summary>
/// Gets the performance counter representing the total number of scaleout errors since the application was started.
/// </summary>
IPerformanceCounter ScaleoutErrorsTotal { get; }
/// <summary>
/// Gets the performance counter representing the number of scaleout errors per second.
/// </summary>
IPerformanceCounter ScaleoutErrorsPerSec { get; }
/// <summary>
/// Gets the performance counter representing the current scaleout send queue length.
/// </summary>
IPerformanceCounter ScaleoutSendQueueLength { get; }
}
}
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public interface IProtectedData
{
string Protect(string data, string purpose);
string Unprotect(string protectedValue, string purpose);
}
}
@@ -0,0 +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.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Handles commands from server to server.
/// </summary>
internal interface IServerCommandHandler
{
/// <summary>
/// Sends a command to all connected servers.
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
Task SendCommand(ServerCommand command);
/// <summary>
/// Gets or sets a callback that is invoked when a command is received.
/// </summary>
Action<ServerCommand> Command { get; set; }
}
}
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Generates a server id
/// </summary>
public interface IServerIdManager
{
/// <summary>
/// The id of the server.
/// </summary>
string ServerId { get; }
}
}
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public interface IStringMinifier
{
/// <summary>
/// Minifies a string in a way that can be reversed by this instance of <see cref="IStringMinifier"/>.
/// </summary>
/// <param name="value">The string to be minified</param>
/// <returns>A minified representation of the <paramref name="value"/> without the following characters:,|\</returns>
string Minify(string value);
/// <summary>
/// Reverses a <see cref="Minify"/> call that was executed at least once previously on this instance of
/// <see cref="IStringMinifier"/> without any subsequent calls to <see cref="RemoveUnminified"/> sharing the
/// same argument as the <see cref="Minify"/> call that returned <paramref name="value"/>.
/// </summary>
/// <param name="value">
/// A minified string that was returned by a previous call to <see cref="Minify"/>.
/// </param>
/// <returns>
/// The argument of all previous calls to <see cref="Minify"/> that returned <paramref name="value"/>.
/// If every call to <see cref="Minify"/> on this instance of <see cref="IStringMinifier"/> has never
/// returned <paramref name="value"/> or if the most recent call to <see cref="Minify"/> that did
/// return <paramref name="value"/> was followed by a call to <see cref="RemoveUnminified"/> sharing
/// the same argument, <see cref="Unminify"/> may return null but must not throw.
/// </returns>
string Unminify(string value);
/// <summary>
/// A call to this function indicates that any future attempt to unminify strings that were previously minified
/// from <paramref name="value"/> may be met with a null return value. This provides an opportunity clean up
/// any internal data structures that reference <paramref name="value"/>.
/// </summary>
/// <param name="value">The string that may have previously have been minified.</param>
void RemoveUnminified(string value);
}
}
@@ -0,0 +1,16 @@
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public static class InterlockedHelper
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification="This is an interlocked helper...")]
public static bool CompareExchangeOr(ref int location, int value, int comparandA, int comparandB)
{
return Interlocked.CompareExchange(ref location, value, comparandA) == comparandA ||
Interlocked.CompareExchange(ref location, value, comparandB) == comparandB;
}
}
}
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class ListHelper<T>
{
public static readonly IList<T> Empty = new ReadOnlyCollection<T>(new List<T>());
}
}
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Diagnostics;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class NoOpPerformanceCounter : IPerformanceCounter
{
public string CounterName
{
get
{
return GetType().Name;
}
}
public long Decrement()
{
return 0;
}
public long Increment()
{
return 0;
}
public long IncrementBy(long value)
{
return 0;
}
public long RawValue
{
get { return 0; }
set { }
}
public void Close()
{
}
public void RemoveInstance()
{
}
public CounterSample NextSample()
{
return CounterSample.Empty;
}
}
}
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
internal sealed class PerformanceCounterAttribute : Attribute
{
public string Name { get; set; }
public string Description { get; set; }
public PerformanceCounterType CounterType { get; set; }
}
}
@@ -0,0 +1,420 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
#if !UTILS
using Microsoft.AspNet.SignalR.Tracing;
#endif
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Manages performance counters using Windows performance counters.
/// </summary>
public class PerformanceCounterManager : IPerformanceCounterManager
{
/// <summary>
/// The performance counter category name for SignalR counters.
/// </summary>
public const string CategoryName = "SignalR";
private readonly static PropertyInfo[] _counterProperties = GetCounterPropertyInfo();
private readonly static IPerformanceCounter _noOpCounter = new NoOpPerformanceCounter();
private volatile bool _initialized;
private object _initLocker = new object();
#if !UTILS
private readonly TraceSource _trace;
public PerformanceCounterManager(DefaultDependencyResolver resolver)
: this(resolver.Resolve<ITraceManager>())
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
public PerformanceCounterManager(ITraceManager traceManager)
: this()
{
if (traceManager == null)
{
throw new ArgumentNullException("traceManager");
}
_trace = traceManager["SignalR.PerformanceCounterManager"];
}
#endif
public PerformanceCounterManager()
{
InitNoOpCounters();
}
/// <summary>
/// Gets the performance counter representing the total number of connection Connect events since the application was started.
/// </summary>
[PerformanceCounter(Name = "Connections Connected", Description = "The total number of connection Connect events since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ConnectionsConnected { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of connection Reconnect events since the application was started.
/// </summary>
[PerformanceCounter(Name = "Connections Reconnected", Description = "The total number of connection Reconnect events since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ConnectionsReconnected { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of connection Disconnect events since the application was started.
/// </summary>
[PerformanceCounter(Name = "Connections Disconnected", Description = "The total number of connection Disconnect events since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ConnectionsDisconnected { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of connections currently connected.
/// </summary>
[PerformanceCounter(Name = "Connections Current", Description = "The number of connections currently connected.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ConnectionsCurrent { get; private set; }
/// <summary>
/// Gets the performance counter representing the toal number of messages received by connections (server to client) since the application was started.
/// </summary>
[PerformanceCounter(Name = "Connection Messages Received Total", Description = "The toal number of messages received by connections (server to client) since the application was started.", CounterType = PerformanceCounterType.NumberOfItems64)]
public IPerformanceCounter ConnectionMessagesReceivedTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of messages sent by connections (client to server) since the application was started.
/// </summary>
[PerformanceCounter(Name = "Connection Messages Sent Total", Description = "The total number of messages sent by connections (client to server) since the application was started.", CounterType = PerformanceCounterType.NumberOfItems64)]
public IPerformanceCounter ConnectionMessagesSentTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of messages received by connections (server to client) per second.
/// </summary>
[PerformanceCounter(Name = "Connection Messages Received/Sec", Description = "The number of messages received by connections (server to client) per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ConnectionMessagesReceivedPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of messages sent by connections (client to server) per second.
/// </summary>
[PerformanceCounter(Name = "Connection Messages Sent/Sec", Description = "The number of messages sent by connections (client to server) per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ConnectionMessagesSentPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of messages received by subscribers since the application was started.
/// </summary>
[PerformanceCounter(Name = "Message Bus Messages Received Total", Description = "The total number of messages received by subscribers since the application was started.", CounterType = PerformanceCounterType.NumberOfItems64)]
public IPerformanceCounter MessageBusMessagesReceivedTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of messages received by a subscribers per second.
/// </summary>
[PerformanceCounter(Name = "Message Bus Messages Received/Sec", Description = "The number of messages received by subscribers per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter MessageBusMessagesReceivedPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of messages received by the scaleout message bus per second.
/// </summary>
[PerformanceCounter(Name = "Scaleout Message Bus Messages Received/Sec", Description = "The number of messages received by the scaleout message bus per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ScaleoutMessageBusMessagesReceivedPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of messages published to the message bus since the application was started.
/// </summary>
[PerformanceCounter(Name = "Messages Bus Messages Published Total", Description = "The total number of messages published to the message bus since the application was started.", CounterType = PerformanceCounterType.NumberOfItems64)]
public IPerformanceCounter MessageBusMessagesPublishedTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of messages published to the message bus per second.
/// </summary>
[PerformanceCounter(Name = "Messages Bus Messages Published/Sec", Description = "The number of messages published to the message bus per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter MessageBusMessagesPublishedPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the current number of subscribers to the message bus.
/// </summary>
[PerformanceCounter(Name = "Message Bus Subscribers Current", Description = "The current number of subscribers to the message bus.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter MessageBusSubscribersCurrent { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of subscribers to the message bus since the application was started.
/// </summary>
[PerformanceCounter(Name = "Message Bus Subscribers Total", Description = "The total number of subscribers to the message bus since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter MessageBusSubscribersTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of new subscribers to the message bus per second.
/// </summary>
[PerformanceCounter(Name = "Message Bus Subscribers/Sec", Description = "The number of new subscribers to the message bus per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter MessageBusSubscribersPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of workers allocated to deliver messages in the message bus.
/// </summary>
[PerformanceCounter(Name = "Message Bus Allocated Workers", Description = "The number of workers allocated to deliver messages in the message bus.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter MessageBusAllocatedWorkers { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of workers currently busy delivering messages in the message bus.
/// </summary>
[PerformanceCounter(Name = "Message Bus Busy Workers", Description = "The number of workers currently busy delivering messages in the message bus.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter MessageBusBusyWorkers { get; private set; }
/// <summary>
/// Gets the performance counter representing representing the current number of topics in the message bus.
/// </summary>
[PerformanceCounter(Name = "Message Bus Topics Current", Description = "The number of topics in the message bus.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter MessageBusTopicsCurrent { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of all errors processed since the application was started.
/// </summary>
[PerformanceCounter(Name = "Errors: All Total", Description = "The total number of all errors processed since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ErrorsAllTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of all errors processed per second.
/// </summary>
[PerformanceCounter(Name = "Errors: All/Sec", Description = "The number of all errors processed per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ErrorsAllPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of hub resolution errors processed since the application was started.
/// </summary>
[PerformanceCounter(Name = "Errors: Hub Resolution Total", Description = "The total number of hub resolution errors processed since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ErrorsHubResolutionTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of hub resolution errors per second.
/// </summary>
[PerformanceCounter(Name = "Errors: Hub Resolution/Sec", Description = "The number of hub resolution errors per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ErrorsHubResolutionPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of hub invocation errors processed since the application was started.
/// </summary>
[PerformanceCounter(Name = "Errors: Hub Invocation Total", Description = "The total number of hub invocation errors processed since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ErrorsHubInvocationTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of hub invocation errors per second.
/// </summary>
[PerformanceCounter(Name = "Errors: Hub Invocation/Sec", Description = "The number of hub invocation errors per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ErrorsHubInvocationPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of transport errors processed since the application was started.
/// </summary>
[PerformanceCounter(Name = "Errors: Tranport Total", Description = "The total number of transport errors processed since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ErrorsTransportTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of transport errors per second.
/// </summary>
[PerformanceCounter(Name = "Errors: Transport/Sec", Description = "The number of transport errors per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ErrorsTransportPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider.
/// </summary>
[PerformanceCounter(Name = "Scaleout Streams Total", Description = "The number of logical streams in the currently configured scaleout message bus provider.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ScaleoutStreamCountTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider that are in the open state.
/// </summary>
[PerformanceCounter(Name = "Scaleout Streams Open", Description = "The number of logical streams in the currently configured scaleout message bus provider that are in the open state", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ScaleoutStreamCountOpen { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of logical streams in the currently configured scaleout message bus provider that are in the buffering state.
/// </summary>
[PerformanceCounter(Name = "Scaleout Streams Buffering", Description = "The number of logical streams in the currently configured scaleout message bus provider that are in the buffering state", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ScaleoutStreamCountBuffering { get; private set; }
/// <summary>
/// Gets the performance counter representing the total number of scaleout errors since the application was started.
/// </summary>
[PerformanceCounter(Name = "Scaleout Errors Total", Description = "The total number of scaleout errors since the application was started.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ScaleoutErrorsTotal { get; private set; }
/// <summary>
/// Gets the performance counter representing the number of scaleout errors per second.
/// </summary>
[PerformanceCounter(Name = "Scaleout Errors/Sec", Description = "The number of scaleout errors per second.", CounterType = PerformanceCounterType.RateOfCountsPerSecond32)]
public IPerformanceCounter ScaleoutErrorsPerSec { get; private set; }
/// <summary>
/// Gets the performance counter representing the current scaleout send queue length.
/// </summary>
[PerformanceCounter(Name = "Scaleout Send Queue Length", Description = "The current scaleout send queue length.", CounterType = PerformanceCounterType.NumberOfItems32)]
public IPerformanceCounter ScaleoutSendQueueLength { get; private set; }
/// <summary>
/// Initializes the performance counters.
/// </summary>
/// <param name="instanceName">The host instance name.</param>
/// <param name="hostShutdownToken">The CancellationToken representing the host shutdown.</param>
public void Initialize(string instanceName, CancellationToken hostShutdownToken)
{
if (_initialized)
{
return;
}
var needToRegisterWithShutdownToken = false;
lock (_initLocker)
{
if (!_initialized)
{
instanceName = instanceName ?? Guid.NewGuid().ToString();
SetCounterProperties(instanceName);
// The initializer ran, so let's register the shutdown cleanup
if (hostShutdownToken != CancellationToken.None)
{
needToRegisterWithShutdownToken = true;
}
_initialized = true;
}
}
if (needToRegisterWithShutdownToken)
{
hostShutdownToken.Register(UnloadCounters);
}
}
private void UnloadCounters()
{
lock (_initLocker)
{
if (!_initialized)
{
// We were never initalized
return;
}
}
var counterProperties = this.GetType()
.GetProperties()
.Where(p => p.PropertyType == typeof(IPerformanceCounter));
foreach (var property in counterProperties)
{
var counter = property.GetValue(this, null) as IPerformanceCounter;
counter.Close();
counter.RemoveInstance();
}
}
private void InitNoOpCounters()
{
// Set all the counter properties to no-op by default.
// These will get reset to real counters when/if the Initialize method is called.
foreach (var property in _counterProperties)
{
property.SetValue(this, new NoOpPerformanceCounter(), null);
}
}
private void SetCounterProperties(string instanceName)
{
var loadCounters = true;
foreach (var property in _counterProperties)
{
PerformanceCounterAttribute attribute = GetPerformanceCounterAttribute(property);
if (attribute == null)
{
continue;
}
IPerformanceCounter counter = null;
if (loadCounters)
{
counter = LoadCounter(CategoryName, attribute.Name, instanceName, isReadOnly:false);
if (counter == null)
{
// We failed to load the counter so skip the rest
loadCounters = false;
}
}
counter = counter ?? _noOpCounter;
property.SetValue(this, counter, null);
}
}
internal static PropertyInfo[] GetCounterPropertyInfo()
{
return typeof(PerformanceCounterManager)
.GetProperties()
.Where(p => p.PropertyType == typeof(IPerformanceCounter))
.ToArray();
}
internal static PerformanceCounterAttribute GetPerformanceCounterAttribute(PropertyInfo property)
{
return property.GetCustomAttributes(typeof(PerformanceCounterAttribute), false)
.Cast<PerformanceCounterAttribute>()
.SingleOrDefault();
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This file is shared")]
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Counters are disposed later")]
public IPerformanceCounter LoadCounter(string categoryName, string counterName, string instanceName, bool isReadOnly)
{
// See http://msdn.microsoft.com/en-us/library/356cx381.aspx for the list of exceptions
// and when they are thrown.
try
{
var counter = new PerformanceCounter(categoryName, counterName, instanceName, isReadOnly);
// Initialize the counter sample
counter.NextSample();
return new PerformanceCounterWrapper(counter);
}
#if UTILS
catch (InvalidOperationException) { return null; }
catch (UnauthorizedAccessException) { return null; }
catch (Win32Exception) { return null; }
catch (PlatformNotSupportedException) { return null; }
#else
catch (InvalidOperationException ex)
{
_trace.TraceEvent(TraceEventType.Error, 0, "Performance counter failed to load: " + ex.GetBaseException());
return null;
}
catch (UnauthorizedAccessException ex)
{
_trace.TraceEvent(TraceEventType.Error, 0, "Performance counter failed to load: " + ex.GetBaseException());
return null;
}
catch (Win32Exception ex)
{
_trace.TraceEvent(TraceEventType.Error, 0, "Performance counter failed to load: " + ex.GetBaseException());
return null;
}
catch (PlatformNotSupportedException ex)
{
_trace.TraceEvent(TraceEventType.Error, 0, "Performance counter failed to load: " + ex.GetBaseException());
return null;
}
#endif
}
}
}
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class PerformanceCounterWrapper : IPerformanceCounter
{
private readonly PerformanceCounter _counter;
public PerformanceCounterWrapper(PerformanceCounter counter)
{
_counter = counter;
}
public string CounterName
{
get
{
return _counter.CounterName;
}
}
public long RawValue
{
get { return _counter.RawValue; }
set { _counter.RawValue = value; }
}
public long Decrement()
{
return _counter.Decrement();
}
public long Increment()
{
return _counter.Increment();
}
public long IncrementBy(long value)
{
return _counter.IncrementBy(value);
}
public void Close()
{
_counter.Close();
}
public void RemoveInstance()
{
try
{
_counter.RemoveInstance();
}
catch(NotImplementedException)
{
// This happens on mono
}
}
public CounterSample NextSample()
{
return _counter.NextSample();
}
}
}
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class PersistentConnectionContext : IPersistentConnectionContext
{
public PersistentConnectionContext(IConnection connection, IConnectionGroupManager groupManager)
{
Connection = connection;
Groups = groupManager;
}
public IConnection Connection { get; private set; }
public IConnectionGroupManager Groups { get; private set; }
}
}
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal static class PrefixHelper
{
// Hubs
internal const string HubPrefix = "h-";
internal const string HubGroupPrefix = "hg-";
internal const string HubConnectionIdPrefix = "hc-";
// Persistent Connections
internal const string PersistentConnectionPrefix = "pc-";
internal const string PersistentConnectionGroupPrefix = "pcg-";
// Both
internal const string ConnectionIdPrefix = "c-";
internal const string AckPrefix = "ack-";
public static bool HasGroupPrefix(string value)
{
return value.StartsWith(HubGroupPrefix, StringComparison.Ordinal) ||
value.StartsWith(PersistentConnectionGroupPrefix, StringComparison.Ordinal);
}
public static string GetConnectionId(string connectionId)
{
return ConnectionIdPrefix + connectionId;
}
public static string GetHubConnectionId(string connectionId)
{
return HubConnectionIdPrefix + connectionId;
}
public static string GetHubName(string connectionId)
{
return HubPrefix + connectionId;
}
public static string GetHubGroupName(string groupName)
{
return HubGroupPrefix + groupName;
}
public static string GetPersistentConnectionGroupName(string groupName)
{
return PersistentConnectionGroupPrefix + groupName;
}
public static string GetPersistentConnectionName(string connectionName)
{
return PersistentConnectionPrefix + connectionName;
}
public static string GetAck(string connectionId)
{
return AckPrefix + connectionId;
}
public static IList<string> GetPrefixedConnectionIds(IList<string> connectionIds)
{
if (connectionIds.Count == 0)
{
return ListHelper<string>.Empty;
}
return connectionIds.Select(PrefixHelper.GetConnectionId).ToList();
}
public static IEnumerable<string> RemoveGroupPrefixes(IEnumerable<string> groups)
{
return groups.Select(PrefixHelper.RemoveGroupPrefix);
}
public static string RemoveGroupPrefix(string name)
{
if (name.StartsWith(HubGroupPrefix, StringComparison.Ordinal))
{
return name.Substring(HubGroupPrefix.Length);
}
if (name.StartsWith(PersistentConnectionGroupPrefix, StringComparison.Ordinal))
{
return name.Substring(PersistentConnectionGroupPrefix.Length);
}
return name;
}
}
}
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
// These need to change when the format changes
public static class Purposes
{
public const string ConnectionToken = "SignalR.ConnectionToken";
public const string Groups = "SignalR.Groups.v1.1";
}
}
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Thread safe cancellation token source. Allows the following:
/// - Cancel will no-op if the token is disposed.
/// - Dispose may be called after Cancel.
/// </summary>
internal class SafeCancellationTokenSource : IDisposable
{
private CancellationTokenSource _cts;
private int _state;
public SafeCancellationTokenSource()
{
_cts = new CancellationTokenSource();
Token = _cts.Token;
}
public CancellationToken Token { get; private set; }
public void Cancel()
{
var value = Interlocked.CompareExchange(ref _state, State.Cancelling, State.Initial);
if (value == State.Initial)
{
// Because cancellation tokens are so poorly behaved, always invoke the cancellation token on
// another thread. Don't capture any of the context (execution context or sync context)
// while doing this.
#if WINDOWS_PHONE || SILVERLIGHT
ThreadPool.QueueUserWorkItem(_ =>
#elif NETFX_CORE
Task.Run(() =>
#else
ThreadPool.UnsafeQueueUserWorkItem(_ =>
#endif
{
try
{
_cts.Cancel();
}
finally
{
if (Interlocked.CompareExchange(ref _state, State.Cancelled, State.Cancelling) == State.Disposing)
{
_cts.Dispose();
Interlocked.Exchange(ref _state, State.Disposed);
}
}
}
#if !NETFX_CORE
, state: null
#endif
);
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
var value = Interlocked.Exchange(ref _state, State.Disposing);
switch (value)
{
case State.Initial:
case State.Cancelled:
_cts.Dispose();
Interlocked.Exchange(ref _state, State.Disposed);
break;
case State.Cancelling:
case State.Disposing:
// No-op
break;
case State.Disposed:
Interlocked.Exchange(ref _state, State.Disposed);
break;
default:
break;
}
}
}
public void Dispose()
{
Dispose(true);
}
private static class State
{
public const int Initial = 0;
public const int Cancelling = 1;
public const int Cancelled = 2;
public const int Disposing = 3;
public const int Disposed = 4;
}
}
}
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class SafeSet<T>
{
private readonly ConcurrentDictionary<T, object> _items;
public SafeSet()
{
_items = new ConcurrentDictionary<T, object>();
}
public SafeSet(IEqualityComparer<T> comparer)
{
_items = new ConcurrentDictionary<T, object>(comparer);
}
public SafeSet(IEnumerable<T> items)
{
_items = new ConcurrentDictionary<T, object>(items.Select(x => new KeyValuePair<T, object>(x, null)));
}
public ICollection<T> GetSnapshot()
{
// The Keys property locks, so Select instead
return _items.Keys;
}
public bool Contains(T item)
{
return _items.ContainsKey(item);
}
public bool Add(T item)
{
return _items.TryAdd(item, null);
}
public bool Remove(T item)
{
object _;
return _items.TryRemove(item, out _);
}
public bool Any()
{
return _items.Any();
}
public long Count
{
get { return _items.Count; }
}
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// A server to server command.
/// </summary>
internal class ServerCommand
{
/// <summary>
/// Gets or sets the id of the command where this message originated from.
/// </summary>
public string ServerId { get; set; }
/// <summary>
/// Gets of sets the command type.
/// </summary>
public ServerCommandType ServerCommandType { get; set; }
/// <summary>
/// Gets or sets the value for this command.
/// </summary>
public object Value { get; set; }
internal bool IsFromSelf(string serverId)
{
return serverId.Equals(ServerId);
}
}
}
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Json;
using Microsoft.AspNet.SignalR.Messaging;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Default <see cref="IServerCommandHandler"/> implementation.
/// </summary>
internal class ServerCommandHandler : IServerCommandHandler, ISubscriber, IDisposable
{
private readonly IMessageBus _messageBus;
private readonly IServerIdManager _serverIdManager;
private readonly IJsonSerializer _serializer;
private IDisposable _subscription;
private const int MaxMessages = 10;
// The signal for all signalr servers
private const string ServerSignal = "__SIGNALR__SERVER__";
private static readonly string[] ServerSignals = new[] { ServerSignal };
public ServerCommandHandler(IDependencyResolver resolver) :
this(resolver.Resolve<IMessageBus>(),
resolver.Resolve<IServerIdManager>(),
resolver.Resolve<IJsonSerializer>())
{
}
public ServerCommandHandler(IMessageBus messageBus, IServerIdManager serverIdManager, IJsonSerializer serializer)
{
_messageBus = messageBus;
_serverIdManager = serverIdManager;
_serializer = serializer;
ProcessMessages();
}
public Action<ServerCommand> Command
{
get;
set;
}
public IList<string> EventKeys
{
get
{
return ServerSignals;
}
}
event Action<ISubscriber, string> ISubscriber.EventKeyAdded
{
add
{
}
remove
{
}
}
event Action<ISubscriber, string> ISubscriber.EventKeyRemoved
{
add
{
}
remove
{
}
}
public Action<TextWriter> WriteCursor { get; set; }
public string Identity
{
get
{
return _serverIdManager.ServerId;
}
}
public Subscription Subscription
{
get;
set;
}
public Task SendCommand(ServerCommand command)
{
// Store where the message originated from
command.ServerId = _serverIdManager.ServerId;
// Send the command to the all servers
return _messageBus.Publish(_serverIdManager.ServerId, ServerSignal, _serializer.Stringify(command));
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_subscription != null)
{
_subscription.Dispose();
}
}
}
public void Dispose()
{
Dispose(true);
}
private void ProcessMessages()
{
// Process messages that come from the bus for servers
_subscription = _messageBus.Subscribe(this, cursor: null, callback: HandleServerCommands, maxMessages: MaxMessages, state: null);
}
private Task<bool> HandleServerCommands(MessageResult result, object state)
{
result.Messages.Enumerate<object>(m => ServerSignal.Equals(m.Key),
(s, m) =>
{
var command = _serializer.Parse<ServerCommand>(m.Value, m.Encoding);
OnCommand(command);
},
state: null);
return TaskAsyncHelper.True;
}
private void OnCommand(ServerCommand command)
{
if (Command != null)
{
Command(command);
}
}
}
}
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public enum ServerCommandType
{
RemoveConnection
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// Default <see cref="IServerIdManager"/> implementation.
/// </summary>
public class ServerIdManager : IServerIdManager
{
public ServerIdManager()
{
ServerId = Guid.NewGuid().ToString();
}
/// <summary>
/// The id of the server.
/// </summary>
public string ServerId
{
get;
private set;
}
}
}
@@ -0,0 +1,242 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
// A string equality comparer based on the SipHash-2-4 algorithm. Key differences:
// (a) we output 32-bit hashes instead of 64-bit hashes, and
// (b) we don't care about endianness since hashes are used only in hash tables
// and aren't returned to user code.
//
// Meant to serve as a replacement for StringComparer.Ordinal.
// Derivative work of https://github.com/tanglebones/ch-siphash.
internal unsafe sealed class SipHashBasedStringEqualityComparer : IEqualityComparer<string>
{
private static readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
// the 128-bit secret key
private readonly ulong _k0;
private readonly ulong _k1;
public SipHashBasedStringEqualityComparer()
: this(GenerateRandomKeySegment(), GenerateRandomKeySegment())
{
}
// for unit testing
internal SipHashBasedStringEqualityComparer(ulong k0, ulong k1)
{
_k0 = k0;
_k1 = k1;
}
public bool Equals(string x, string y)
{
return String.Equals(x, y);
}
private static ulong GenerateRandomKeySegment()
{
byte[] bytes = new byte[sizeof(ulong)];
_rng.GetBytes(bytes);
return (ulong)BitConverter.ToInt64(bytes, 0);
}
public int GetHashCode(string obj)
{
if (obj == null)
{
return 0;
}
fixed (char* pChars = obj)
{
// treat input as an opaque blob, convert char count to byte count
return GetHashCode((byte*)pChars, checked((uint)obj.Length * sizeof(char)));
}
}
// for unit testing
internal int GetHashCode(byte* bytes, uint len)
{
// Assume SipHash-2-4 is a strong PRF, therefore truncation to 32 bits is acceptable.
return (int)SipHash_2_4_UlongCast_ForcedInline(bytes, len, _k0, _k1);
}
private static unsafe ulong SipHash_2_4_UlongCast_ForcedInline(byte* finb, uint inlen, ulong k0, ulong k1)
{
var v0 = 0x736f6d6570736575 ^ k0;
var v1 = 0x646f72616e646f6d ^ k1;
var v2 = 0x6c7967656e657261 ^ k0;
var v3 = 0x7465646279746573 ^ k1;
var b = ((ulong)inlen) << 56;
if (inlen > 0)
{
var inb = finb;
var left = inlen & 7;
var end = inb + inlen - left;
var linb = (ulong*)finb;
var lend = (ulong*)end;
for (; linb < lend; ++linb)
{
v3 ^= *linb;
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 ^= *linb;
}
for (var i = 0; i < left; ++i)
{
b |= ((ulong)end[i]) << (8 * i);
}
}
v3 ^= b;
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 ^= b;
v2 ^= 0xff;
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
v0 += v1;
v1 = (v1 << 13) | (v1 >> (64 - 13));
v1 ^= v0;
v0 = (v0 << 32) | (v0 >> (64 - 32));
v2 += v3;
v3 = (v3 << 16) | (v3 >> (64 - 16));
v3 ^= v2;
v0 += v3;
v3 = (v3 << 21) | (v3 >> (64 - 21));
v3 ^= v0;
v2 += v1;
v1 = (v1 << 17) | (v1 >> (64 - 17));
v1 ^= v2;
v2 = (v2 << 32) | (v2 >> (64 - 32));
return v0 ^ v1 ^ v2 ^ v3;
}
}
}
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal class StringMinifier : IStringMinifier
{
private readonly ConcurrentDictionary<string, string> _stringMinifier = new ConcurrentDictionary<string, string>();
private readonly ConcurrentDictionary<string, string> _stringMaximizer = new ConcurrentDictionary<string, string>();
private int _lastMinifiedKey = -1;
private readonly Func<string, string> _createMinifiedString;
public StringMinifier()
{
_createMinifiedString = CreateMinifiedString;
}
public string Minify(string fullString)
{
return _stringMinifier.GetOrAdd(fullString, _createMinifiedString);
}
public string Unminify(string minifiedString)
{
string result;
_stringMaximizer.TryGetValue(minifiedString, out result);
return result;
}
public void RemoveUnminified(string fullString)
{
string minifiedString;
if (_stringMinifier.TryRemove(fullString, out minifiedString))
{
string value;
_stringMaximizer.TryRemove(minifiedString, out value);
}
}
private string CreateMinifiedString(string fullString)
{
var minString = GetStringFromInt((uint)Interlocked.Increment(ref _lastMinifiedKey));
_stringMaximizer.TryAdd(minString, fullString);
return minString;
}
[SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Justification = "This is a valid exception to throw.")]
private static char GetCharFromSixBitInt(uint num)
{
if (num < 26)
{
return (char)(num + 'A');
}
if (num < 52)
{
return (char)(num - 26 + 'a');
}
if (num < 62)
{
return (char)(num - 52 + '0');
}
if (num == 62)
{
return '_';
}
if (num == 63)
{
return ':';
}
throw new IndexOutOfRangeException();
}
private static string GetStringFromInt(uint num)
{
const int maxSize = 6;
// Buffer must be large enough to store any 32 bit uint at 6 bits per character
var buffer = new char[maxSize];
var index = maxSize;
do
{
// Append next 6 bits of num
buffer[--index] = GetCharFromSixBitInt(num & 0x3f);
num >>= 6;
// Don't pad output string, but ensure at least one character is written
} while (num != 0);
return new string(buffer, index, maxSize - index);
}
}
}
@@ -0,0 +1,131 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
// Allows serial queuing of Task instances
// The tasks are not called on the current synchronization context
internal sealed class TaskQueue
{
private readonly object _lockObj = new object();
private Task _lastQueuedTask;
private volatile bool _drained;
private readonly int? _maxSize;
private long _size;
public TaskQueue()
: this(TaskAsyncHelper.Empty)
{
}
public TaskQueue(Task initialTask)
{
_lastQueuedTask = initialTask;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code")]
public TaskQueue(Task initialTask, int maxSize)
{
_lastQueuedTask = initialTask;
_maxSize = maxSize;
}
#if !CLIENT_NET45
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")]
public IPerformanceCounter QueueSizeCounter { get; set; }
#endif
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code")]
public bool IsDrained
{
get
{
return _drained;
}
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code")]
public Task Enqueue(Func<object, Task> taskFunc, object state)
{
// Lock the object for as short amount of time as possible
lock (_lockObj)
{
if (_drained)
{
return _lastQueuedTask;
}
if (_maxSize != null)
{
if (Interlocked.Read(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?
// 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
var counter = QueueSizeCounter;
if (counter != null)
{
counter.Increment();
}
#endif
}
Task newTask = _lastQueuedTask.Then((next, nextState) =>
{
return next(nextState).Finally(s =>
{
var queue = (TaskQueue)s;
if (queue._maxSize != null)
{
// Decrement the number of items left in the queue
Interlocked.Decrement(ref queue._size);
#if !CLIENT_NET45
var counter = QueueSizeCounter;
if (counter != null)
{
counter.Decrement();
}
#endif
}
},
this);
},
taskFunc, state);
_lastQueuedTask = newTask;
return newTask;
}
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code")]
public Task Enqueue(Func<Task> taskFunc)
{
return Enqueue(state => ((Func<Task>)state).Invoke(), taskFunc);
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code")]
public Task Drain()
{
lock (_lockObj)
{
_drained = true;
return _lastQueuedTask;
}
}
}
}