Permalink
Browse files

Fixes #859

- Signal names are now minified in cursors by a StringMinifier
- MakeCursor no longer zero pads its Ids
  • Loading branch information...
1 parent 9f028fd commit cfa09f7632d5dc7755d0d56d9761333f5c947ef7 @halter73 halter73 committed Oct 31, 2012
@@ -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);
@@ -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 == '|')
@@ -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;
@@ -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);
+ }
+}
@@ -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);
}
}
}
Oops, something went wrong.

0 comments on commit cfa09f7

Please sign in to comment.