Commit b6e4bd65 authored by Marc Gravell's avatar Marc Gravell

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget;...

Revisit prefixed keys to avoid allocations; update MS redis-64 from nuget; update tests; change namespace of keyspace isolation - that was an error
parent a541e22d
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NuGet.CommandLine" version="2.8.2" />
<package id="Redis-64" version="2.8.12" />
<package id="NuGet.CommandLine" version="2.8.3" />
<package id="Redis-64" version="2.8.17" />
</packages>
\ No newline at end of file
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7000
\ No newline at end of file
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7000
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7001
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7001
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7002
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7002
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7003
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7003
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7004
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7004
@..\packages\Redis-64.2.8.12\redis-cli.exe -h cluster -p 7005
@..\packages\Redis-64.2.8.17\redis-cli.exe -h cluster -p 7005
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6379
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6379
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6381
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6381
@..\packages\Redis-64.2.8.12\redis-cli.exe -p 6380
@..\packages\Redis-64.2.8.17\redis-cli.exe -p 6380
@start ..\packages\Redis-64.2.8.12\redis-server.exe master.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe slave.conf
@start ..\packages\Redis-64.2.8.12\redis-server.exe secure.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
@start ..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe master.conf
@..\packages\Redis-64.2.8.17\redis-server.exe master.conf
@..\packages\Redis-64.2.8.12\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.17\redis-server.exe secure.conf
@..\packages\Redis-64.2.8.12\redis-server.exe slave.conf
@..\packages\Redis-64.2.8.17\redis-server.exe slave.conf
......@@ -4,7 +4,7 @@
using System.Threading.Tasks;
using BookSleeve;
using NUnit.Framework;
using StackExchange.Redis.KeyspaceIsolation;
namespace StackExchange.Redis.Tests
{
[TestFixture]
......@@ -664,6 +664,22 @@ private void Incr(IDatabase database, RedisKey key, int delta, ref int total)
database.StringIncrement(key, delta, CommandFlags.FireAndForget);
total += delta;
}
[Test]
public void WrappedDatabasePrefixIntegration()
{
using (var conn = Create())
{
var db = conn.GetDatabase().WithKeyPrefix("abc");
db.KeyDelete("count");
db.StringIncrement("count");
db.StringIncrement("count");
db.StringIncrement("count");
int count = (int)conn.GetDatabase().StringGet("abccount");
Assert.AreEqual(3, count);
}
}
}
}
using System;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -15,7 +16,7 @@ public sealed class BatchWrapperTests
public void Initialize()
{
mock = new Mock<IBatch>();
wrapper = new BatchWrapper(mock.Object, "prefix:");
wrapper = new BatchWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
......@@ -4,7 +4,8 @@
using System.Net;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -18,7 +19,7 @@ public sealed class DatabaseWrapperTests
public void Initialize()
{
mock = new Mock<IDatabase>();
wrapper = new DatabaseWrapper(mock.Object, "prefix:");
wrapper = new DatabaseWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......@@ -149,7 +150,7 @@ public void HashLength()
public void HashScan()
{
wrapper.HashScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.HashScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......@@ -604,7 +605,7 @@ public void SetRemove_2()
public void SetScan()
{
wrapper.SetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.SetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......@@ -774,7 +775,7 @@ public void SortedSetRemoveRangeByValue()
public void SortedSetScan()
{
wrapper.SortedSetScan("key", "pattern", 123, flags: CommandFlags.HighPriority);
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, CommandFlags.HighPriority));
mock.Verify(_ => _.SortedSetScan("prefix:key", "pattern", 123, 0, 0, CommandFlags.HighPriority));
}
[Test]
......
......@@ -5,10 +5,6 @@ namespace StackExchange.Redis.Tests
[TestFixture]
public class Lex : TestBase
{
protected override string GetConfiguration()
{
return "ubuntu";
}
[Test]
public void QueryRangeAndLengthByLex()
......
......@@ -14,7 +14,7 @@ protected override string GetConfiguration()
return PrimaryServer + ":" + SecurePort + "," + PrimaryServer + ":" + PrimaryPort + ",password=" + SecurePassword;
}
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "FLUSHDB cannot be issued to a slave")]
[Test, ExpectedException(typeof(RedisCommandException), ExpectedMessage = "Command cannot be issued to a slave: FLUSHDB")]
public void CannotFlushSlave()
{
ConfigurationOptions config = GetMasterSlaveConfig();
......@@ -73,7 +73,7 @@ public void DeslaveGoesToPrimary()
}
catch (RedisConnectionException ex)
{
Assert.AreEqual("No connection is available to service this operation: EXISTS", ex.Message);
Assert.AreEqual("No connection is available to service this operation: EXISTS DeslaveGoesToPrimary", ex.Message);
}
primary.MakeMaster(ReplicationChangeOptions.Broadcast | ReplicationChangeOptions.EnslaveSubordinates | ReplicationChangeOptions.SetTiebreaker);
......
......@@ -73,6 +73,8 @@ public void CheckDatabaseMethodsUseKeys(Type type)
{
case "KeyRandom":
case "KeyRandomAsync":
case "Publish":
case "PublishAsync":
continue; // they're fine, but don't want to widen check to return type
}
......
......@@ -33,9 +33,5 @@ public void SubscriberCount()
Assert.IsTrue(channels.Contains(channel));
}
}
protected override string GetConfiguration()
{
return "ubuntu";
}
}
}
using System;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -15,7 +16,7 @@ public sealed class TransactionWrapperTests
public void Initialize()
{
mock = new Mock<ITransaction>();
wrapper = new TransactionWrapper(mock.Object, "prefix:");
wrapper = new TransactionWrapper(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System;
namespace StackExchange.Redis.Tests
......
......@@ -4,7 +4,8 @@
using System.Net;
using Moq;
using NUnit.Framework;
using StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation;
using StackExchange.Redis.KeyspaceIsolation;
using System.Text;
namespace StackExchange.Redis.Tests
{
......@@ -18,7 +19,7 @@ public sealed class WrapperBaseTests
public void Initialize()
{
mock = new Mock<IDatabaseAsync>();
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, "prefix:");
wrapper = new WrapperBase<IDatabaseAsync>(mock.Object, Encoding.UTF8.GetBytes("prefix:"));
}
[Test]
......
......@@ -163,7 +163,7 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedResult ? " exists" : " does not exists");
}
......@@ -221,7 +221,7 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
return (hashField.IsNull ? key.ToString() : ((string)key) + " > " + hashField)
+ (expectedEqual ? " == " : " != ")
+ expectedValue;
}
......
using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class BatchWrapper : WrapperBase<IBatch>, IBatch
{
public BatchWrapper(IBatch inner, RedisKey prefix)
public BatchWrapper(IBatch inner, byte[] prefix)
: base(inner, prefix)
{
}
......
using System;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
/// <summary>
/// Provides the <see cref="WithKeyPrefix"/> extension method to <see cref="IDatabase"/>.
......@@ -49,7 +49,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
throw new ArgumentNullException("keyPrefix");
}
if (keyPrefix.Value.Length == 0)
if (keyPrefix.IsEmpty)
{
return database; // fine - you can keep using the original, then
}
......@@ -62,7 +62,7 @@ public static IDatabase WithKeyPrefix(this IDatabase database, RedisKey keyPrefi
database = wrapper.Inner;
}
return new DatabaseWrapper(database, keyPrefix);
return new DatabaseWrapper(database, keyPrefix.AsPrefix());
}
}
}
......@@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.Net;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class DatabaseWrapper : WrapperBase<IDatabase>, IDatabase
{
public DatabaseWrapper(IDatabase inner, RedisKey prefix)
public DatabaseWrapper(IDatabase inner, byte[] prefix)
: base(inner, prefix)
{
}
......
using System;
using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal sealed class TransactionWrapper : WrapperBase<ITransaction>, ITransaction
{
public TransactionWrapper(ITransaction inner, RedisKey prefix)
public TransactionWrapper(ITransaction inner, byte[] prefix)
: base(inner, prefix)
{
}
......
......@@ -4,17 +4,17 @@
using System.Net;
using System.Threading.Tasks;
namespace StackExchange.Redis.StackExchange.Redis.KeyspaceIsolation
namespace StackExchange.Redis.KeyspaceIsolation
{
internal class WrapperBase<TInner> : IDatabaseAsync where TInner : IDatabaseAsync
{
private readonly TInner _inner;
private readonly RedisKey _prefix;
private readonly byte[] _keyPrefix;
internal WrapperBase(TInner inner, RedisKey prefix)
internal WrapperBase(TInner inner, byte[] keyPrefix)
{
_inner = inner;
_prefix = prefix;
_keyPrefix = keyPrefix;
}
public ConnectionMultiplexer Multiplexer
......@@ -27,9 +27,9 @@ internal TInner Inner
get { return _inner; }
}
internal RedisKey Prefix
internal byte[] Prefix
{
get { return _prefix; }
get { return _keyPrefix; }
}
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
......@@ -653,7 +653,7 @@ public Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None)
protected internal RedisKey ToInner(RedisKey outer)
{
return this.Prefix + outer;
return RedisKey.WithPrefix(_keyPrefix, outer);
}
protected RedisKey ToInnerOrDefault(RedisKey outer)
......@@ -713,7 +713,7 @@ protected RedisKey[] ToInner(RedisKey[] outer)
protected RedisValue ToInner(RedisValue outer)
{
return RedisKey.Concatenate(this.Prefix, outer);
return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
}
protected RedisValue SortByToInner(RedisValue outer)
......@@ -761,7 +761,7 @@ protected RedisValue[] SortGetToInner(RedisValue[] outer)
protected RedisChannel ToInner(RedisChannel outer)
{
return RedisKey.Concatenate((byte[])Prefix, (byte[])outer);
return RedisKey.ConcatenateBytes(this.Prefix, null, (byte[])outer);
}
private Func<RedisKey, RedisKey> mapFunction;
......
......@@ -612,7 +612,7 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey
this.Key = key;
}
public override string CommandAndKey { get { return Command + " " + Key; } }
public override string CommandAndKey { get { return Command + " " + ((string)Key); } }
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(Key);
......
......@@ -354,7 +354,15 @@ internal void SetUnknownDatabase()
internal void Write(RedisKey key)
{
WriteUnified(outStream, key.Value);
var val = key.KeyValue;
if (val is string)
{
WriteUnified(outStream, key.KeyPrefix, (string)val);
}
else
{
WriteUnified(outStream, key.KeyPrefix, (byte[])val);
}
}
internal void Write(RedisChannel channel)
......@@ -494,6 +502,61 @@ internal static byte ToHexNibble(int value)
return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value);
}
void WriteUnified(Stream stream, byte[] prefix, string value)
{
stream.WriteByte((byte)'$');
if (value == null)
{
WriteRaw(stream, -1); // note that not many things like this...
}
else
{
int encodedLength = Encoding.UTF8.GetByteCount(value);
if (prefix == null)
{
WriteRaw(stream, encodedLength);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
else
{
WriteRaw(stream, prefix.Length + encodedLength);
stream.Write(prefix, 0, prefix.Length);
WriteRaw(stream, value, encodedLength);
stream.Write(Crlf, 0, 2);
}
}
}
unsafe void WriteRaw(Stream stream, string value, int encodedLength)
{
if (encodedLength <= ScratchSize)
{
int bytes = Encoding.UTF8.GetBytes(value, 0, value.Length, outScratch, 0);
stream.Write(outScratch, 0, bytes);
}
else
{
fixed (char* c = value)
fixed (byte* b = outScratch)
{
int charsRemaining = value.Length, charOffset = 0, bytesWritten;
while (charsRemaining > Scratch_CharsPerBlock)
{
bytesWritten = outEncoder.GetBytes(c + charOffset, Scratch_CharsPerBlock, b, ScratchSize, false);
stream.Write(outScratch, 0, bytesWritten);
charOffset += Scratch_CharsPerBlock;
charsRemaining -= Scratch_CharsPerBlock;
}
bytesWritten = outEncoder.GetBytes(c + charOffset, charsRemaining, b, ScratchSize, true);
if (bytesWritten != 0) stream.Write(outScratch, 0, bytesWritten);
}
}
}
const int ScratchSize = 512;
static readonly int Scratch_CharsPerBlock = ScratchSize / Encoding.UTF8.GetMaxByteCount(1);
private readonly byte[] outScratch = new byte[ScratchSize];
private readonly Encoder outEncoder = Encoding.UTF8.GetEncoder();
static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{
stream.WriteByte((byte)'$');
......
......@@ -2322,7 +2322,7 @@ public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCo
{
this.ttlCommand = ttlCommand;
}
public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + Key; } }
public override string CommandAndKey { get { return ttlCommand + "+" + RedisCommand.GET + " " + (string)Key; } }
public IEnumerable<Message> GetMessages(PhysicalConnection connection)
{
......
......@@ -9,18 +9,36 @@ namespace StackExchange.Redis
public struct RedisKey : IEquatable<RedisKey>
{
internal static readonly RedisKey[] EmptyArray = new RedisKey[0];
private readonly byte[] value;
private RedisKey(byte[] value)
private readonly byte[] keyPrefix;
private readonly object keyValue; // always either a string or a byte[]
internal RedisKey(byte[] keyPrefix, object keyValue)
{
this.value = value;
this.keyPrefix = (keyPrefix != null && keyPrefix.Length == 0) ? null : keyPrefix;
this.keyValue = keyValue;
}
internal RedisKey AsPrefix()
{
return new RedisKey((byte[])this, null);
}
internal bool IsNull
{
get { return value == null; }
get { return keyPrefix == null && keyValue == null; }
}
internal byte[] Value { get { return value; } }
internal bool IsEmpty
{
get
{
if (keyPrefix != null) return false;
if (keyValue == null) return true;
if (keyValue is string) return ((string)keyValue).Length == 0;
return ((byte[])keyValue).Length == 0;
}
}
internal byte[] KeyPrefix { get { return keyPrefix; } }
internal object KeyValue { get { return keyValue; } }
/// <summary>
/// Indicate whether two keys are not equal
......@@ -67,7 +85,7 @@ internal bool IsNull
/// </summary>
public static bool operator ==(RedisKey x, RedisKey y)
{
return RedisValue.Equals(x.value, y.value);
return CompositeEquals(x.keyPrefix, x.keyValue, y.keyPrefix, y.keyValue);
}
/// <summary>
......@@ -75,7 +93,7 @@ internal bool IsNull
/// </summary>
public static bool operator ==(string x, RedisKey y)
{
return RedisValue.Equals(x == null ? null : Encoding.UTF8.GetBytes(x), y.value);
return CompositeEquals(null, x, y.keyPrefix, y.keyValue);
}
/// <summary>
......@@ -83,7 +101,7 @@ internal bool IsNull
/// </summary>
public static bool operator ==(byte[] x, RedisKey y)
{
return RedisValue.Equals(x, y.value);
return CompositeEquals(null, x, y.keyPrefix, y.keyValue);
}
/// <summary>
......@@ -91,7 +109,7 @@ internal bool IsNull
/// </summary>
public static bool operator ==(RedisKey x, string y)
{
return RedisValue.Equals(x.value, y == null ? null : Encoding.UTF8.GetBytes(y));
return CompositeEquals(x.keyPrefix, x.keyValue, null, y);
}
/// <summary>
......@@ -99,7 +117,7 @@ internal bool IsNull
/// </summary>
public static bool operator ==(RedisKey x, byte[] y)
{
return RedisValue.Equals(x.value, y);
return CompositeEquals(x.keyPrefix, x.keyValue, null, y);
}
/// <summary>
......@@ -109,15 +127,12 @@ public override bool Equals(object obj)
{
if (obj is RedisKey)
{
return RedisValue.Equals(this.value, ((RedisKey)obj).value);
}
if (obj is string)
{
return RedisValue.Equals(this.value, Encoding.UTF8.GetBytes((string)obj));
var other = (RedisKey)obj;
return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue);
}
if (obj is byte[])
if (obj is string || obj is byte[])
{
return RedisValue.Equals(this.value, (byte[])obj);
return CompositeEquals(this.keyPrefix, this.keyValue, null, obj);
}
return false;
}
......@@ -127,7 +142,21 @@ public override bool Equals(object obj)
/// </summary>
public bool Equals(RedisKey other)
{
return RedisValue.Equals(this.value, other.value);
return CompositeEquals(this.keyPrefix, this.keyValue, other.keyPrefix, other.keyValue);
}
private static bool CompositeEquals(byte[] keyPrefix0, object keyValue0, byte[] keyPrefix1, object keyValue1)
{
if(RedisValue.Equals(keyPrefix0, keyPrefix1))
{
if (keyValue0 == keyValue1) return true; // ref equal
if (keyValue0 == null || keyValue1 == null) return false; // null vs non-null
if (keyValue0 is string && keyValue1 is string) return ((string)keyValue0) == ((string)keyValue1);
if (keyValue0 is byte[] && keyValue1 is byte[]) return RedisValue.Equals((byte[])keyValue0, (byte[])keyValue1);
}
return RedisValue.Equals(ConcatenateBytes(keyPrefix0, keyValue0, null), ConcatenateBytes(keyPrefix1, keyValue1, null));
}
/// <summary>
......@@ -135,7 +164,10 @@ public bool Equals(RedisKey other)
/// </summary>
public override int GetHashCode()
{
return RedisValue.GetHashCode(this.value);
int chk0 = (keyPrefix == null) ? 0 : RedisValue.GetHashCode(this.keyPrefix),
chk1 = keyValue is string ? keyValue.GetHashCode() : RedisValue.GetHashCode((byte[])keyValue);
return unchecked((17 * chk0) + chk1);
}
/// <summary>
......@@ -148,7 +180,7 @@ public override string ToString()
internal RedisValue AsRedisValue()
{
return value;
return (byte[])this;
}
internal void AssertNotNull()
......@@ -162,7 +194,7 @@ internal void AssertNotNull()
public static implicit operator RedisKey(string key)
{
if (key == null) return default(RedisKey);
return new RedisKey(Encoding.UTF8.GetBytes(key));
return new RedisKey(null, key);
}
/// <summary>
/// Create a key from a Byte[]
......@@ -170,21 +202,33 @@ internal void AssertNotNull()
public static implicit operator RedisKey(byte[] key)
{
if (key == null) return default(RedisKey);
return new RedisKey(key);
return new RedisKey(null, key);
}
/// <summary>
/// Obtain the key as a Byte[]
/// </summary>
public static implicit operator byte[](RedisKey key)
{
return key.value;
return ConcatenateBytes(key.keyPrefix, key.keyValue, null);
}
/// <summary>
/// Obtain the key as a String
/// </summary>
public static implicit operator string(RedisKey key)
{
var arr = key.value;
byte[] arr;
if(key.keyPrefix == null)
{
if (key.keyValue == null) return null;
if (key.keyValue is string) return (string)key.keyValue;
arr = (byte[])key.keyValue;
}
else
{
arr = (byte[])key;
}
if (arr == null) return null;
try
{
......@@ -194,29 +238,61 @@ internal void AssertNotNull()
{
return BitConverter.ToString(arr);
}
}
/// <summary>
/// Concatenate two keys
/// </summary>
[Obsolete]
public static RedisKey operator +(RedisKey x, RedisKey y)
{
return Concatenate(x.value, y.value);
return new RedisKey(ConcatenateBytes(x.keyPrefix, x.keyValue, y.keyPrefix), y.keyValue);
}
internal static RedisKey WithPrefix(byte[] prefix, RedisKey value)
{
if(prefix == null || prefix.Length == 0) return value;
if (value.keyPrefix == null) return new RedisKey(prefix, value.keyValue);
if (value.keyValue == null) return new RedisKey(prefix, value.keyPrefix);
// two prefixes; darn
byte[] copy = new byte[prefix.Length + value.keyPrefix.Length];
Buffer.BlockCopy(prefix, 0, copy, 0, prefix.Length);
Buffer.BlockCopy(value.keyPrefix, 0, copy, prefix.Length, value.keyPrefix.Length);
return new RedisKey(copy, value.keyValue);
}
internal static byte[] Concatenate(byte[] x, byte[] y)
internal static byte[] ConcatenateBytes(byte[] a, object b, byte[] c)
{
if ((a == null || a.Length == 0) && (c == null || c.Length == 0))
{
// either null? yield the other; note this includes the "both null becomes null" case
if (x == null) return y;
if (y == null) return x;
if (b == null) return null;
if (b is string) return Encoding.UTF8.GetBytes((string)b);
return (byte[])b;
}
// either empty? yield the other
if (x.Length == 0) return y;
if (y.Length == 0) return x;
int aLen = a == null ? 0 : a.Length,
bLen = b == null ? 0 : (b is string
? Encoding.UTF8.GetByteCount((string)b)
: ((byte[])b).Length),
cLen = c == null ? 0 : c.Length;
byte[] result = new byte[x.Length + y.Length];
Buffer.BlockCopy(x, 0, result, 0, x.Length);
Buffer.BlockCopy(y, 0, result, x.Length, y.Length);
byte[] result = new byte[aLen + bLen + cLen];
if (aLen != 0) Buffer.BlockCopy(a, 0, result, 0, aLen);
if (bLen != 0)
{
if (b is string)
{
string s = (string)b;
Encoding.UTF8.GetBytes(s, 0, s.Length, result, aLen);
}
else
{
Buffer.BlockCopy((byte[])b, 0, result, aLen, bLen);
}
}
if (cLen != 0) Buffer.BlockCopy(c, 0, result, aLen + bLen, cLen);
return result;
}
}
......
......@@ -61,15 +61,16 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer)
/// <summary>
/// Computes the hash-slot that would be used by the given key
/// </summary>
public unsafe int HashSlot(byte[] key)
public unsafe int HashSlot(RedisKey key)
{
//HASH_SLOT = CRC16(key) mod 16384
if (key == null) return NoSlot;
if (key.IsNull) return NoSlot;
unchecked
{
fixed (byte* ptr = key)
var blob = (byte[])key;
fixed (byte* ptr = blob)
{
int offset = 0, count = key.Length, start, end;
int offset = 0, count = blob.Length, start, end;
if ((start = IndexOf(ptr, (byte)'{', 0, count - 1)) >= 0
&& (end = IndexOf(ptr, (byte)'}', start + 1, count)) >= 0
&& --end != start)
......@@ -183,10 +184,9 @@ internal int CombineSlot(int oldSlot, int newSlot)
}
internal int CombineSlot(int oldSlot, RedisKey key)
{
byte[] blob = key.Value;
if (oldSlot == MultipleSlots || (blob = key.Value) == null) return oldSlot;
if (oldSlot == MultipleSlots || key.IsNull) return oldSlot;
int newSlot = HashSlot(blob);
int newSlot = HashSlot(key);
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
......
......@@ -327,6 +327,16 @@ slave-priority 100
# Note that you must specify a directory here, not a file name.
# heapdir <directory path(absolute or relative)>
# If Redis is to be used as an in-memory-only cache without any kind of
# persistence, then the fork() mechanism used by the background AOF/RDB
# persistence is unneccessary. As an optimization, all persistence can be
# turned off in the Windows version of Redis. This will disable the creation of
# the memory mapped heap file, redirect heap allocations to the system heap
# allocator, and disable commands that would otherwise cause fork() operations:
# BGSAVE and BGREWRITEAOF. This flag may not be combined with any of the other
# flags that configure AOF and RDB operations.
# persistence-available [(yes)|no]
# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# accordingly to the eviction policy selected (see maxmemmory-policy).
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment