mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-20 21:54:25 -04:00
imported signalr 1.1.3 into NzbDrone.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+242
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user