Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixes #859

- Signal names are now minified in cursors by a StringMinifier
- MakeCursor no longer zero pads its Ids
  • Loading branch information...
commit cfa09f7632d5dc7755d0d56d9761333f5c947ef7 1 parent 9f028fd
@halter73 halter73 authored
View
3  src/Microsoft.AspNet.SignalR.Core/Infrastructure/DefaultDependencyResolver.cs
@@ -36,6 +36,9 @@ private void RegisterDefaultServices()
var newMessageBus = new Lazy<IMessageBus>(() => new MessageBus(this));
Register(typeof(IMessageBus), () => newMessageBus.Value);
+ var stringMinifier = new Lazy<IStringMinifier>(() => new StringMinifier());
+ Register(typeof(IStringMinifier), () => stringMinifier.Value);
+
var serializer = new Lazy<JsonNetSerializer>();
Register(typeof(IJsonSerializer), () => serializer.Value);
View
81 src/Microsoft.AspNet.SignalR.Core/MessageBus/Cursor.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Net;
using System.Text;
namespace Microsoft.AspNet.SignalR
@@ -21,12 +22,9 @@ public string Key
set
{
_key = value;
- EscapedKey = Escape(value);
}
}
- private string EscapedKey { get; set; }
-
public ulong Id { get; set; }
public static Cursor Clone(Cursor cursor)
@@ -40,21 +38,26 @@ public static Cursor Clone(Cursor cursor)
public static string MakeCursor(IList<Cursor> cursors)
{
- return MakeCursorFast(cursors) ?? MakeCursorSlow(cursors);
+ return MakeCursor(cursors, s => s);
}
- private static string MakeCursorSlow(IList<Cursor> cursors)
+ public static string MakeCursor(IList<Cursor> cursors, Func<string, string> keyMap)
+ {
+ return MakeCursorFast(cursors, keyMap) ?? MakeCursorSlow(cursors, keyMap);
+ }
+
+ private static string MakeCursorSlow(IList<Cursor> cursors, Func<string, string> keyMap)
{
var serialized = new string[cursors.Count];
for (int i = 0; i < cursors.Count; i++)
{
- serialized[i] = cursors[i].EscapedKey + ',' + cursors[i].Id;
+ serialized[i] = Escape(keyMap(cursors[i].Key)) + ',' + cursors[i].Id.ToString("X");
}
return String.Join("|", serialized);
}
- private static string MakeCursorFast(IList<Cursor> cursors)
+ private static string MakeCursorFast(IList<Cursor> cursors, Func<string, string> keyMap)
{
const int MAX_CHARS = 8 * 1024;
char* pChars = stackalloc char[MAX_CHARS];
@@ -65,12 +68,10 @@ private static string MakeCursorFast(IList<Cursor> cursors)
for (int i = 0; i < cursors.Count; i++)
{
Cursor cursor = cursors[i];
- string escapedKey = cursor.EscapedKey;
+ string escapedKey = Escape(keyMap(cursor.Key));
- checked
- {
- numCharsInBuffer += escapedKey.Length + 18; // comma + 16-char hex Id + pipe
- }
+ // comma + up to 16-char hex Id + pipe
+ numCharsInBuffer += escapedKey.Length + 18;
if (numCharsInBuffer > MAX_CHARS)
{
@@ -82,24 +83,48 @@ private static string MakeCursorFast(IList<Cursor> cursors)
*pNextChar++ = escapedKey[j];
}
- *pNextChar = ',';
- pNextChar++;
- WriteUlongAsHexToBuffer(cursor.Id, pNextChar);
- pNextChar += 16;
- *pNextChar = '|';
- pNextChar++;
+ *pNextChar++ = ',';
+ int hexLength = WriteUlongAsHexToBuffer(cursor.Id, pNextChar);
+
+ // Since we reserved 16 chars for the hex value, update numCharsInBuffer to reflect the actual number of
+ // characters written by WriteUlongAsHexToBuffer.
+ numCharsInBuffer += hexLength - 16;
+ pNextChar += hexLength;
+ *pNextChar++ = '|';
}
return (numCharsInBuffer == 0) ? String.Empty : new String(pChars, 0, numCharsInBuffer - 1); // -1 for final pipe
}
- private static void WriteUlongAsHexToBuffer(ulong value, char* pBuffer)
+ private static int WriteUlongAsHexToBuffer(ulong value, char* pBuffer)
{
- for (int i = 15; i >= 0; i--)
+ // This tracks the length of the output and serves as the index for the next character to be written into the pBuffer.
+ // The length could reach up to 16 characters, so at least that much space should remain in the pBuffer.
+ int length = 0;
+
+ // Write the hex value from left to right into the buffer without zero padding.
+ for (int i = 0; i < 16; i++)
+ {
+ // Convert the first 4 bits of the value to a valid hex character.
+ pBuffer[length] = Int32ToHex((int)((value & 0xf000000000000000) >> 60)); // take first 4 bits and shift 60 bits
+ value <<= 4;
+
+ // Don't increment length if it would just add zero padding
+ if (length != 0 || pBuffer[length] != '0')
+ {
+ length++;
+ }
+ }
+
+ // The final length will be 0 iff the original value was 0. In this case we want to add 1 character, '0', to pBuffer
+ // '0' will have already been written to pBuffer[0] 16 times, so it is safe to simply return that 1 character was
+ // written to the output.
+ if (length == 0)
{
- pBuffer[i] = Int32ToHex((int)value & 0xf); // don't care about overflows here
- value >>= 4;
+ return 1;
}
+
+ return length;
}
private static char Int32ToHex(int value)
@@ -143,6 +168,11 @@ private static string Escape(string value)
public static Cursor[] GetCursors(string cursor)
{
+ return GetCursors(cursor, s => s);
+ }
+
+ public static Cursor[] GetCursors(string cursor, Func<string, string> keyMap)
+ {
var cursors = new List<Cursor>();
var current = new Cursor();
bool escape = false;
@@ -163,7 +193,12 @@ public static Cursor[] GetCursors(string cursor)
}
else if (ch == ',')
{
- current.Key = sb.ToString();
+ current.Key = keyMap(sb.ToString());
+ // If the keyMap cannot find a key, we cannot create an array of cursors.
+ if (current.Key == null)
+ {
+ return null;
+ }
sb.Clear();
}
else if (ch == '|')
View
30 src/Microsoft.AspNet.SignalR.Core/MessageBus/DefaultSubscription.cs
@@ -13,6 +13,7 @@ internal class DefaultSubscription : Subscription
private List<Cursor> _cursors;
private List<Topic> _cursorTopics;
+ private readonly IStringMinifier _stringMinifier;
private readonly object _lockObj = new object();
public DefaultSubscription(string identity,
@@ -21,23 +22,20 @@ internal class DefaultSubscription : Subscription
string cursor,
Func<MessageResult, Task<bool>> callback,
int maxMessages,
+ IStringMinifier stringMinifier,
IPerformanceCounterManager counters) :
base(identity, eventKeys, callback, maxMessages, counters)
{
+ _stringMinifier = stringMinifier;
- IEnumerable<Cursor> cursors = null;
+ IEnumerable<Cursor> cursors;
if (cursor == null)
{
- cursors = from key in eventKeys
- select new Cursor
- {
- Key = key,
- Id = GetMessageId(topics, key)
- };
+ cursors = GetCursorsFromEventKeys(EventKeys, topics);
}
else
{
- cursors = Cursor.GetCursors(cursor);
+ cursors = Cursor.GetCursors(cursor, stringMinifier.Unminify) ?? GetCursorsFromEventKeys(EventKeys, topics);
}
_cursors = new List<Cursor>(cursors);
@@ -50,7 +48,7 @@ internal class DefaultSubscription : Subscription
{
Cursor c = _cursors[i];
Topic topic;
- if (!eventKeys.Contains(c.Key))
+ if (!EventKeys.Contains(c.Key))
{
_cursors.Remove(c);
}
@@ -119,7 +117,7 @@ public override void SetEventTopic(string eventKey, Topic topic)
public override string GetCursor()
{
- return Cursor.MakeCursor(_cursors);
+ return Cursor.MakeCursor(_cursors, _stringMinifier.Minify);
}
protected override void PerformWork(ref List<ArraySegment<Message>> items, out string nextCursor, ref int totalCount, out object state)
@@ -146,7 +144,7 @@ protected override void PerformWork(ref List<ArraySegment<Message>> items, out s
}
}
- nextCursor = Cursor.MakeCursor(cursors);
+ nextCursor = Cursor.MakeCursor(cursors, _stringMinifier.Minify);
// Return the state as a list of cursors
state = cursors;
@@ -178,6 +176,16 @@ private bool UpdateCursor(string key, ulong id)
}
}
+ private IEnumerable<Cursor> GetCursorsFromEventKeys(IEnumerable<string> eventKeys, IDictionary<string, Topic> topics)
+ {
+ return from key in eventKeys
+ select new Cursor
+ {
+ Key = key,
+ Id = GetMessageId(topics, key)
+ };
+ }
+
private ulong GetMessageId(IDictionary<string, Topic> topics, string key)
{
Topic topic;
View
37 src/Microsoft.AspNet.SignalR.Core/MessageBus/IStringMinifier.cs
@@ -0,0 +1,37 @@
+namespace Microsoft.AspNet.SignalR
+{
+ public interface IStringMinifier
+ {
+ /// <summary>
+ /// Minifies a string in a way that can be reversed by this instance of <see cref="IStringMinifier"/>.
+ /// </summary>
+ /// <param name="fullString">The string to be minified</param>
+ /// <returns>A minified representation of the <see cref="fullString"/>.</returns>
+ string Minify(string fullString);
+
+ /// <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 <see cref="minifiedString"/>.
+ /// </summary>
+ /// <param name="minifiedString">
+ /// 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 <see cref="minifiedString"/>.
+ /// If every call to <see cref="Minify"/> on this instance of <see cref="IStringMinifier"/> has never
+ /// returned <see cref="minifiedString"/> or if the most recent call to <see cref="Minify"/> that did
+ /// return <see cref="minifiedString"/> 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 minifiedString);
+
+ /// <summary>
+ /// A call to this function indicates that any future attempt to unminify strings that were previously minified
+ /// from <see cref="fullString"/> may be met with a null return value. This provides an opportunity clean up
+ /// any internal data structures that reference <see cref="fullString"/>.
+ /// </summary>
+ /// <param name="fullString">The string that may have previously have been minified.</param>
+ void RemoveUnminified(string fullString);
+ }
+}
View
12 src/Microsoft.AspNet.SignalR.Core/MessageBus/MessageBus.cs
@@ -4,6 +4,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
@@ -20,6 +21,8 @@ public class MessageBus : IMessageBus, IDisposable
private const int DefaultMessageStoreSize = 5000;
+ private readonly IStringMinifier _stringMinifier;
+
private readonly ITraceManager _trace;
protected readonly IPerformanceCounterManager _counters;
@@ -35,7 +38,8 @@ public class MessageBus : IMessageBus, IDisposable
/// </summary>
/// <param name="resolver"></param>
public MessageBus(IDependencyResolver resolver)
- : this(resolver.Resolve<ITraceManager>(),
+ : this(resolver.Resolve<IStringMinifier>(),
+ resolver.Resolve<ITraceManager>(),
resolver.Resolve<IPerformanceCounterManager>(),
resolver.Resolve<IConfigurationManager>())
{
@@ -46,8 +50,9 @@ public MessageBus(IDependencyResolver resolver)
/// </summary>
/// <param name="traceManager"></param>
/// <param name="performanceCounterManager"></param>
- public MessageBus(ITraceManager traceManager, IPerformanceCounterManager performanceCounterManager, IConfigurationManager configurationManager)
+ public MessageBus(IStringMinifier stringMinifier, ITraceManager traceManager, IPerformanceCounterManager performanceCounterManager, IConfigurationManager configurationManager)
{
+ _stringMinifier = stringMinifier;
_trace = traceManager;
_counters = performanceCounterManager;
@@ -190,7 +195,7 @@ public virtual IDisposable Subscribe(ISubscriber subscriber, string cursor, Func
protected virtual Subscription CreateSubscription(ISubscriber subscriber, string cursor, Func<MessageResult, Task<bool>> callback, int messageBufferSize)
{
- return new DefaultSubscription(subscriber.Identity, subscriber.EventKeys, _topics, cursor, callback, messageBufferSize, _counters);
+ return new DefaultSubscription(subscriber.Identity, subscriber.EventKeys, _topics, cursor, callback, messageBufferSize, _stringMinifier, _counters);
}
protected void ScheduleEvent(string eventKey)
@@ -258,6 +263,7 @@ private void CheckTopics()
{
Topic topic;
_topics.TryRemove(pair.Key, out topic);
+ _stringMinifier.RemoveUnminified(pair.Key);
}
}
}
View
84 src/Microsoft.AspNet.SignalR.Core/MessageBus/StringMinifier.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Microsoft.AspNet.SignalR
+{
+ 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;
+
+ public string Minify(string fullString)
+ {
+ return _stringMinifier.GetOrAdd(fullString, _ =>
+ {
+ var minString = GetStringFromInt((uint)Interlocked.Increment(ref _lastMinifiedKey));
+ _stringMaximizer.TryAdd(minString, fullString);
+ return minString;
+ });
+ }
+
+ 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 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 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);
+ }
+ }
+}
View
2  src/Microsoft.AspNet.SignalR.Core/Microsoft.AspNet.SignalR.Core.csproj
@@ -133,6 +133,7 @@
<Compile Include="Infrastructure\IServerCommandHandler.cs" />
<Compile Include="Json\JsonSerializerExtensions.cs" />
<Compile Include="MessageBus\Cursor.cs" />
+ <Compile Include="MessageBus\IStringMinifier.cs" />
<Compile Include="MessageBus\ISubscription.cs" />
<Compile Include="MessageBus\Linktionary.cs" />
<Compile Include="MessageBus\LocalEventKeyInfo.cs" />
@@ -149,6 +150,7 @@
<Compile Include="MessageBus\MessageStoreResult.cs" />
<Compile Include="MessageBus\ScaleoutSubscription.cs" />
<Compile Include="MessageBus\DefaultSubscription.cs" />
+ <Compile Include="MessageBus\StringMinifier.cs" />
<Compile Include="MessageBus\Subscription.cs" />
<Compile Include="MessageBus\Topic.cs" />
<Compile Include="MessageBus\Volatile.cs" />
View
2  tests/Microsoft.AspNet.SignalR.FunctionalTests/Server/Connections/DisconnectFacts.cs
@@ -77,7 +77,7 @@ public void FarmDisconnectOnlyRaisesEventOnce()
// Each node shares the same bus but are indepenent servers
var counters = new SignalR.Infrastructure.PerformanceCounterManager();
var configurationManager = new DefaultConfigurationManager();
- using (var bus = new MessageBus(new TraceManager(), counters, configurationManager))
+ using (var bus = new MessageBus(new StringMinifier(), new TraceManager(), counters, configurationManager))
{
var nodeCount = 3;
var nodes = new List<ServerNode>();
Please sign in to comment.
Something went wrong with that request. Please try again.