Commit e7a3485e authored by Marc Gravell's avatar Marc Gravell

fix partial interfaces; codemaid; split SocketManager.Poll.cs code out (mono vs .NET)

parent 15d1d2f4
...@@ -352,7 +352,7 @@ private void breakSocket_Click(object sender, EventArgs e) ...@@ -352,7 +352,7 @@ private void breakSocket_Click(object sender, EventArgs e)
{ {
try try
{ {
((IRedisServerDebug)muxer.GetServer(pair.EndPoint)).SimulateConnectionFailure(); muxer.GetServer(pair.EndPoint).SimulateConnectionFailure();
} catch(Exception ex) } catch(Exception ex)
{ {
Log(ex.Message); Log(ex.Message);
......
...@@ -24,7 +24,7 @@ public void AsyncTasksReportFailureIfServerUnavailable() ...@@ -24,7 +24,7 @@ public void AsyncTasksReportFailureIfServerUnavailable()
using(var conn = Create(allowAdmin: true)) using(var conn = Create(allowAdmin: true))
{ {
var server = (IRedisServerDebug)conn.GetServer(PrimaryServer, PrimaryPort); var server = conn.GetServer(PrimaryServer, PrimaryPort);
RedisKey key = Me(); RedisKey key = Me();
var db = conn.GetDatabase(); var db = conn.GetDatabase();
......
...@@ -521,7 +521,7 @@ public void TestQuit(bool preserveOrder) ...@@ -521,7 +521,7 @@ public void TestQuit(bool preserveOrder)
string key = Guid.NewGuid().ToString(); string key = Guid.NewGuid().ToString();
db.KeyDelete(key, CommandFlags.FireAndForget); db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, key, flags: CommandFlags.FireAndForget); db.StringSet(key, key, flags: CommandFlags.FireAndForget);
((IRedisDebug)GetServer(muxer)).Quit(CommandFlags.FireAndForget); GetServer(muxer).Quit(CommandFlags.FireAndForget);
var watch = Stopwatch.StartNew(); var watch = Stopwatch.StartNew();
try try
{ {
...@@ -550,7 +550,7 @@ public void TestSevered(bool preserveOrder) ...@@ -550,7 +550,7 @@ public void TestSevered(bool preserveOrder)
string key = Guid.NewGuid().ToString(); string key = Guid.NewGuid().ToString();
db.KeyDelete(key, CommandFlags.FireAndForget); db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, key, flags: CommandFlags.FireAndForget); db.StringSet(key, key, flags: CommandFlags.FireAndForget);
((IRedisServerDebug)GetServer(muxer)).SimulateConnectionFailure(); GetServer(muxer).SimulateConnectionFailure();
var watch = Stopwatch.StartNew(); var watch = Stopwatch.StartNew();
db.Ping(); db.Ping();
watch.Stop(); watch.Stop();
......
...@@ -146,19 +146,19 @@ public void IntentionalWrongServer() ...@@ -146,19 +146,19 @@ public void IntentionalWrongServer()
#if DEBUG #if DEBUG
string a = ((IRedisServerDebug)conn.GetServer(rightMasterNode.EndPoint)).StringGet(db.Database, key); string a = conn.GetServer(rightMasterNode.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, a, "right master"); Assert.AreEqual(value, a, "right master");
var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId); var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId);
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string b = ((IRedisServerDebug)conn.GetServer(node.EndPoint)).StringGet(db.Database, key); string b = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, b, "wrong master, allow redirect"); Assert.AreEqual(value, b, "wrong master, allow redirect");
try try
{ {
string c = ((IRedisServerDebug)conn.GetServer(node.EndPoint)).StringGet(db.Database, key, CommandFlags.NoRedirect); string c = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect);
Assert.Fail("wrong master, no redirect"); Assert.Fail("wrong master, no redirect");
} catch (RedisServerException ex) } catch (RedisServerException ex)
{ {
...@@ -170,7 +170,7 @@ public void IntentionalWrongServer() ...@@ -170,7 +170,7 @@ public void IntentionalWrongServer()
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string d = ((IRedisServerDebug)conn.GetServer(node.EndPoint)).StringGet(db.Database, key); string d = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, d, "right slave"); Assert.AreEqual(value, d, "right slave");
} }
...@@ -178,12 +178,12 @@ public void IntentionalWrongServer() ...@@ -178,12 +178,12 @@ public void IntentionalWrongServer()
Assert.IsNotNull(node); Assert.IsNotNull(node);
if (node != null) if (node != null)
{ {
string e = ((IRedisServerDebug)conn.GetServer(node.EndPoint)).StringGet(db.Database, key); string e = conn.GetServer(node.EndPoint).StringGet(db.Database, key);
Assert.AreEqual(value, e, "wrong slave, allow redirect"); Assert.AreEqual(value, e, "wrong slave, allow redirect");
try try
{ {
string f = ((IRedisServerDebug)conn.GetServer(node.EndPoint)).StringGet(db.Database, key, CommandFlags.NoRedirect); string f = conn.GetServer(node.EndPoint).StringGet(db.Database, key, CommandFlags.NoRedirect);
Assert.Fail("wrong slave, no redirect"); Assert.Fail("wrong slave, no redirect");
} }
catch (RedisServerException ex) catch (RedisServerException ex)
......
...@@ -116,7 +116,7 @@ public void ClientName() ...@@ -116,7 +116,7 @@ public void ClientName()
var conn = muxer.GetDatabase(); var conn = muxer.GetDatabase();
conn.Ping(); conn.Ping();
#if DEBUG #if DEBUG
var name = ((IRedisDebug)GetServer(muxer)).ClientGetName(); var name = GetServer(muxer).ClientGetName();
Assert.AreEqual("TestRig", name); Assert.AreEqual("TestRig", name);
#endif #endif
} }
......
...@@ -41,7 +41,7 @@ public void ShutdownRaisesConnectionFailedAndRestore() ...@@ -41,7 +41,7 @@ public void ShutdownRaisesConnectionFailedAndRestore()
#if DEBUG #if DEBUG
conn.AllowConnect = false; conn.AllowConnect = false;
var server = (IRedisServerDebug)conn.GetServer(PrimaryServer, PrimaryPort); var server = conn.GetServer(PrimaryServer, PrimaryPort);
SetExpectedAmbientFailureCount(2); SetExpectedAmbientFailureCount(2);
server.SimulateConnectionFailure(); server.SimulateConnectionFailure();
......
...@@ -355,7 +355,7 @@ public void SubscriptionsSurviveConnectionFailure() ...@@ -355,7 +355,7 @@ public void SubscriptionsSurviveConnectionFailure()
Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets"); Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets");
#if DEBUG #if DEBUG
((IRedisServerDebug)server).SimulateConnectionFailure(); server.SimulateConnectionFailure();
SetExpectedAmbientFailureCount(2); SetExpectedAmbientFailureCount(2);
#endif #endif
......
...@@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ...@@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
monobuild.bash = monobuild.bash monobuild.bash = monobuild.bash
monobuild.cmd = monobuild.cmd monobuild.cmd = monobuild.cmd
netbuild.cmd = netbuild.cmd
StackExchange.Redis.nuspec = StackExchange.Redis.nuspec StackExchange.Redis.nuspec = StackExchange.Redis.nuspec
EndProjectSection EndProjectSection
EndProject EndProject
......
...@@ -136,6 +136,9 @@ ...@@ -136,6 +136,9 @@
<Compile Include="StackExchange\Redis\ServerType.cs" /> <Compile Include="StackExchange\Redis\ServerType.cs" />
<Compile Include="StackExchange\Redis\SetOperation.cs" /> <Compile Include="StackExchange\Redis\SetOperation.cs" />
<Compile Include="StackExchange\Redis\SocketManager.cs" /> <Compile Include="StackExchange\Redis\SocketManager.cs" />
<Compile Include="StackExchange\Redis\SocketManager.NoPoll.cs">
<DependentUpon>SocketManager.cs</DependentUpon>
</Compile>
<Compile Include="StackExchange\Redis\SortType.cs" /> <Compile Include="StackExchange\Redis\SortType.cs" />
<Compile Include="StackExchange\Redis\StringSplits.cs" /> <Compile Include="StackExchange\Redis\StringSplits.cs" />
<Compile Include="StackExchange\Redis\TaskSource.cs" /> <Compile Include="StackExchange\Redis\TaskSource.cs" />
...@@ -143,6 +146,11 @@ ...@@ -143,6 +146,11 @@
<Compile Include="StackExchange\Redis\ShutdownMode.cs" /> <Compile Include="StackExchange\Redis\ShutdownMode.cs" />
<Compile Include="StackExchange\Redis\SaveType.cs" /> <Compile Include="StackExchange\Redis\SaveType.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="StackExchange\Redis\SocketManager.Poll.cs">
<DependentUpon>SocketManager.cs</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
......
...@@ -16,32 +16,6 @@ public sealed class ClientInfo ...@@ -16,32 +16,6 @@ public sealed class ClientInfo
/// </summary> /// </summary>
public EndPoint Address { get; private set; } public EndPoint Address { get; private set; }
/// <summary>
/// The host of the client (typically an IP address)
/// </summary>
public string Host
{
get
{
string host;
int port;
return Format.TryGetHostPort(Address, out host, out port) ? host : null;
}
}
/// <summary>
/// The port of the client
/// </summary>
public int Port
{
get
{
string host;
int port;
return Format.TryGetHostPort(Address, out host, out port) ? port : 0;
}
}
/// <summary> /// <summary>
/// total duration of the connection in seconds /// total duration of the connection in seconds
/// </summary> /// </summary>
...@@ -73,6 +47,19 @@ public int Port ...@@ -73,6 +47,19 @@ public int Port
/// </summary> /// </summary>
public string FlagsRaw { get; private set; } public string FlagsRaw { get; private set; }
/// <summary>
/// The host of the client (typically an IP address)
/// </summary>
public string Host
{
get
{
string host;
int port;
return Format.TryGetHostPort(Address, out host, out port) ? host : null;
}
}
/// <summary> /// <summary>
/// idle time of the connection in seconds /// idle time of the connection in seconds
/// </summary> /// </summary>
...@@ -93,6 +80,18 @@ public int Port ...@@ -93,6 +80,18 @@ public int Port
/// </summary> /// </summary>
public int PatternSubscriptionCount { get; private set; } public int PatternSubscriptionCount { get; private set; }
/// <summary>
/// The port of the client
/// </summary>
public int Port
{
get
{
string host;
int port;
return Format.TryGetHostPort(Address, out host, out port) ? port : 0;
}
}
/// <summary> /// <summary>
/// The raw content from redis /// The raw content from redis
/// </summary> /// </summary>
......
...@@ -41,6 +41,22 @@ private SlotRange(short from, short to) ...@@ -41,6 +41,22 @@ private SlotRange(short from, short to)
/// </summary> /// </summary>
public int To { get { return to; } } public int To { get { return to; } }
/// <summary>
/// Indicates whether two ranges are not equal
/// </summary>
public static bool operator !=(SlotRange x, SlotRange y)
{
return x.from != y.from || x.to != y.to;
}
/// <summary>
/// Indicates whether two ranges are equal
/// </summary>
public static bool operator ==(SlotRange x, SlotRange y)
{
return x.from == y.from && x.to == y.to;
}
/// <summary> /// <summary>
/// Try to parse a string as a range /// Try to parse a string as a range
/// </summary> /// </summary>
...@@ -93,22 +109,6 @@ public override bool Equals(object obj) ...@@ -93,22 +109,6 @@ public override bool Equals(object obj)
} }
return false; return false;
} }
/// <summary>
/// Indicates whether two ranges are equal
/// </summary>
public static bool operator ==(SlotRange x, SlotRange y)
{
return x.from == y.from && x.to == y.to;
}
/// <summary>
/// Indicates whether two ranges are not equal
/// </summary>
public static bool operator !=(SlotRange x, SlotRange y)
{
return x.from != y.from || x.to != y.to;
}
/// <summary> /// <summary>
/// Indicates whether two ranges are equal /// Indicates whether two ranges are equal
/// </summary> /// </summary>
...@@ -122,7 +122,8 @@ public bool Equals(SlotRange range) ...@@ -122,7 +122,8 @@ public bool Equals(SlotRange range)
/// </summary> /// </summary>
public override int GetHashCode() public override int GetHashCode()
{ {
return (int)from | ((int)to << 16); int x = from, y = to; // makes CS0675 a little happier
return x | (y << 16);
} }
/// <summary> /// <summary>
......
...@@ -16,6 +16,7 @@ sealed partial class CompletionManager ...@@ -16,6 +16,7 @@ sealed partial class CompletionManager
private readonly string name; private readonly string name;
int activeAsyncWorkerThread = 0;
long completedSync, completedAsync, failedAsync; long completedSync, completedAsync, failedAsync;
public CompletionManager(ConnectionMultiplexer multiplexer, string name) public CompletionManager(ConnectionMultiplexer multiplexer, string name)
{ {
...@@ -110,8 +111,6 @@ private static void ProcessAsyncCompletionQueue(object state) ...@@ -110,8 +111,6 @@ private static void ProcessAsyncCompletionQueue(object state)
} }
partial void OnCompletedAsync(); partial void OnCompletedAsync();
int activeAsyncWorkerThread = 0;
private void ProcessAsyncCompletionQueueImpl() private void ProcessAsyncCompletionQueueImpl()
{ {
int currentThread = Environment.CurrentManagedThreadId; int currentThread = Environment.CurrentManagedThreadId;
......
...@@ -12,8 +12,6 @@ public abstract class Condition ...@@ -12,8 +12,6 @@ public abstract class Condition
private Condition() { } private Condition() { }
internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy);
/// <summary> /// <summary>
/// Enforces that the given hash-field must have the specified value /// Enforces that the given hash-field must have the specified value
/// </summary> /// </summary>
...@@ -53,43 +51,44 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField) ...@@ -53,43 +51,44 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField)
} }
/// <summary> /// <summary>
/// Enforces that the given key must have the specified value /// Enforces that the given key must exist
/// </summary> /// </summary>
public static Condition StringEqual(RedisKey key, RedisValue value) public static Condition KeyExists(RedisKey key)
{ {
if (value.IsNull) return KeyNotExists(key); return new ExistsCondition(key, RedisValue.Null, true);
return new EqualsCondition(key, RedisValue.Null, true, value);
} }
/// <summary> /// <summary>
/// Enforces that the given key must exist /// Enforces that the given key must not exist
/// </summary> /// </summary>
public static Condition KeyExists(RedisKey key) public static Condition KeyNotExists(RedisKey key)
{ {
return new ExistsCondition(key, RedisValue.Null, true); return new ExistsCondition(key, RedisValue.Null, false);
} }
/// <summary> /// <summary>
/// Enforces that the given key must not have the specified value /// Enforces that the given key must have the specified value
/// </summary> /// </summary>
public static Condition StringNotEqual(RedisKey key, RedisValue value) public static Condition StringEqual(RedisKey key, RedisValue value)
{ {
if (value.IsNull) return KeyExists(key); if (value.IsNull) return KeyNotExists(key);
return new EqualsCondition(key, RedisValue.Null, false, value); return new EqualsCondition(key, RedisValue.Null, true, value);
} }
/// <summary> /// <summary>
/// Enforces that the given key must not exist /// Enforces that the given key must not have the specified value
/// </summary> /// </summary>
public static Condition KeyNotExists(RedisKey key) public static Condition StringNotEqual(RedisKey key, RedisValue value)
{ {
return new ExistsCondition(key, RedisValue.Null, false); if (value.IsNull) return KeyExists(key);
return new EqualsCondition(key, RedisValue.Null, false, value);
} }
internal abstract void CheckCommands(CommandMap commandMap); internal abstract void CheckCommands(CommandMap commandMap);
internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox); internal abstract IEnumerable<Message> CreateMessages(int db, ResultBox resultBox);
internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy);
internal abstract bool TryValidate(RawResult result, out bool value); internal abstract bool TryValidate(RawResult result, out bool value);
internal sealed class ConditionProcessor : ResultProcessor<bool> internal sealed class ConditionProcessor : ResultProcessor<bool>
...@@ -159,15 +158,12 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult) ...@@ -159,15 +158,12 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
this.expectedResult = expectedResult; this.expectedResult = expectedResult;
} }
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
public override string ToString() public override string ToString()
{ {
return (hashField.IsNull ? key.ToString() : key + " > " + hashField) return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
+ (expectedResult ? " exists" : " does not exists"); + (expectedResult ? " exists" : " does not exists");
} }
internal override void CheckCommands(CommandMap commandMap) internal override void CheckCommands(CommandMap commandMap)
{ {
commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS); commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS);
...@@ -182,6 +178,11 @@ internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBo ...@@ -182,6 +178,11 @@ internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBo
message.SetSource(ConditionProcessor.Default, resultBox); message.SetSource(ConditionProcessor.Default, resultBox);
yield return message; yield return message;
} }
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
internal override bool TryValidate(RawResult result, out bool value) internal override bool TryValidate(RawResult result, out bool value)
{ {
bool parsed; bool parsed;
...@@ -210,11 +211,6 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R ...@@ -210,11 +211,6 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R
this.expectedValue = expectedValue; this.expectedValue = expectedValue;
} }
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
public override string ToString() public override string ToString()
{ {
return (hashField.IsNull ? key.ToString() : key + " > " + hashField) return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
...@@ -236,6 +232,11 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, ResultBox r ...@@ -236,6 +232,11 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, ResultBox r
message.SetSource(ConditionProcessor.Default, resultBox); message.SetSource(ConditionProcessor.Default, resultBox);
yield return message; yield return message;
} }
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
internal override bool TryValidate(RawResult result, out bool value) internal override bool TryValidate(RawResult result, out bool value)
{ {
switch (result.Type) switch (result.Type)
......
...@@ -40,20 +40,18 @@ public sealed class ConfigurationOptions : ICloneable ...@@ -40,20 +40,18 @@ public sealed class ConfigurationOptions : ICloneable
private readonly EndPointCollection endpoints = new EndPointCollection(); private readonly EndPointCollection endpoints = new EndPointCollection();
/// <summary>
/// Automatically encodes and decodes channels
/// </summary>
public RedisChannel ChannelPrefix { get;set; }
private bool? allowAdmin, abortOnConnectFail, resolveDns; private bool? allowAdmin, abortOnConnectFail, resolveDns;
private Proxy? proxy;
private CommandMap commandMap;
private string clientName, serviceName, password, tieBreaker, sslHost, configChannel; private string clientName, serviceName, password, tieBreaker, sslHost, configChannel;
private CommandMap commandMap;
private Version defaultVersion; private Version defaultVersion;
private int? keepAlive, syncTimeout, connectTimeout, writeBuffer; private int? keepAlive, syncTimeout, connectTimeout, writeBuffer;
private Proxy? proxy;
/// <summary> /// <summary>
/// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note /// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note
/// that this cannot be specified in the configuration-string. /// that this cannot be specified in the configuration-string.
...@@ -69,15 +67,9 @@ public sealed class ConfigurationOptions : ICloneable ...@@ -69,15 +67,9 @@ public sealed class ConfigurationOptions : ICloneable
public event RemoteCertificateValidationCallback CertificateValidation; public event RemoteCertificateValidationCallback CertificateValidation;
/// <summary> /// <summary>
/// Gets or sets the SocketManager instance to be used with these options; if this is null a per-multiplexer /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException
/// SocketManager is created automatically.
/// </summary>
public SocketManager SocketManager { get;set; }
/// <summary>
/// Indicates whether admin operations should be allowed
/// </summary> /// </summary>
public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } } public bool AbortOnConnectFail { get { return abortOnConnectFail ?? true; } set { abortOnConnectFail = value; } }
/// <summary> /// <summary>
/// Indicates whether admin operations should be allowed /// Indicates whether admin operations should be allowed
...@@ -85,10 +77,9 @@ public sealed class ConfigurationOptions : ICloneable ...@@ -85,10 +77,9 @@ public sealed class ConfigurationOptions : ICloneable
public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } } public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } }
/// <summary> /// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting /// Automatically encodes and decodes channels
/// </summary> /// </summary>
public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } } public RedisChannel ChannelPrefix { get;set; }
/// <summary> /// <summary>
/// The client name to user for all connections /// The client name to user for all connections
/// </summary> /// </summary>
...@@ -102,7 +93,7 @@ public CommandMap CommandMap ...@@ -102,7 +93,7 @@ public CommandMap CommandMap
get get
{ {
if (commandMap != null) return commandMap; if (commandMap != null) return commandMap;
switch(Proxy) switch (Proxy)
{ {
case Redis.Proxy.Twemproxy: case Redis.Proxy.Twemproxy:
return CommandMap.Twemproxy; return CommandMap.Twemproxy;
...@@ -110,7 +101,8 @@ public CommandMap CommandMap ...@@ -110,7 +101,8 @@ public CommandMap CommandMap
return CommandMap.Default; return CommandMap.Default;
} }
} }
set { set
{
if (value == null) throw new ArgumentNullException("value"); if (value == null) throw new ArgumentNullException("value");
commandMap = value; commandMap = value;
} }
...@@ -146,11 +138,26 @@ public CommandMap CommandMap ...@@ -146,11 +138,26 @@ public CommandMap CommandMap
/// </summary> /// </summary>
public string Password { get { return password; } set { password = value; } } public string Password { get { return password; } set { password = value; } }
/// <summary>
/// Indicates whether admin operations should be allowed
/// </summary>
public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } }
/// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting
/// </summary>
public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } }
/// <summary> /// <summary>
/// The service name used to resolve a service via sentinel /// The service name used to resolve a service via sentinel
/// </summary> /// </summary>
public string ServiceName { get { return serviceName; } set { serviceName = value; } } public string ServiceName { get { return serviceName; } set { serviceName = value; } }
/// <summary>
/// Gets or sets the SocketManager instance to be used with these options; if this is null a per-multiplexer
/// SocketManager is created automatically.
/// </summary>
public SocketManager SocketManager { get;set; }
/// <summary> /// <summary>
/// The target-host to use when validating SSL certificate; setting a value here enables SSL mode /// The target-host to use when validating SSL certificate; setting a value here enables SSL mode
/// </summary> /// </summary>
...@@ -174,12 +181,6 @@ public CommandMap CommandMap ...@@ -174,12 +181,6 @@ public CommandMap CommandMap
// these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream // these just rip out the underlying handlers, bypassing the event accessors - needed when creating the SSL stream
internal RemoteCertificateValidationCallback CertificateValidationCallback { get { return CertificateValidation; } private set { CertificateValidation = value; } } internal RemoteCertificateValidationCallback CertificateValidationCallback { get { return CertificateValidation; } private set { CertificateValidation = value; } }
/// <summary>
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException
/// </summary>
public bool AbortOnConnectFail { get { return abortOnConnectFail ?? true; } set { abortOnConnectFail = value; } }
/// <summary> /// <summary>
/// Parse the configuration from a comma-delimited configuration string /// Parse the configuration from a comma-delimited configuration string
/// </summary> /// </summary>
......
using System; using System.Text;
using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -43,41 +42,15 @@ public bool IsEmpty ...@@ -43,41 +42,15 @@ public bool IsEmpty
} }
/// <summary> /// <summary>
/// The number of operations performed on this connection /// Indicates the total number of messages despatched to a non-preferred endpoint, for example sent to a master
/// when the caller stated a preference of slave
/// </summary> /// </summary>
public long OperationCount { get; internal set; } public long NonPreferredEndpointCount { get; internal set; }
/// <summary> /// <summary>
/// The number of subscriptions (with and without patterns) currently held against this connection /// The number of operations performed on this connection
/// </summary> /// </summary>
public long Subscriptions { get;internal set; } public long OperationCount { get; internal set; }
internal void Add(ConnectionCounters other)
{
if (other == null) return;
this.CompletedAsynchronously += other.CompletedAsynchronously;
this.CompletedSynchronously += other.CompletedSynchronously;
this.FailedAsynchronously += other.FailedAsynchronously;
this.OperationCount += other.OperationCount;
this.PendingUnsentItems += other.PendingUnsentItems;
this.ResponsesAwaitingAsyncCompletion += other.ResponsesAwaitingAsyncCompletion;
this.SentItemsAwaitingResponse += other.SentItemsAwaitingResponse;
this.SocketCount += other.SocketCount;
this.Subscriptions += other.Subscriptions;
this.WriterCount += other.WriterCount;
this.NonPreferredEndpointCount += other.NonPreferredEndpointCount;
}
internal bool Any()
{
return CompletedAsynchronously != 0 || CompletedSynchronously != 0
|| FailedAsynchronously != 0 || OperationCount != 0
|| PendingUnsentItems != 0 || ResponsesAwaitingAsyncCompletion != 0
|| SentItemsAwaitingResponse != 0 || SocketCount != 0
|| Subscriptions != 0 || WriterCount != 0
|| NonPreferredEndpointCount != 0;
}
/// <summary> /// <summary>
/// Operations that have been requested, but which have not yet been sent to the server /// Operations that have been requested, but which have not yet been sent to the server
...@@ -99,6 +72,11 @@ internal bool Any() ...@@ -99,6 +72,11 @@ internal bool Any()
/// </summary> /// </summary>
public long SocketCount { get; internal set; } public long SocketCount { get; internal set; }
/// <summary>
/// The number of subscriptions (with and without patterns) currently held against this connection
/// </summary>
public long Subscriptions { get;internal set; }
/// <summary> /// <summary>
/// Indicates the total number of outstanding items against this connection /// Indicates the total number of outstanding items against this connection
/// </summary> /// </summary>
...@@ -108,13 +86,6 @@ internal bool Any() ...@@ -108,13 +86,6 @@ internal bool Any()
/// Indicates the total number of writers items against this connection /// Indicates the total number of writers items against this connection
/// </summary> /// </summary>
public int WriterCount { get; internal set; } public int WriterCount { get; internal set; }
/// <summary>
/// Indicates the total number of messages despatched to a non-preferred endpoint, for example sent to a master
/// when the caller stated a preference of slave
/// </summary>
public long NonPreferredEndpointCount { get; internal set; }
/// <summary> /// <summary>
/// See Object.ToString() /// See Object.ToString()
...@@ -126,6 +97,31 @@ public override string ToString() ...@@ -126,6 +97,31 @@ public override string ToString()
return sb.ToString(); return sb.ToString();
} }
internal void Add(ConnectionCounters other)
{
if (other == null) return;
this.CompletedAsynchronously += other.CompletedAsynchronously;
this.CompletedSynchronously += other.CompletedSynchronously;
this.FailedAsynchronously += other.FailedAsynchronously;
this.OperationCount += other.OperationCount;
this.PendingUnsentItems += other.PendingUnsentItems;
this.ResponsesAwaitingAsyncCompletion += other.ResponsesAwaitingAsyncCompletion;
this.SentItemsAwaitingResponse += other.SentItemsAwaitingResponse;
this.SocketCount += other.SocketCount;
this.Subscriptions += other.Subscriptions;
this.WriterCount += other.WriterCount;
this.NonPreferredEndpointCount += other.NonPreferredEndpointCount;
}
internal bool Any()
{
return CompletedAsynchronously != 0 || CompletedSynchronously != 0
|| FailedAsynchronously != 0 || OperationCount != 0
|| PendingUnsentItems != 0 || ResponsesAwaitingAsyncCompletion != 0
|| SentItemsAwaitingResponse != 0 || SocketCount != 0
|| Subscriptions != 0 || WriterCount != 0
|| NonPreferredEndpointCount != 0;
}
internal void Append(StringBuilder sb) internal void Append(StringBuilder sb)
{ {
sb.Append("ops=").Append(OperationCount).Append(", qu=").Append(PendingUnsentItems) sb.Append("ops=").Append(OperationCount).Append(", qu=").Append(PendingUnsentItems)
......
...@@ -9,8 +9,8 @@ namespace StackExchange.Redis ...@@ -9,8 +9,8 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable
{ {
private readonly EndPoint endpoint;
private readonly ConnectionType connectionType; private readonly ConnectionType connectionType;
private readonly EndPoint endpoint;
private readonly Exception exception; private readonly Exception exception;
private readonly ConnectionFailureType failureType; private readonly ConnectionFailureType failureType;
private readonly EventHandler<ConnectionFailedEventArgs> handler; private readonly EventHandler<ConnectionFailedEventArgs> handler;
...@@ -26,21 +26,20 @@ internal ConnectionFailedEventArgs(EventHandler<ConnectionFailedEventArgs> handl ...@@ -26,21 +26,20 @@ internal ConnectionFailedEventArgs(EventHandler<ConnectionFailedEventArgs> handl
} }
/// <summary> /// <summary>
/// Gets the failing server-endpoint /// Gets the connection-type of the failing connection
/// </summary> /// </summary>
public EndPoint EndPoint public ConnectionType ConnectionType
{ {
get { return endpoint; } get { return connectionType; }
} }
/// <summary> /// <summary>
/// Gets the connection-type of the failing connection /// Gets the failing server-endpoint
/// </summary> /// </summary>
public ConnectionType ConnectionType public EndPoint EndPoint
{ {
get { return connectionType; } get { return endpoint; }
} }
/// <summary> /// <summary>
/// Gets the exception if available (this can be null) /// Gets the exception if available (this can be null)
/// </summary> /// </summary>
...@@ -56,16 +55,16 @@ public ConnectionFailureType FailureType ...@@ -56,16 +55,16 @@ public ConnectionFailureType FailureType
{ {
get { return failureType; } get { return failureType; }
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb) void ICompletable.AppendStormLog(StringBuilder sb)
{ {
sb.Append("event, connection-failed: "); sb.Append("event, connection-failed: ");
if (endpoint == null) sb.Append("n/a"); if (endpoint == null) sb.Append("n/a");
else sb.Append(Format.ToString(endpoint)); else sb.Append(Format.ToString(endpoint));
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
} }
} }
using System; using System;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -25,10 +20,7 @@ public static long GetAllocationCount() ...@@ -25,10 +20,7 @@ public static long GetAllocationCount()
Interlocked.Increment(ref ResultBox.allocations); Interlocked.Increment(ref ResultBox.allocations);
} }
} }
/// <summary> partial interface IServer
/// Additional IRedisServer methods for debugging
/// </summary>
public interface IRedisServerDebug : IServer
{ {
/// <summary> /// <summary>
/// Show what is in the pending (unsent) queue /// Show what is in the pending (unsent) queue
...@@ -65,10 +57,7 @@ public interface IRedisServerDebug : IServer ...@@ -65,10 +57,7 @@ public interface IRedisServerDebug : IServer
/// <remarks>http://redis.io/commands/client-pause</remarks> /// <remarks>http://redis.io/commands/client-pause</remarks>
void Hang(TimeSpan duration, CommandFlags flags = CommandFlags.None); void Hang(TimeSpan duration, CommandFlags flags = CommandFlags.None);
} }
/// <summary> partial interface IRedis
/// Additional IRedis methods for debugging
/// </summary>
public interface IRedisDebug : IRedis, IRedisDebugAsync
{ {
/// <summary> /// <summary>
/// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned. /// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned.
...@@ -83,10 +72,8 @@ public interface IRedisDebug : IRedis, IRedisDebugAsync ...@@ -83,10 +72,8 @@ public interface IRedisDebug : IRedis, IRedisDebugAsync
/// <remarks>http://redis.io/commands/quit</remarks> /// <remarks>http://redis.io/commands/quit</remarks>
void Quit(CommandFlags flags = CommandFlags.None); void Quit(CommandFlags flags = CommandFlags.None);
} }
/// <summary>
/// Additional IRedisAsync methods for debugging partial interface IRedisAsync
/// </summary>
public interface IRedisDebugAsync : IRedisAsync
{ {
/// <summary> /// <summary>
/// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned. /// The CLIENT GETNAME returns the name of the current connection as set by CLIENT SETNAME. Since every new connection starts without an associated name, if no name was assigned a null string is returned.
...@@ -95,15 +82,15 @@ public interface IRedisDebugAsync : IRedisAsync ...@@ -95,15 +82,15 @@ public interface IRedisDebugAsync : IRedisAsync
/// <returns>The connection name, or a null string if no name is set.</returns> /// <returns>The connection name, or a null string if no name is set.</returns>
Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None); Task<string> ClientGetNameAsync(CommandFlags flags = CommandFlags.None);
} }
partial class RedisBase : IRedisDebug partial class RedisBase
{ {
string IRedisDebug.ClientGetName(CommandFlags flags) string IRedis.ClientGetName(CommandFlags flags)
{ {
var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME); var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME);
return ExecuteSync(msg, ResultProcessor.String); return ExecuteSync(msg, ResultProcessor.String);
} }
Task<string> IRedisDebugAsync.ClientGetNameAsync(CommandFlags flags) Task<string> IRedisAsync.ClientGetNameAsync(CommandFlags flags)
{ {
var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME); var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.GETNAME);
return ExecuteAsync(msg, ResultProcessor.String); return ExecuteAsync(msg, ResultProcessor.String);
...@@ -130,23 +117,23 @@ internal string ListPending(int maxCount) ...@@ -130,23 +117,23 @@ internal string ListPending(int maxCount)
} }
} }
partial class RedisServer : IRedisServerDebug partial class RedisServer
{ {
void IRedisServerDebug.SimulateConnectionFailure() void IServer.SimulateConnectionFailure()
{ {
server.SimulateConnectionFailure(); server.SimulateConnectionFailure();
} }
string IRedisServerDebug.ListPending(int maxCount) string IServer.ListPending(int maxCount)
{ {
return server.ListPending(maxCount); return server.ListPending(maxCount);
} }
void IRedisServerDebug.Crash() void IServer.Crash()
{ {
// using DB-0 because we also use "DEBUG OBJECT", which is db-centric // using DB-0 because we also use "DEBUG OBJECT", which is db-centric
var msg = Message.Create(0, CommandFlags.FireAndForget, RedisCommand.DEBUG, RedisLiterals.SEGFAULT); var msg = Message.Create(0, CommandFlags.FireAndForget, RedisCommand.DEBUG, RedisLiterals.SEGFAULT);
ExecuteSync(msg, ResultProcessor.DemandOK); ExecuteSync(msg, ResultProcessor.DemandOK);
} }
void IRedisServerDebug.Hang(TimeSpan duration, CommandFlags flags) void IServer.Hang(TimeSpan duration, CommandFlags flags)
{ {
var msg = Message.Create(0, flags, RedisCommand.CLIENT, RedisLiterals.PAUSE, (long)duration.TotalMilliseconds); var msg = Message.Create(0, flags, RedisCommand.CLIENT, RedisLiterals.PAUSE, (long)duration.TotalMilliseconds);
ExecuteSync(msg, ResultProcessor.DemandOK); ExecuteSync(msg, ResultProcessor.DemandOK);
......
...@@ -9,13 +9,6 @@ namespace StackExchange.Redis ...@@ -9,13 +9,6 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public sealed class EndPointCollection : Collection<EndPoint> public sealed class EndPointCollection : Collection<EndPoint>
{ {
/// <summary>
/// Attempt to parse a string into an EndPoint
/// </summary>
public static EndPoint TryParse(string endpoint)
{
return Format.TryParseEndPoint(endpoint);
}
/// <summary> /// <summary>
/// Format an endpoint /// Format an endpoint
/// </summary> /// </summary>
...@@ -24,6 +17,13 @@ public static string ToString(EndPoint endpoint) ...@@ -24,6 +17,13 @@ public static string ToString(EndPoint endpoint)
return Format.ToString(endpoint); return Format.ToString(endpoint);
} }
/// <summary>
/// Attempt to parse a string into an EndPoint
/// </summary>
public static EndPoint TryParse(string endpoint)
{
return Format.TryParseEndPoint(endpoint);
}
/// <summary> /// <summary>
/// Adds a new endpoint to the list /// Adds a new endpoint to the list
/// </summary> /// </summary>
......
...@@ -26,10 +26,6 @@ public EndPoint EndPoint ...@@ -26,10 +26,6 @@ public EndPoint EndPoint
{ {
get { return endpoint; } get { return endpoint; }
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb) void ICompletable.AppendStormLog(StringBuilder sb)
{ {
sb.Append("event, endpoint: "); sb.Append("event, endpoint: ");
...@@ -37,5 +33,9 @@ void ICompletable.AppendStormLog(StringBuilder sb) ...@@ -37,5 +33,9 @@ void ICompletable.AppendStormLog(StringBuilder sb)
else sb.Append(Format.ToString(endpoint)); else sb.Append(Format.ToString(endpoint));
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
} }
} }
\ No newline at end of file
...@@ -4,6 +4,9 @@ namespace StackExchange.Redis ...@@ -4,6 +4,9 @@ namespace StackExchange.Redis
{ {
internal static class ExceptionFactory internal static class ExceptionFactory
{ {
const string DataCommandKey = "redis-command",
DataServerKey = "redis-server";
internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
string s = GetLabel(includeDetail, command, message); string s = GetLabel(includeDetail, command, message);
...@@ -11,42 +14,26 @@ internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand c ...@@ -11,42 +14,26 @@ internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand c
if (includeDetail) AddDetail(ex, message, server, s); if (includeDetail) AddDetail(ex, message, server, s);
return ex; return ex;
} }
static string GetLabel(bool includeDetail, RedisCommand command, Message message) internal static Exception CommandDisabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.Command.ToString());
}
internal static Exception NoConnectionAvailable(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
string s = GetLabel(includeDetail, command, message); string s = GetLabel(includeDetail, command, message);
var ex = new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, "No connection is available to service this operation: " + s); var ex = new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + s);
if (includeDetail) AddDetail(ex, message, server, s); if (includeDetail) AddDetail(ex, message, server, s);
return ex; return ex;
} }
const string DataCommandKey = "redis-command", internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint server)
DataServerKey = "redis-server";
private static void AddDetail(Exception exception, Message message, ServerEndPoint server, string label)
{
if (exception != null)
{
if (message != null) exception.Data.Add(DataCommandKey, message.CommandAndKey);
else if(label != null) exception.Data.Add(DataCommandKey, label);
if (server != null) exception.Data.Add(DataServerKey, Format.ToString(server.EndPoint));
}
}
internal static Exception CommandDisabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
string s = GetLabel(includeDetail, command, message); var ex = new RedisConnectionException(failureType, message);
var ex = new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + s); if (includeDetail) AddDetail(ex, null, server, null);
if (includeDetail) AddDetail(ex, message, server, s);
return ex; return ex;
} }
internal static Exception MultiSlot(bool includeDetail, Message message)
internal static Exception DatabaseNotRequired(bool includeDetail, RedisCommand command)
{ {
var ex = new RedisCommandException("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot"); string s = command.ToString();
if (includeDetail) AddDetail(ex, message, null, null); var ex = new RedisCommandException("A target database is not required for " + s);
if (includeDetail) AddDetail(ex, null, null, s);
return ex; return ex;
} }
...@@ -65,14 +52,6 @@ internal static Exception DatabaseRequired(bool includeDetail, RedisCommand comm ...@@ -65,14 +52,6 @@ internal static Exception DatabaseRequired(bool includeDetail, RedisCommand comm
return ex; return ex;
} }
internal static Exception DatabaseNotRequired(bool includeDetail, RedisCommand command)
{
string s = command.ToString();
var ex = new RedisCommandException("A target database is not required for " + s);
if (includeDetail) AddDetail(ex, null, null, s);
return ex;
}
internal static Exception MasterOnly(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) internal static Exception MasterOnly(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
string s = GetLabel(includeDetail, command, message); string s = GetLabel(includeDetail, command, message);
...@@ -81,17 +60,18 @@ internal static Exception MasterOnly(bool includeDetail, RedisCommand command, M ...@@ -81,17 +60,18 @@ internal static Exception MasterOnly(bool includeDetail, RedisCommand command, M
return ex; return ex;
} }
internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server) internal static Exception MultiSlot(bool includeDetail, Message message)
{ {
var ex = new TimeoutException(errorMessage); var ex = new RedisCommandException("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot");
if (includeDetail) AddDetail(ex, message, server, null); if (includeDetail) AddDetail(ex, message, null, null);
return ex; return ex;
} }
internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint server) internal static Exception NoConnectionAvailable(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
var ex = new RedisConnectionException(failureType, message); string s = GetLabel(includeDetail, command, message);
if (includeDetail) AddDetail(ex, null, server, null); var ex = new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, "No connection is available to service this operation: " + s);
if (includeDetail) AddDetail(ex, message, server, s);
return ex; return ex;
} }
...@@ -102,5 +82,28 @@ internal static Exception NotSupported(bool includeDetail, RedisCommand command) ...@@ -102,5 +82,28 @@ internal static Exception NotSupported(bool includeDetail, RedisCommand command)
if (includeDetail) AddDetail(ex, null, null, s); if (includeDetail) AddDetail(ex, null, null, s);
return ex; return ex;
} }
internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server)
{
var ex = new TimeoutException(errorMessage);
if (includeDetail) AddDetail(ex, message, server, null);
return ex;
}
private static void AddDetail(Exception exception, Message message, ServerEndPoint server, string label)
{
if (exception != null)
{
if (message != null) exception.Data.Add(DataCommandKey, message.CommandAndKey);
else if (label != null) exception.Data.Add(DataCommandKey, label);
if (server != null) exception.Data.Add(DataServerKey, Format.ToString(server.EndPoint));
}
}
static string GetLabel(bool includeDetail, RedisCommand command, Message message)
{
return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.Command.ToString());
}
} }
} }
...@@ -6,14 +6,16 @@ namespace StackExchange.Redis ...@@ -6,14 +6,16 @@ namespace StackExchange.Redis
{ {
internal static class Format internal static class Format
{ {
public static bool TryParseInt32(string s, out int value)
{
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value);
}
public static int ParseInt32(string s) public static int ParseInt32(string s)
{ {
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo); return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
} }
public static string ToString(int value)
{
return value.ToString(NumberFormatInfo.InvariantInfo);
}
public static bool TryParseBoolean(string s, out bool value) public static bool TryParseBoolean(string s, out bool value)
{ {
if (bool.TryParse(s, out value)) return true; if (bool.TryParse(s, out value)) return true;
...@@ -32,39 +34,20 @@ public static bool TryParseBoolean(string s, out bool value) ...@@ -32,39 +34,20 @@ public static bool TryParseBoolean(string s, out bool value)
return false; return false;
} }
public static string ToString(int value) public static bool TryParseInt32(string s, out int value)
{ {
return value.ToString(NumberFormatInfo.InvariantInfo); return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out value);
} }
internal static string ToString(long value) internal static EndPoint ParseEndPoint(string host, int port)
{ {
return value.ToString(NumberFormatInfo.InvariantInfo); IPAddress ip;
if (IPAddress.TryParse(host, out ip)) return new IPEndPoint(ip, port);
return new DnsEndPoint(host, port);
} }
internal static bool TryParseDouble(string s, out double value) internal static string ToString(long value)
{
if(s == null || s.Length == 0)
{
value = 0;
return false;
}
if(s.Length==1 && s[0] >= '0' && s[1] <= '9')
{
value = (int)(s[0] - '0');
return true;
}
// need to handle these
if(string.Equals("+inf", s, StringComparison.OrdinalIgnoreCase))
{
value = double.PositiveInfinity;
return true;
}
if(string.Equals("-inf", s, StringComparison.OrdinalIgnoreCase))
{ {
value = double.NegativeInfinity; return value.ToString(NumberFormatInfo.InvariantInfo);
return true;
}
return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
} }
internal static string ToString(double value) internal static string ToString(double value)
...@@ -76,6 +59,7 @@ internal static string ToString(double value) ...@@ -76,6 +59,7 @@ internal static string ToString(double value)
} }
return value.ToString("G17", NumberFormatInfo.InvariantInfo); return value.ToString("G17", NumberFormatInfo.InvariantInfo);
} }
internal static string ToString(object value) internal static string ToString(object value)
{ {
return Convert.ToString(value, CultureInfo.InvariantCulture); return Convert.ToString(value, CultureInfo.InvariantCulture);
...@@ -119,11 +103,31 @@ internal static bool TryGetHostPort(EndPoint endpoint, out string host, out int ...@@ -119,11 +103,31 @@ internal static bool TryGetHostPort(EndPoint endpoint, out string host, out int
port = 0; port = 0;
return false; return false;
} }
internal static EndPoint ParseEndPoint(string host, int port)
internal static bool TryParseDouble(string s, out double value)
{ {
IPAddress ip; if(s == null || s.Length == 0)
if (IPAddress.TryParse(host, out ip)) return new IPEndPoint(ip, port); {
return new DnsEndPoint(host, port); value = 0;
return false;
}
if(s.Length==1 && s[0] >= '0' && s[1] <= '9')
{
value = (int)(s[0] - '0');
return true;
}
// need to handle these
if(string.Equals("+inf", s, StringComparison.OrdinalIgnoreCase))
{
value = double.PositiveInfinity;
return true;
}
if(string.Equals("-inf", s, StringComparison.OrdinalIgnoreCase))
{
value = double.NegativeInfinity;
return true;
}
return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
} }
internal static EndPoint TryParseEndPoint(string endpoint) internal static EndPoint TryParseEndPoint(string endpoint)
{ {
......
...@@ -4,7 +4,8 @@ namespace StackExchange.Redis ...@@ -4,7 +4,8 @@ namespace StackExchange.Redis
{ {
interface ICompletable interface ICompletable
{ {
bool TryComplete(bool isAsync);
void AppendStormLog(StringBuilder sb); void AppendStormLog(StringBuilder sb);
bool TryComplete(bool isAsync);
} }
} }
...@@ -125,6 +125,13 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -125,6 +125,13 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/hlen</remarks> /// <remarks>http://redis.io/commands/hlen</remarks>
long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None); long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash
/// </summary>
/// <returns>yields all elements of the hash.</returns>
/// <remarks>http://redis.io/commands/hscan</remarks>
IEnumerable<KeyValuePair<RedisValue, RedisValue>> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. /// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created.
/// </summary> /// </summary>
...@@ -214,6 +221,13 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -214,6 +221,13 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/persist</remarks> /// <remarks>http://redis.io/commands/persist</remarks>
bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None); bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return a random key from the currently selected database.
/// </summary>
/// <returns>the random key, or nil when the database is empty.</returns>
/// <remarks>http://redis.io/commands/randomkey</remarks>
RedisKey KeyRandom(CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist. /// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist.
/// </summary> /// </summary>
...@@ -376,14 +390,6 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -376,14 +390,6 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// Takes a lock (specifying a token value) if it is not already taken /// Takes a lock (specifying a token value) if it is not already taken
/// </summary> /// </summary>
bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return a random key from the currently selected database.
/// </summary>
/// <returns>the random key, or nil when the database is empty.</returns>
/// <remarks>http://redis.io/commands/randomkey</remarks>
RedisKey KeyRandom(CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Execute a Lua script against the server /// Execute a Lua script against the server
/// </summary> /// </summary>
...@@ -511,21 +517,7 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -511,21 +517,7 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// </summary> /// </summary>
/// <returns>yields all elements of the set.</returns> /// <returns>yields all elements of the set.</returns>
/// <remarks>http://redis.io/commands/sscan</remarks> /// <remarks>http://redis.io/commands/sscan</remarks>
IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None); IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The ZSCAN command is used to incrementally iterate over a sorted set
/// </summary>
/// <returns>yields all elements of the sorted set.</returns>
/// <remarks>http://redis.io/commands/zscan</remarks>
IEnumerable<KeyValuePair<RedisValue, double>> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash
/// </summary>
/// <returns>yields all elements of the hash.</returns>
/// <remarks>http://redis.io/commands/hscan</remarks>
IEnumerable<KeyValuePair<RedisValue, RedisValue>> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be /// Sorts a list, set or sorted set (numerically or alphabetically, ascending by default); By default, the elements themselves are compared, but the values can also be
...@@ -603,6 +595,7 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -603,6 +595,7 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <returns>the cardinality (number of elements) of the sorted set, or 0 if key does not exist.</returns> /// <returns>the cardinality (number of elements) of the sorted set, or 0 if key does not exist.</returns>
/// <remarks>http://redis.io/commands/zcard</remarks> /// <remarks>http://redis.io/commands/zcard</remarks>
long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
...@@ -612,7 +605,6 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -612,7 +605,6 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/zrevrange</remarks> /// <remarks>http://redis.io/commands/zrevrange</remarks>
RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None); RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score. /// Returns the specified range of elements in the sorted set stored at key. By default the elements are considered to be ordered from the lowest to the highest score. Lexicographical order is used for elements with equal score.
/// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on. /// Both start and stop are zero-based indexes, where 0 is the first element, 1 is the next element and so on. They can also be negative numbers indicating offsets from the end of the sorted set, with -1 being the last element of the sorted set, -2 the penultimate element and so on.
...@@ -682,6 +674,12 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -682,6 +674,12 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/zremrangebyscore</remarks> /// <remarks>http://redis.io/commands/zremrangebyscore</remarks>
long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None); long SortedSetRemoveRangeByScore(RedisKey key, double start, double stop, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The ZSCAN command is used to incrementally iterate over a sorted set
/// </summary>
/// <returns>yields all elements of the sorted set.</returns>
/// <remarks>http://redis.io/commands/zscan</remarks>
IEnumerable<KeyValuePair<RedisValue, double>> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Returns the score of member in the sorted set at key; If member does not exist in the sorted set, or key does not exist, nil is returned. /// Returns the score of member in the sorted set at key; If member does not exist in the sorted set, or key does not exist, nil is returned.
/// </summary> /// </summary>
......
...@@ -200,6 +200,13 @@ public interface IDatabaseAsync : IRedisAsync ...@@ -200,6 +200,13 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/persist</remarks> /// <remarks>http://redis.io/commands/persist</remarks>
Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None); Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return a random key from the currently selected database.
/// </summary>
/// <returns>the random key, or nil when the database is empty.</returns>
/// <remarks>http://redis.io/commands/randomkey</remarks>
Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist. /// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist.
/// </summary> /// </summary>
...@@ -362,17 +369,6 @@ public interface IDatabaseAsync : IRedisAsync ...@@ -362,17 +369,6 @@ public interface IDatabaseAsync : IRedisAsync
/// Takes a lock (specifying a token value) if it is not already taken /// Takes a lock (specifying a token value) if it is not already taken
/// </summary> /// </summary>
Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None); Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return a random key from the currently selected database.
/// </summary>
/// <returns>the random key, or nil when the database is empty.</returns>
/// <remarks>http://redis.io/commands/randomkey</remarks>
Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Execute a Lua script against the server /// Execute a Lua script against the server
/// </summary> /// </summary>
......
...@@ -6,7 +6,7 @@ namespace StackExchange.Redis ...@@ -6,7 +6,7 @@ namespace StackExchange.Redis
/// <summary> /// <summary>
/// Common operations available to all redis connections /// Common operations available to all redis connections
/// </summary> /// </summary>
public interface IRedis : IRedisAsync public partial interface IRedis : IRedisAsync
{ {
/// <summary> /// <summary>
/// This command is often used to test if a connection is still alive, or to measure latency. /// This command is often used to test if a connection is still alive, or to measure latency.
......
...@@ -6,14 +6,24 @@ namespace StackExchange.Redis ...@@ -6,14 +6,24 @@ namespace StackExchange.Redis
/// <summary> /// <summary>
/// Common operations available to all redis connections /// Common operations available to all redis connections
/// </summary> /// </summary>
public interface IRedisAsync public partial interface IRedisAsync
{ {
/// <summary>
/// Gets the multiplexer that created this instance
/// </summary>
ConnectionMultiplexer Multiplexer { get; }
/// <summary> /// <summary>
/// This command is often used to test if a connection is still alive, or to measure latency. /// This command is often used to test if a connection is still alive, or to measure latency.
/// </summary> /// </summary>
/// <returns>The observed latency.</returns> /// <returns>The observed latency.</returns>
/// <remarks>http://redis.io/commands/ping</remarks> /// <remarks>http://redis.io/commands/ping</remarks>
Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None); Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None);
/// <summary>
/// Wait for a given asynchronous operation to complete (or timeout), reporting which
/// </summary>
bool TryWait(Task task);
/// <summary> /// <summary>
/// Wait for a given asynchronous operation to complete (or timeout) /// Wait for a given asynchronous operation to complete (or timeout)
/// </summary> /// </summary>
...@@ -27,15 +37,5 @@ public interface IRedisAsync ...@@ -27,15 +37,5 @@ public interface IRedisAsync
/// </summary> /// </summary>
void WaitAll(params Task[] tasks); void WaitAll(params Task[] tasks);
/// <summary>
/// Gets the multiplexer that created this instance
/// </summary>
ConnectionMultiplexer Multiplexer { get; }
/// <summary>
/// Wait for a given asynchronous operation to complete (or timeout), reporting which
/// </summary>
bool TryWait(Task task);
} }
} }
...@@ -10,7 +10,7 @@ namespace StackExchange.Redis ...@@ -10,7 +10,7 @@ namespace StackExchange.Redis
/// <summary> /// <summary>
/// Provides configuration controls of a redis server /// Provides configuration controls of a redis server
/// </summary> /// </summary>
public interface IServer : IRedis public partial interface IServer : IRedis
{ {
/// <summary> /// <summary>
/// Gets the cluster configuration associated with this server, if known /// Gets the cluster configuration associated with this server, if known
......
...@@ -10,6 +10,18 @@ namespace StackExchange.Redis ...@@ -10,6 +10,18 @@ namespace StackExchange.Redis
public interface ISubscriber : IRedis public interface ISubscriber : IRedis
{ {
/// <summary>
/// Inidicate exactly which redis server we are talking to
/// </summary>
[IgnoreNamePrefix]
EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Inidicate exactly which redis server we are talking to
/// </summary>
[IgnoreNamePrefix]
Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Indicates whether the instance can communicate with the server; /// Indicates whether the instance can communicate with the server;
/// if a channel is specified, the existing subscription map is queried to /// if a channel is specified, the existing subscription map is queried to
...@@ -35,22 +47,21 @@ public interface ISubscriber : IRedis ...@@ -35,22 +47,21 @@ public interface ISubscriber : IRedis
/// </summary> /// </summary>
/// <remarks>http://redis.io/commands/subscribe</remarks> /// <remarks>http://redis.io/commands/subscribe</remarks>
/// <remarks>http://redis.io/commands/psubscribe</remarks> /// <remarks>http://redis.io/commands/psubscribe</remarks>
Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None); void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless
/// of the subscribers; if a handler is specified, the subscription is only cancelled if this handler is the
/// last handler remaining against the channel
/// </summary>
/// <remarks>http://redis.io/commands/unsubscribe</remarks>
/// <remarks>http://redis.io/commands/punsubscribe</remarks>
Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Subscribe to perform some operation when a change to the preferred/active node is broadcast. /// Subscribe to perform some operation when a change to the preferred/active node is broadcast.
/// </summary> /// </summary>
/// <remarks>http://redis.io/commands/subscribe</remarks> /// <remarks>http://redis.io/commands/subscribe</remarks>
/// <remarks>http://redis.io/commands/psubscribe</remarks> /// <remarks>http://redis.io/commands/psubscribe</remarks>
void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None); Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Inidicate to which redis server we are actively subscribed for a given channel; returns null if
/// the channel is not actively subscribed
/// </summary>
[IgnoreNamePrefix]
EndPoint SubscribedEndpoint(RedisChannel channel);
/// <summary> /// <summary>
/// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless /// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless
...@@ -76,23 +87,12 @@ public interface ISubscriber : IRedis ...@@ -76,23 +87,12 @@ public interface ISubscriber : IRedis
Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None); Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None);
/// <summary> /// <summary>
/// Inidicate exactly which redis server we are talking to /// Unsubscribe from a specified message channel; note; if no handler is specified, the subscription is cancelled regardless
/// </summary> /// of the subscribers; if a handler is specified, the subscription is only cancelled if this handler is the
[IgnoreNamePrefix] /// last handler remaining against the channel
EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Inidicate exactly which redis server we are talking to
/// </summary>
[IgnoreNamePrefix]
Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Inidicate to which redis server we are actively subscribed for a given channel; returns null if
/// the channel is not actively subscribed
/// </summary> /// </summary>
[IgnoreNamePrefix] /// <remarks>http://redis.io/commands/unsubscribe</remarks>
EndPoint SubscribedEndpoint(RedisChannel channel); /// <remarks>http://redis.io/commands/punsubscribe</remarks>
Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None);
} }
} }
\ No newline at end of file
...@@ -9,13 +9,12 @@ namespace StackExchange.Redis ...@@ -9,13 +9,12 @@ namespace StackExchange.Redis
/// </summary> /// </summary>
public class InternalErrorEventArgs : EventArgs, ICompletable public class InternalErrorEventArgs : EventArgs, ICompletable
{ {
private readonly object sender;
private readonly ConnectionType connectionType; private readonly ConnectionType connectionType;
private readonly EndPoint endpoint; private readonly EndPoint endpoint;
private readonly Exception exception; private readonly Exception exception;
private readonly EventHandler<InternalErrorEventArgs> handler; private readonly EventHandler<InternalErrorEventArgs> handler;
private readonly string origin; private readonly string origin;
private readonly object sender;
internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, object sender, EndPoint endpoint, ConnectionType connectionType, Exception exception, string origin) internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, object sender, EndPoint endpoint, ConnectionType connectionType, Exception exception, string origin)
{ {
this.handler = handler; this.handler = handler;
...@@ -26,21 +25,20 @@ internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, ob ...@@ -26,21 +25,20 @@ internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, ob
this.origin = origin; this.origin = origin;
} }
/// <summary> /// <summary>
/// Gets the failing server-endpoint (this can be null) /// Gets the connection-type of the failing connection
/// </summary> /// </summary>
public EndPoint EndPoint public ConnectionType ConnectionType
{ {
get { return endpoint; } get { return connectionType; }
} }
/// <summary> /// <summary>
/// Gets the connection-type of the failing connection /// Gets the failing server-endpoint (this can be null)
/// </summary> /// </summary>
public ConnectionType ConnectionType public EndPoint EndPoint
{ {
get { return connectionType; } get { return endpoint; }
} }
/// <summary> /// <summary>
/// Gets the exception if available (this can be null) /// Gets the exception if available (this can be null)
/// </summary> /// </summary>
...@@ -56,15 +54,15 @@ public string Origin ...@@ -56,15 +54,15 @@ public string Origin
{ {
get { return origin; } get { return origin; }
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb) void ICompletable.AppendStormLog(StringBuilder sb)
{ {
sb.Append("event, internal-error: ").Append(origin); sb.Append("event, internal-error: ").Append(origin);
if (endpoint != null) sb.Append(", ").Append(Format.ToString(endpoint)); if (endpoint != null) sb.Append(", ").Append(Format.ToString(endpoint));
} }
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
} }
} }
\ No newline at end of file
using System; namespace StackExchange.Redis
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace StackExchange.Redis
{ {
#if LOGOUTPUT #if LOGOUTPUT
sealed class LoggingTextStream : Stream sealed class LoggingTextStream : Stream
...@@ -144,4 +138,4 @@ public override void Write(byte[] buffer, int offset, int count) ...@@ -144,4 +138,4 @@ public override void Write(byte[] buffer, int offset, int count)
} }
} }
#endif #endif
} }
...@@ -63,17 +63,11 @@ abstract class Message : ICompletable ...@@ -63,17 +63,11 @@ abstract class Message : ICompletable
public static readonly Message[] EmptyArray = new Message[0]; public static readonly Message[] EmptyArray = new Message[0];
public readonly int Db; public readonly int Db;
internal const CommandFlags InternalCallFlag = (CommandFlags)128;
protected RedisCommand command; protected RedisCommand command;
private const CommandFlags AskingFlag = (CommandFlags)32; private const CommandFlags AskingFlag = (CommandFlags)32;
internal const CommandFlags InternalCallFlag = (CommandFlags)128; const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave;
public virtual void AppendStormLog(StringBuilder sb)
{
if (Db >= 0) sb.Append(Db).Append(':');
sb.Append(CommandAndKey);
}
private const CommandFlags UserSelectableFlags private const CommandFlags UserSelectableFlags
= CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave = CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave
...@@ -127,6 +121,8 @@ protected Message(int db, CommandFlags flags, RedisCommand command) ...@@ -127,6 +121,8 @@ protected Message(int db, CommandFlags flags, RedisCommand command)
public RedisCommand Command { get { return command; } } public RedisCommand Command { get { return command; } }
public virtual string CommandAndKey { get { return Command.ToString(); } }
public CommandFlags Flags { get { return flags; } } public CommandFlags Flags { get { return flags; } }
/// <summary> /// <summary>
...@@ -183,12 +179,6 @@ public bool IsInternalCall ...@@ -183,12 +179,6 @@ public bool IsInternalCall
public ResultBox ResultBox { get { return resultBox; } } public ResultBox ResultBox { get { return resultBox; } }
public virtual string CommandAndKey { get { return Command.ToString(); } }
public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values)
{
return new CommandSlotValuesMessage(db, slot, flags, command, values);
}
public static Message Create(int db, CommandFlags flags, RedisCommand command) public static Message Create(int db, CommandFlags flags, RedisCommand command)
{ {
if (command == RedisCommand.SELECT) if (command == RedisCommand.SELECT)
...@@ -205,10 +195,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R ...@@ -205,10 +195,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
{ {
return new CommandKeyKeyMessage(db, flags, command, key0, key1); return new CommandKeyKeyMessage(db, flags, command, key0, key1);
} }
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value) public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value)
{ {
return new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value); return new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value);
} }
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisKey key2) public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisKey key2)
{ {
return new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2); return new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2);
...@@ -228,10 +220,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R ...@@ -228,10 +220,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
{ {
return new CommandChannelMessage(db, flags, command, channel); return new CommandChannelMessage(db, flags, command, channel);
} }
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value) public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value)
{ {
return new CommandChannelValueMessage(db, flags, command, channel, value); return new CommandChannelValueMessage(db, flags, command, channel, value);
} }
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel) public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel)
{ {
return new CommandValueChannelMessage(db, flags, command, value, channel); return new CommandValueChannelMessage(db, flags, command, value, channel);
...@@ -272,6 +266,11 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R ...@@ -272,6 +266,11 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
return new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4); return new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4);
} }
public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values)
{
return new CommandSlotValuesMessage(db, slot, flags, command, values);
}
public static bool IsMasterOnly(RedisCommand command) public static bool IsMasterOnly(RedisCommand command)
{ {
switch (command) switch (command)
...@@ -345,6 +344,11 @@ public static bool IsMasterOnly(RedisCommand command) ...@@ -345,6 +344,11 @@ public static bool IsMasterOnly(RedisCommand command)
} }
} }
public virtual void AppendStormLog(StringBuilder sb)
{
if (Db >= 0) sb.Append(Db).Append(':');
sb.Append(CommandAndKey);
}
public virtual int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) { return ServerSelectionStrategy.NoSlot; } public virtual int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) { return ServerSelectionStrategy.NoSlot; }
public bool IsMasterOnly() public bool IsMasterOnly()
{ {
...@@ -435,19 +439,6 @@ internal static CommandFlags GetMasterSlaveFlags(CommandFlags flags) ...@@ -435,19 +439,6 @@ internal static CommandFlags GetMasterSlaveFlags(CommandFlags flags)
// for the purposes of the switch, we only care about two bits // for the purposes of the switch, we only care about two bits
return flags & MaskMasterServerPreference; return flags & MaskMasterServerPreference;
} }
const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave;
internal void SetPreferMaster()
{
flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferMaster;
}
internal void SetPreferSlave()
{
flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferSlave;
}
internal static bool RequiresDatabase(RedisCommand command) internal static bool RequiresDatabase(RedisCommand command)
{ {
switch (command) switch (command)
...@@ -489,6 +480,13 @@ internal static bool RequiresDatabase(RedisCommand command) ...@@ -489,6 +480,13 @@ internal static bool RequiresDatabase(RedisCommand command)
} }
} }
internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
{
// take away the two flags we don't want, and add back the ones we care about
return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave))
| masterSlave;
}
internal void Cancel() internal void Cancel()
{ {
if (resultProcessor != null) resultProcessor.SetException(this, new TaskCanceledException()); if (resultProcessor != null) resultProcessor.SetException(this, new TaskCanceledException());
...@@ -520,6 +518,15 @@ internal void SetNoRedirect() ...@@ -520,6 +518,15 @@ internal void SetNoRedirect()
this.flags |= CommandFlags.NoRedirect; this.flags |= CommandFlags.NoRedirect;
} }
internal void SetPreferMaster()
{
flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferMaster;
}
internal void SetPreferSlave()
{
flags = (flags & ~MaskMasterServerPreference) | CommandFlags.PreferSlave;
}
internal void SetSource(ResultProcessor resultProcessor, ResultBox resultBox) internal void SetSource(ResultProcessor resultProcessor, ResultBox resultBox)
{ // note order here reversed to prevent overload resolution errors { // note order here reversed to prevent overload resolution errors
this.resultBox = resultBox; this.resultBox = resultBox;
...@@ -550,19 +557,20 @@ internal void WriteTo(PhysicalConnection physical) ...@@ -550,19 +557,20 @@ internal void WriteTo(PhysicalConnection physical)
Fail(ConnectionFailureType.InternalFailure, ex); Fail(ConnectionFailureType.InternalFailure, ex);
} }
} }
internal abstract class CommandChannelBase : Message
{
protected readonly RedisChannel Channel;
public CommandChannelBase(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command)
internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
{ {
// take away the two flags we don't want, and add back the ones we care about this.Channel = channel.Assert();
return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave)) }
| masterSlave;
public override string CommandAndKey { get { return Command + " " + Channel; } }
} }
internal abstract class CommandKeyBase : Message internal abstract class CommandKeyBase : Message
{ {
public override string CommandAndKey { get { return Command + " " + Key; } }
protected readonly RedisKey Key; protected readonly RedisKey Key;
public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command) public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command)
...@@ -570,60 +578,38 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey ...@@ -570,60 +578,38 @@ public CommandKeyBase(int db, CommandFlags flags, RedisCommand command, RedisKey
this.Key = key.Assert(); this.Key = key.Assert();
} }
public override string CommandAndKey { get { return Command + " " + Key; } }
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{ {
return serverSelectionStrategy.HashSlot(Key); return serverSelectionStrategy.HashSlot(Key);
} }
} }
internal abstract class CommandChannelBase : Message sealed class CommandChannelMessage : CommandChannelBase
{
public override string CommandAndKey { get { return Command + " " + Channel; } }
protected readonly RedisChannel Channel;
public CommandChannelBase(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command)
{
this.Channel= channel.Assert();
}
}
class CommandKeyKeyMessage : CommandKeyBase
{
protected readonly RedisKey key1;
public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1) : base(db, flags, command, key0)
{
this.key1 = key1.Assert();
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{ {
var slot = serverSelectionStrategy.HashSlot(Key); public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command, channel)
slot = serverSelectionStrategy.CombineSlot(slot, key1); { }
return slot;
}
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, 2); physical.WriteHeader(Command, 1);
physical.Write(Key); physical.Write(Channel);
physical.Write(key1);
} }
} }
sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage
sealed class CommandChannelValueMessage : CommandChannelBase
{ {
private readonly RedisValue value; private readonly RedisValue value;
public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value) : base(db, flags, command, key0, key1) public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value) : base(db, flags, command, channel)
{ {
this.value = value.Assert(); this.value = value.Assert();
} }
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, 3); physical.WriteHeader(Command, 2);
physical.Write(Key); physical.Write(Channel);
physical.Write(key1);
physical.Write(value); physical.Write(value);
} }
} }
sealed class CommandKeyKeyKeyMessage : CommandKeyBase sealed class CommandKeyKeyKeyMessage : CommandKeyBase
{ {
private readonly RedisKey key1, key2; private readonly RedisKey key1, key2;
...@@ -649,17 +635,27 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -649,17 +635,27 @@ internal override void WriteImpl(PhysicalConnection physical)
} }
} }
sealed class CommandKeyMessage : CommandKeyBase class CommandKeyKeyMessage : CommandKeyBase
{ {
public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command, key) protected readonly RedisKey key1;
{ } public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1) : base(db, flags, command, key0)
{
this.key1 = key1.Assert();
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
var slot = serverSelectionStrategy.HashSlot(Key);
slot = serverSelectionStrategy.CombineSlot(slot, key1);
return slot;
}
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, 1); physical.WriteHeader(Command, 2);
physical.Write(Key); physical.Write(Key);
physical.Write(key1);
} }
} }
sealed class CommandKeyKeysMessage : CommandKeyBase sealed class CommandKeyKeysMessage : CommandKeyBase
{ {
private readonly RedisKey[] keys; private readonly RedisKey[] keys;
...@@ -690,6 +686,33 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -690,6 +686,33 @@ internal override void WriteImpl(PhysicalConnection physical)
} }
} }
} }
sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage
{
private readonly RedisValue value;
public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisKey key1, RedisValue value) : base(db, flags, command, key0, key1)
{
this.value = value.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 3);
physical.Write(Key);
physical.Write(key1);
physical.Write(value);
}
}
sealed class CommandKeyMessage : CommandKeyBase
{
public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key) : base(db, flags, command, key)
{ }
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 1);
physical.Write(Key);
}
}
sealed class CommandKeysMessage : Message sealed class CommandKeysMessage : Message
{ {
private readonly RedisKey[] keys; private readonly RedisKey[] keys;
...@@ -736,88 +759,50 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -736,88 +759,50 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(value); physical.Write(value);
} }
} }
sealed class CommandChannelMessage : CommandChannelBase sealed class CommandKeyValuesKeyMessage : CommandKeyBase
{
public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command, channel)
{}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 1);
physical.Write(Channel);
}
}
sealed class CommandChannelValueMessage : CommandChannelBase
{
private readonly RedisValue value;
public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value) : base(db, flags, command, channel)
{
this.value = value.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(Channel);
physical.Write(value);
}
}
sealed class CommandValueChannelMessage : CommandChannelBase
{
private readonly RedisValue value;
public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel) : base(db, flags, command, channel)
{
this.value = value.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(value);
physical.Write(Channel);
}
}
sealed class CommandKeyValuesMessage : CommandKeyBase
{ {
private readonly RedisKey key1;
private readonly RedisValue[] values; private readonly RedisValue[] values;
public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue[] values) : base(db, flags, command, key) public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) : base(db, flags, command, key0)
{ {
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
values[i].Assert(); values[i].Assert();
} }
this.values = values; this.values = values;
this.key1 = key1.Assert();
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
var slot = base.GetHashSlot(serverSelectionStrategy);
return serverSelectionStrategy.CombineSlot(slot, key1);
} }
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, values.Length + 1); physical.WriteHeader(Command, values.Length + 2);
physical.Write(Key); physical.Write(Key);
for (int i = 0; i < values.Length; i++) physical.Write(values[i]); for (int i = 0; i < values.Length; i++) physical.Write(values[i]);
physical.Write(key1);
} }
} }
sealed class CommandKeyValuesKeyMessage : CommandKeyBase sealed class CommandKeyValuesMessage : CommandKeyBase
{ {
private readonly RedisValue[] values; private readonly RedisValue[] values;
private readonly RedisKey key1; public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue[] values) : base(db, flags, command, key)
public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) : base(db, flags, command, key0)
{ {
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
values[i].Assert(); values[i].Assert();
} }
this.values = values; this.values = values;
this.key1 = key1.Assert();
} }
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, values.Length + 2); physical.WriteHeader(Command, values.Length + 1);
physical.Write(Key); physical.Write(Key);
for (int i = 0; i < values.Length; i++) physical.Write(values[i]); for (int i = 0; i < values.Length; i++) physical.Write(values[i]);
physical.Write(key1);
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
var slot = base.GetHashSlot(serverSelectionStrategy);
return serverSelectionStrategy.CombineSlot(slot, key1);
} }
} }
...@@ -837,6 +822,7 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -837,6 +822,7 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(value1); physical.Write(value1);
} }
} }
sealed class CommandKeyValueValueValueMessage : CommandKeyBase sealed class CommandKeyValueValueValueMessage : CommandKeyBase
{ {
private readonly RedisValue value0, value1, value2; private readonly RedisValue value0, value1, value2;
...@@ -855,6 +841,7 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -855,6 +841,7 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(value2); physical.Write(value2);
} }
} }
sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase
{ {
private readonly RedisValue value0, value1, value2, value3; private readonly RedisValue value0, value1, value2, value3;
...@@ -878,26 +865,70 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -878,26 +865,70 @@ internal override void WriteImpl(PhysicalConnection physical)
sealed class CommandMessage : Message sealed class CommandMessage : Message
{ {
public CommandMessage(int db, CommandFlags flags, RedisCommand command) : base(db, flags, command) {} public CommandMessage(int db, CommandFlags flags, RedisCommand command) : base(db, flags, command) { }
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, 0); physical.WriteHeader(Command, 0);
} }
} }
sealed class CommandValueKeyMessage : CommandKeyBase
private class CommandSlotValuesMessage : Message
{ {
private readonly RedisValue value; private readonly int slot;
private readonly RedisValue[] values;
public override void AppendStormLog(StringBuilder sb) public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values)
: base(db, flags, command)
{ {
base.AppendStormLog(sb); this.slot = slot;
sb.Append(" (").Append((string)value).Append(')'); for (int i = 0; i < values.Length; i++)
{
values[i].Assert();
}
this.values = values;
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return slot;
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(command, values.Length);
for (int i = 0; i < values.Length; i++)
{
physical.Write(values[i]);
}
}
} }
sealed class CommandValueChannelMessage : CommandChannelBase
{
private readonly RedisValue value;
public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel) : base(db, flags, command, channel)
{
this.value = value.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(value);
physical.Write(Channel);
}
}
sealed class CommandValueKeyMessage : CommandKeyBase
{
private readonly RedisValue value;
public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisKey key) : base(db, flags, command, key) public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisKey key) : base(db, flags, command, key)
{ {
this.value = value.Assert(); this.value = value.Assert();
} }
public override void AppendStormLog(StringBuilder sb)
{
base.AppendStormLog(sb);
sb.Append(" (").Append((string)value).Append(')');
}
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
physical.WriteHeader(Command, 2); physical.WriteHeader(Command, 2);
...@@ -987,34 +1018,5 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -987,34 +1018,5 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(Db); physical.Write(Db);
} }
} }
private class CommandSlotValuesMessage : Message
{
private readonly int slot;
private readonly RedisValue[] values;
public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values)
: base(db, flags, command)
{
this.slot = slot;
for(int i = 0; i < values.Length;i++)
{
values[i].Assert();
}
this.values = values;
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return slot;
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(command, values.Length);
for(int i = 0; i < values.Length;i++)
{
physical.Write(values[i]);
}
}
}
} }
} }
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -10,6 +9,8 @@ sealed partial class MessageQueue ...@@ -10,6 +9,8 @@ sealed partial class MessageQueue
regular = new Queue<Message>(), regular = new Queue<Message>(),
high = new Queue<Message>(); high = new Queue<Message>();
public object SyncLock { get { return regular; } }
public Message Dequeue() public Message Dequeue()
{ {
lock (regular) lock (regular)
...@@ -26,23 +27,6 @@ public Message Dequeue() ...@@ -26,23 +27,6 @@ public Message Dequeue()
return null; return null;
} }
internal Message[] DequeueAll()
{
lock (regular)
{
int count = high.Count + regular.Count;
if (count == 0) return Message.EmptyArray;
var arr = new Message[count];
high.CopyTo(arr, 0);
regular.CopyTo(arr, high.Count);
high.Clear();
regular.Clear();
return arr;
}
}
public object SyncLock { get { return regular; } }
public Message PeekPing(out int queueLength) public Message PeekPing(out int queueLength)
{ {
lock (regular) lock (regular)
...@@ -70,6 +54,14 @@ public bool Push(Message message) ...@@ -70,6 +54,14 @@ public bool Push(Message message)
} }
} }
internal bool Any()
{
lock (regular)
{
return high.Count != 0 || regular.Count != 0;
}
}
internal int Count() internal int Count()
{ {
lock (regular) lock (regular)
...@@ -78,14 +70,21 @@ internal int Count() ...@@ -78,14 +70,21 @@ internal int Count()
} }
} }
internal bool Any() internal Message[] DequeueAll()
{ {
lock(regular) lock (regular)
{ {
return high.Count != 0 || regular.Count != 0; int count = high.Count + regular.Count;
if (count == 0) return Message.EmptyArray;
var arr = new Message[count];
high.CopyTo(arr, 0);
regular.CopyTo(arr, high.Count);
high.Clear();
regular.Clear();
return arr;
} }
} }
internal void GetStormLog(StringBuilder sb) internal void GetStormLog(StringBuilder sb)
{ {
lock(regular) lock(regular)
......
...@@ -2,34 +2,52 @@ ...@@ -2,34 +2,52 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
enum WriteResult
{
QueueEmpty,
MoreWork,
CompetingWriter,
NoConnection,
}
sealed partial class PhysicalBridge : IDisposable sealed partial class PhysicalBridge : IDisposable
{ {
internal readonly string Name;
internal int inWriteQueue = 0;
const int ProfileLogSamples = 10;
const double ProfileLogSeconds = (ConnectionMultiplexer.MillisecondsPerHeartbeat * ProfileLogSamples) / 1000.0;
private static readonly Message private static readonly Message
ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING); ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING);
private readonly CompletionManager completionManager; private readonly CompletionManager completionManager;
private readonly ConnectionType connectionType; private readonly ConnectionType connectionType;
private readonly ConnectionMultiplexer multiplexer; private readonly ConnectionMultiplexer multiplexer;
internal readonly string Name; readonly long[] profileLog = new long[ProfileLogSamples];
private readonly MessageQueue queue = new MessageQueue(); private readonly MessageQueue queue = new MessageQueue();
private readonly ServerEndPoint serverEndPoint; private readonly ServerEndPoint serverEndPoint;
int activeWriters = 0; int activeWriters = 0;
internal int inWriteQueue = 0;
private int beating; private int beating;
int failConnectCount = 0; int failConnectCount = 0;
volatile bool isDisposed; volatile bool isDisposed;
long nonPreferredEndpointCount;
//private volatile int missedHeartbeats; //private volatile int missedHeartbeats;
private long operationCount, socketCount; private long operationCount, socketCount;
private volatile PhysicalConnection physical; private volatile PhysicalConnection physical;
long profileLastLog;
int profileLogIndex;
volatile bool reportNextFailure = true, reconfigureNextFailure = false; volatile bool reportNextFailure = true, reconfigureNextFailure = false;
private volatile int state = (int)State.Disconnected; private volatile int state = (int)State.Disconnected;
public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type) public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type)
...@@ -63,12 +81,6 @@ public bool IsConnected ...@@ -63,12 +81,6 @@ public bool IsConnected
public ServerEndPoint ServerEndPoint { get { return serverEndPoint; } } public ServerEndPoint ServerEndPoint { get { return serverEndPoint; } }
internal State ConnectionState { get { return (State)state; } }
internal long OperationCount
{
get { return Interlocked.Read(ref operationCount); }
}
public long SubscriptionCount public long SubscriptionCount
{ {
get get
...@@ -78,6 +90,13 @@ public long SubscriptionCount ...@@ -78,6 +90,13 @@ public long SubscriptionCount
} }
} }
internal State ConnectionState { get { return (State)state; } }
internal bool IsBeating { get { return Interlocked.CompareExchange(ref beating, 0, 0) == 1; } }
internal long OperationCount
{
get { return Interlocked.Read(ref operationCount); }
}
public void CompleteSyncOrAsync(ICompletable operation) public void CompleteSyncOrAsync(ICompletable operation)
{ {
completionManager.CompleteSyncOrAsync(operation); completionManager.CompleteSyncOrAsync(operation);
...@@ -136,23 +155,43 @@ public bool TryEnqueue(Message message, bool isSlave) ...@@ -136,23 +155,43 @@ public bool TryEnqueue(Message message, bool isSlave)
} }
return true; return true;
} }
private void LogNonPreferred(CommandFlags flags, bool isSlave) internal void AppendProfile(StringBuilder sb)
{ {
if ((flags & Message.InternalCallFlag) == 0) // don't log internal-call long[] clone = new long[ProfileLogSamples + 1];
for (int i = 0; i < ProfileLogSamples; i++)
{ {
if (isSlave) clone[i] = Interlocked.Read(ref profileLog[i]);
}
clone[ProfileLogSamples] = Interlocked.Read(ref operationCount);
Array.Sort(clone);
sb.Append(" ").Append(clone[0]);
for (int i = 1; i < clone.Length; i++)
{ {
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferMaster) if (clone[i] != clone[i - 1])
Interlocked.Increment(ref nonPreferredEndpointCount); {
sb.Append("+").Append(clone[i] - clone[i - 1]);
} }
else }
if (clone[0] != clone[ProfileLogSamples])
{ {
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferSlave) sb.Append("=").Append(clone[ProfileLogSamples]);
Interlocked.Increment(ref nonPreferredEndpointCount); }
double rate = (clone[ProfileLogSamples] - clone[0]) / ProfileLogSeconds;
sb.Append(" (").Append(rate.ToString("N2")).Append(" ops/s; spans ").Append(ProfileLogSeconds).Append("s)");
} }
internal bool ConfirmRemoveFromWriteQueue()
{
lock (queue.SyncLock)
{
if (queue.Count() == 0)
{
Interlocked.Exchange(ref inWriteQueue, 0);
return true;
} }
} }
long nonPreferredEndpointCount; return false;
}
internal void GetCounters(ConnectionCounters counters) internal void GetCounters(ConnectionCounters counters)
{ {
...@@ -181,6 +220,11 @@ internal int GetOutstandingCount(out int inst, out int qu, out int qs, out int q ...@@ -181,6 +220,11 @@ internal int GetOutstandingCount(out int inst, out int qu, out int qs, out int q
return qu + qs + qc; return qu + qs + qc;
} }
internal int GetPendingCount()
{
return queue.Count();
}
internal string GetStormLog() internal string GetStormLog()
{ {
var sb = new StringBuilder("Storm log for ").Append(Format.ToString(serverEndPoint.EndPoint)).Append(" / ").Append(connectionType) var sb = new StringBuilder("Storm log for ").Append(Format.ToString(serverEndPoint.EndPoint)).Append(" / ").Append(connectionType)
...@@ -195,35 +239,7 @@ internal string GetStormLog() ...@@ -195,35 +239,7 @@ internal string GetStormLog()
sb.AppendLine(); sb.AppendLine();
return sb.ToString(); return sb.ToString();
} }
internal void AppendProfile(StringBuilder sb)
{
long[] clone = new long[ProfileLogSamples + 1];
for(int i = 0; i < ProfileLogSamples; i++)
{
clone[i] = Interlocked.Read(ref profileLog[i]);
}
clone[ProfileLogSamples] = Interlocked.Read(ref operationCount);
Array.Sort(clone);
sb.Append(" ").Append(clone[0]);
for (int i = 1; i < clone.Length; i++)
{
if (clone[i] != clone[i - 1])
{
sb.Append("+").Append(clone[i] - clone[i - 1]);
}
}
if (clone[0] != clone[ProfileLogSamples])
{
sb.Append("=").Append(clone[ProfileLogSamples]);
}
double rate = (clone[ProfileLogSamples] - clone[0]) / ProfileLogSeconds;
sb.Append(" (").Append(rate.ToString("N2")).Append(" ops/s; spans ").Append(ProfileLogSeconds).Append("s)");
}
const double ProfileLogSeconds = (ConnectionMultiplexer.MillisecondsPerHeartbeat * ProfileLogSamples) / 1000.0;
const int ProfileLogSamples = 10;
readonly long[] profileLog = new long[ProfileLogSamples];
long profileLastLog;
int profileLogIndex;
internal void IncrementOpCount() internal void IncrementOpCount()
{ {
Interlocked.Increment(ref operationCount); Interlocked.Increment(ref operationCount);
...@@ -248,11 +264,11 @@ internal void KeepAlive() ...@@ -248,11 +264,11 @@ internal void KeepAlive()
} }
break; break;
} }
if(msg != null) if (msg != null)
{ {
msg.SetInternalCall(); msg.SetInternalCall();
multiplexer.Trace("Enqueue: " + msg); multiplexer.Trace("Enqueue: " + msg);
if(!TryEnqueue(msg, serverEndPoint.IsSlave)) if (!TryEnqueue(msg, serverEndPoint.IsSlave))
{ {
OnInternalError(ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, msg.Command, msg, serverEndPoint)); OnInternalError(ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, msg.Command, msg, serverEndPoint));
} }
...@@ -268,9 +284,12 @@ internal void OnConnected(PhysicalConnection connection) ...@@ -268,9 +284,12 @@ internal void OnConnected(PhysicalConnection connection)
} }
else else
{ {
try { try
{
connection.Dispose(); connection.Dispose();
} catch { } }
catch
{ }
} }
} }
...@@ -335,12 +354,7 @@ internal void OnFullyEstablished(PhysicalConnection connection) ...@@ -335,12 +354,7 @@ internal void OnFullyEstablished(PhysicalConnection connection)
try { connection.Dispose(); } catch { } try { connection.Dispose(); } catch { }
} }
} }
internal int GetPendingCount()
{
return queue.Count();
}
internal bool IsBeating { get { return Interlocked.CompareExchange(ref beating, 0, 0) == 1; } }
internal void OnHeartbeat(bool ifConnectedOnly) internal void OnHeartbeat(bool ifConnectedOnly)
{ {
bool runThisTime = false; bool runThisTime = false;
...@@ -376,7 +390,8 @@ internal void OnHeartbeat(bool ifConnectedOnly) ...@@ -376,7 +390,8 @@ internal void OnHeartbeat(bool ifConnectedOnly)
State oldState; State oldState;
OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out ignore, out oldState); OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out ignore, out oldState);
} }
} else if(!queue.Any() && tmp.GetSentAwaitingResponseCount() != 0) }
else if (!queue.Any() && tmp.GetSentAwaitingResponseCount() != 0)
{ {
// there's a chance this is a dead socket; sending data will shake that // there's a chance this is a dead socket; sending data will shake that
// up a bit, so if we have an empty unsent queue and a non-empty sent // up a bit, so if we have an empty unsent queue and a non-empty sent
...@@ -413,11 +428,6 @@ internal void OnHeartbeat(bool ifConnectedOnly) ...@@ -413,11 +428,6 @@ internal void OnHeartbeat(bool ifConnectedOnly)
} }
} }
private void OnInternalError(Exception exception, [CallerMemberName] string origin = null)
{
multiplexer.OnInternalError(exception, serverEndPoint.EndPoint, connectionType, origin);
}
internal void RemovePhysical(PhysicalConnection connection) internal void RemovePhysical(PhysicalConnection connection)
{ {
#pragma warning disable 0420 #pragma warning disable 0420
...@@ -443,12 +453,12 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave) ...@@ -443,12 +453,12 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave)
if (isDisposed) throw new ObjectDisposedException(Name); if (isDisposed) throw new ObjectDisposedException(Name);
if(!IsConnected) if (!IsConnected)
{ {
return false; return false;
} }
bool reqWrite = false; bool reqWrite = false;
foreach(var message in messages) foreach (var message in messages)
{ // deliberately not taking a single lock here; we don't care if { // deliberately not taking a single lock here; we don't care if
// other threads manage to interleave - in fact, it would be desirable // other threads manage to interleave - in fact, it would be desirable
// (to avoid a batch monopolising the connection) // (to avoid a batch monopolising the connection)
...@@ -456,26 +466,13 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave) ...@@ -456,26 +466,13 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave)
LogNonPreferred(message.Flags, isSlave); LogNonPreferred(message.Flags, isSlave);
} }
Trace("Now pending: " + GetPendingCount()); Trace("Now pending: " + GetPendingCount());
if(reqWrite) // was empty before if (reqWrite) // was empty before
{ {
multiplexer.RequestWrite(this, false); multiplexer.RequestWrite(this, false);
} }
return true; return true;
} }
internal bool ConfirmRemoveFromWriteQueue()
{
lock(queue.SyncLock)
{
if(queue.Count() == 0)
{
Interlocked.Exchange(ref inWriteQueue, 0);
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// This writes a message **directly** to the output stream; note /// This writes a message **directly** to the output stream; note
/// that this ignores the queue, so should only be used *either* /// that this ignores the queue, so should only be used *either*
...@@ -509,21 +506,79 @@ internal bool WriteMessageDirect(PhysicalConnection tmp, Message next) ...@@ -509,21 +506,79 @@ internal bool WriteMessageDirect(PhysicalConnection tmp, Message next)
} }
} }
private State ChangeState(State newState) internal WriteResult WriteQueue(int maxWork)
{ {
#pragma warning disable 0420 bool weAreWriter = false;
var oldState = (State)Interlocked.Exchange(ref state, (int)newState); PhysicalConnection conn = null;
#pragma warning restore 0420 try
if (oldState != newState)
{ {
multiplexer.Trace(connectionType + " state changed from " + oldState + " to " + newState); Trace("Writing queue from bridge");
if (newState == State.Disconnected) weAreWriter = Interlocked.CompareExchange(ref activeWriters, 1, 0) == 0;
if (!weAreWriter)
{
Trace("(aborting: existing writer)");
return WriteResult.CompetingWriter;
}
conn = GetConnection();
if (conn == null)
{
AbortUnsent();
Trace("Connection not available; exiting");
return WriteResult.NoConnection;
}
int count = 0;
Message last = null;
while (true)
{
var next = queue.Dequeue();
if (next == null)
{
Trace("Nothing to write; exiting");
Trace(last != null, "Flushed up to: " + last);
conn.Flush();
return WriteResult.QueueEmpty;
}
last = next;
Trace("Now pending: " + GetPendingCount());
if (!WriteMessageDirect(conn, next))
{ {
AbortUnsent(); AbortUnsent();
Trace("write failed; connection is toast; exiting");
return WriteResult.NoConnection;
} }
count++;
if (maxWork > 0 && count >= maxWork)
{
Trace("Work limit; exiting");
Trace(last != null, "Flushed up to: " + last);
conn.Flush();
break;
} }
return oldState; }
}
catch (IOException ex)
{
if (conn != null) conn.RecordConnectionFailed(ConnectionFailureType.SocketFailure, ex);
AbortUnsent();
}
catch (Exception ex)
{
AbortUnsent();
OnInternalError(ex);
}
finally
{
if (weAreWriter)
{
Interlocked.Exchange(ref activeWriters, 0);
Trace("Exiting writer");
}
}
return queue.Any() ? WriteResult.MoreWork : WriteResult.QueueEmpty;
} }
private void AbortUnsent() private void AbortUnsent()
...@@ -538,6 +593,23 @@ private void AbortUnsent() ...@@ -538,6 +593,23 @@ private void AbortUnsent()
} }
} }
private State ChangeState(State newState)
{
#pragma warning disable 0420
var oldState = (State)Interlocked.Exchange(ref state, (int)newState);
#pragma warning restore 0420
if (oldState != newState)
{
multiplexer.Trace(connectionType + " state changed from " + oldState + " to " + newState);
if (newState == State.Disconnected)
{
AbortUnsent();
}
}
return oldState;
}
private bool ChangeState(State oldState, State newState) private bool ChangeState(State oldState, State newState)
{ {
#pragma warning disable 0420 #pragma warning disable 0420
...@@ -560,7 +632,7 @@ private void Flush() ...@@ -560,7 +632,7 @@ private void Flush()
Trace(connectionType + " flushed"); Trace(connectionType + " flushed");
tmp.Flush(); tmp.Flush();
} }
catch(Exception ex) catch (Exception ex)
{ {
OnInternalError(ex); OnInternalError(ex);
} }
...@@ -595,8 +667,26 @@ private PhysicalConnection GetConnection() ...@@ -595,8 +667,26 @@ private PhysicalConnection GetConnection()
return physical; return physical;
} }
private void LogNonPreferred(CommandFlags flags, bool isSlave)
{
if ((flags & Message.InternalCallFlag) == 0) // don't log internal-call
{
if (isSlave)
{
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferMaster)
Interlocked.Increment(ref nonPreferredEndpointCount);
}
else
{
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferSlave)
Interlocked.Increment(ref nonPreferredEndpointCount);
}
}
}
private void OnInternalError(Exception exception, [CallerMemberName] string origin = null)
{
multiplexer.OnInternalError(exception, serverEndPoint.EndPoint, connectionType, origin);
}
private void SelectDatabase(PhysicalConnection connection, Message message) private void SelectDatabase(PhysicalConnection connection, Message message)
{ {
int db = message.Db; int db = message.Db;
...@@ -700,89 +790,5 @@ private bool WriteMessageToServer(PhysicalConnection connection, Message message ...@@ -700,89 +790,5 @@ private bool WriteMessageToServer(PhysicalConnection connection, Message message
return false; return false;
} }
} }
internal WriteResult WriteQueue(int maxWork)
{
bool weAreWriter = false;
PhysicalConnection conn = null;
try
{
Trace("Writing queue from bridge");
weAreWriter = Interlocked.CompareExchange(ref activeWriters, 1, 0) == 0;
if (!weAreWriter)
{
Trace("(aborting: existing writer)");
return WriteResult.CompetingWriter;
}
conn = GetConnection();
if(conn == null)
{
AbortUnsent();
Trace("Connection not available; exiting");
return WriteResult.NoConnection;
}
int count = 0;
Message last = null;
while (true)
{
var next = queue.Dequeue();
if (next == null)
{
Trace("Nothing to write; exiting");
Trace(last != null, "Flushed up to: " + last);
conn.Flush();
return WriteResult.QueueEmpty;
}
last = next;
Trace("Now pending: " + GetPendingCount());
if(!WriteMessageDirect(conn, next))
{
AbortUnsent();
Trace("write failed; connection is toast; exiting");
return WriteResult.NoConnection;
}
count++;
if (maxWork > 0 && count >= maxWork)
{
Trace("Work limit; exiting");
Trace(last != null, "Flushed up to: " + last);
conn.Flush();
break;
}
}
}
catch(IOException ex)
{
if (conn != null) conn.RecordConnectionFailed(ConnectionFailureType.SocketFailure, ex);
AbortUnsent();
}
catch(Exception ex)
{
AbortUnsent();
OnInternalError(ex);
}
finally
{
if (weAreWriter)
{
Interlocked.Exchange(ref activeWriters, 0);
Trace("Exiting writer");
}
}
return queue.Any() ? WriteResult.MoreWork : WriteResult.QueueEmpty;
}
}
enum WriteResult
{
QueueEmpty,
MoreWork,
CompetingWriter,
NoConnection,
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
...@@ -25,17 +24,20 @@ internal enum WorkState ...@@ -25,17 +24,20 @@ internal enum WorkState
internal sealed partial class PhysicalConnection : IDisposable, ISocketCallback internal sealed partial class PhysicalConnection : IDisposable, ISocketCallback
{ {
void ISocketCallback.Error() internal readonly byte[] ChannelPrefix;
{
RecordConnectionFailed(ConnectionFailureType.SocketFailure);
}
private const int DefaultRedisDatabaseCount = 16; private const int DefaultRedisDatabaseCount = 16;
public long SubscriptionCount { get;set; }
private static readonly byte[] Crlf = Encoding.ASCII.GetBytes("\r\n"); private static readonly byte[] Crlf = Encoding.ASCII.GetBytes("\r\n");
static readonly AsyncCallback endRead = result =>
{
PhysicalConnection physical;
if (result.CompletedSynchronously || (physical = result.AsyncState as PhysicalConnection) == null) return;
physical.multiplexer.Trace("Completed synchronously: processing in callback", physical.physicalName);
if (physical.EndReading(result)) physical.BeginReading();
};
private static readonly byte[] message = Encoding.UTF8.GetBytes("message"), pmessage = Encoding.UTF8.GetBytes("pmessage"); private static readonly byte[] message = Encoding.UTF8.GetBytes("message"), pmessage = Encoding.UTF8.GetBytes("pmessage");
static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select( static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select(
...@@ -53,20 +55,24 @@ private static readonly Message ...@@ -53,20 +55,24 @@ private static readonly Message
private readonly ConnectionMultiplexer multiplexer; private readonly ConnectionMultiplexer multiplexer;
// things sent to this physical, but not yet received
private readonly Queue<Message> outstanding = new Queue<Message>();
readonly string physicalName; readonly string physicalName;
volatile int currentDatabase = 0; volatile int currentDatabase = 0;
ReadMode currentReadMode = ReadMode.NotSpecified; ReadMode currentReadMode = ReadMode.NotSpecified;
int failureReported;
byte[] ioBuffer = new byte[512]; byte[] ioBuffer = new byte[512];
int ioBufferBytes = 0; int ioBufferBytes = 0;
private Stream netStream, outStream; long lastWriteTickCount, lastReadTickCount, lastBeatTickCount;
// things sent to this physical, but not yet received private Stream netStream, outStream;
private readonly Queue<Message> outstanding = new Queue<Message>();
private SocketToken socketToken; private SocketToken socketToken;
...@@ -87,13 +93,6 @@ public PhysicalConnection(PhysicalBridge bridge) ...@@ -87,13 +93,6 @@ public PhysicalConnection(PhysicalBridge bridge)
//socket.SendTimeout = socket.ReceiveTimeout = multiplexer.TimeoutMilliseconds; //socket.SendTimeout = socket.ReceiveTimeout = multiplexer.TimeoutMilliseconds;
OnCreateEcho(); OnCreateEcho();
} }
public long LastWriteSecondsAgo
{
get
{
return unchecked(Environment.TickCount - Interlocked.Read(ref lastWriteTickCount)) / 1000;
}
}
private enum ReadMode : byte private enum ReadMode : byte
{ {
...@@ -104,10 +103,19 @@ private enum ReadMode : byte ...@@ -104,10 +103,19 @@ private enum ReadMode : byte
public PhysicalBridge Bridge { get { return bridge; } } public PhysicalBridge Bridge { get { return bridge; } }
public long LastWriteSecondsAgo
{
get
{
return unchecked(Environment.TickCount - Interlocked.Read(ref lastWriteTickCount)) / 1000;
}
}
public ConnectionMultiplexer Multiplexer { get { return multiplexer; } } public ConnectionMultiplexer Multiplexer { get { return multiplexer; } }
public bool TransactionActive { get; internal set; } public long SubscriptionCount { get; set; }
public bool TransactionActive { get; internal set; }
public void Dispose() public void Dispose()
{ {
...@@ -127,30 +135,25 @@ public void Dispose() ...@@ -127,30 +135,25 @@ public void Dispose()
if (socketToken.HasValue) if (socketToken.HasValue)
{ {
var socketManager = multiplexer.SocketManager; var socketManager = multiplexer.SocketManager;
if(socketManager !=null) socketManager.Shutdown(socketToken); if (socketManager != null) socketManager.Shutdown(socketToken);
socketToken = default(SocketToken); socketToken = default(SocketToken);
multiplexer.Trace("Disconnected", physicalName); multiplexer.Trace("Disconnected", physicalName);
RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed); RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed);
} }
OnCloseEcho(); OnCloseEcho();
} }
long lastWriteTickCount, lastReadTickCount, lastBeatTickCount;
public void Flush() public void Flush()
{ {
outStream.Flush(); outStream.Flush();
Interlocked.Exchange(ref lastWriteTickCount, Environment.TickCount); Interlocked.Exchange(ref lastWriteTickCount, Environment.TickCount);
} }
int failureReported;
internal void OnHeartbeat()
{
Interlocked.Exchange(ref lastBeatTickCount, Environment.TickCount);
}
public void RecordConnectionFailed(ConnectionFailureType failureType, Exception innerException = null, [CallerMemberName] string origin = null) public void RecordConnectionFailed(ConnectionFailureType failureType, Exception innerException = null, [CallerMemberName] string origin = null)
{ {
IdentifyFailureType(innerException, ref failureType); IdentifyFailureType(innerException, ref failureType);
if(failureType == ConnectionFailureType.InternalFailure) OnInternalError(innerException, origin); if (failureType == ConnectionFailureType.InternalFailure) OnInternalError(innerException, origin);
// stop anything new coming in... // stop anything new coming in...
bridge.Trace("Failed: " + failureType); bridge.Trace("Failed: " + failureType);
...@@ -201,7 +204,7 @@ public void RecordConnectionFailed(ConnectionFailureType failureType, Exception ...@@ -201,7 +204,7 @@ public void RecordConnectionFailed(ConnectionFailureType failureType, Exception
// burn the socket // burn the socket
var socketManager = multiplexer.SocketManager; var socketManager = multiplexer.SocketManager;
if(socketManager != null) socketManager.Shutdown(socketToken); if (socketManager != null) socketManager.Shutdown(socketToken);
} }
public override string ToString() public override string ToString()
...@@ -237,30 +240,6 @@ internal void GetCounters(ConnectionCounters counters) ...@@ -237,30 +240,6 @@ internal void GetCounters(ConnectionCounters counters)
counters.Subscriptions = SubscriptionCount; counters.Subscriptions = SubscriptionCount;
} }
internal int GetSentAwaitingResponseCount()
{
lock (outstanding)
{
return outstanding.Count;
}
}
internal void GetStormLog(StringBuilder sb)
{
lock(outstanding)
{
if (outstanding.Count == 0) return;
sb.Append("Sent, awaiting response from server: ").Append(outstanding.Count).AppendLine();
int total = 0;
foreach(var item in outstanding)
{
if (++total >= 500) break;
item.AppendStormLog(sb);
sb.AppendLine();
}
}
}
internal Message GetReadModeCommand(bool isMasterOnly) internal Message GetReadModeCommand(bool isMasterOnly)
{ {
var serverEndpoint = bridge.ServerEndPoint; var serverEndpoint = bridge.ServerEndPoint;
...@@ -320,6 +299,40 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message) ...@@ -320,6 +299,40 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message)
return null; return null;
} }
internal int GetSentAwaitingResponseCount()
{
lock (outstanding)
{
return outstanding.Count;
}
}
internal void GetStormLog(StringBuilder sb)
{
lock (outstanding)
{
if (outstanding.Count == 0) return;
sb.Append("Sent, awaiting response from server: ").Append(outstanding.Count).AppendLine();
int total = 0;
foreach (var item in outstanding)
{
if (++total >= 500) break;
item.AppendStormLog(sb);
sb.AppendLine();
}
}
}
internal void OnHeartbeat()
{
Interlocked.Exchange(ref lastBeatTickCount, Environment.TickCount);
}
internal void OnInternalError(Exception exception, [CallerMemberName] string origin = null)
{
multiplexer.OnInternalError(exception, bridge.ServerEndPoint.EndPoint, connectionType, origin);
}
internal void SetUnknownDatabase() internal void SetUnknownDatabase()
{ // forces next db-specific command to issue a select { // forces next db-specific command to issue a select
currentDatabase = -1; currentDatabase = -1;
...@@ -329,6 +342,7 @@ internal void Write(RedisKey key) ...@@ -329,6 +342,7 @@ internal void Write(RedisKey key)
{ {
WriteUnified(outStream, key.Value); WriteUnified(outStream, key.Value);
} }
internal void Write(RedisChannel channel) internal void Write(RedisChannel channel)
{ {
WriteUnified(outStream, ChannelPrefix, channel.Value); WriteUnified(outStream, ChannelPrefix, channel.Value);
...@@ -358,7 +372,6 @@ internal void WriteHeader(RedisCommand command, int arguments) ...@@ -358,7 +372,6 @@ internal void WriteHeader(RedisCommand command, int arguments)
WriteUnified(outStream, commandBytes); WriteUnified(outStream, commandBytes);
} }
static void WriteRaw(Stream stream, long value, bool withLengthPrefix = false) static void WriteRaw(Stream stream, long value, bool withLengthPrefix = false)
{ {
if (value >= 0 && value <= 9) if (value >= 0 && value <= 9)
...@@ -443,6 +456,7 @@ static void WriteUnified(Stream stream, byte[] value) ...@@ -443,6 +456,7 @@ static void WriteUnified(Stream stream, byte[] value)
stream.Write(Crlf, 0, 2); stream.Write(Crlf, 0, 2);
} }
} }
static void WriteUnified(Stream stream, byte[] prefix, byte[] value) static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{ {
stream.WriteByte((byte)'$'); stream.WriteByte((byte)'$');
...@@ -450,7 +464,7 @@ static void WriteUnified(Stream stream, byte[] prefix, byte[] value) ...@@ -450,7 +464,7 @@ static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{ {
WriteRaw(stream, -1); // note that not many things like this... WriteRaw(stream, -1); // note that not many things like this...
} }
else if(prefix == null) else if (prefix == null)
{ {
WriteRaw(stream, value.Length); WriteRaw(stream, value.Length);
stream.Write(value, 0, value.Length); stream.Write(value, 0, value.Length);
...@@ -473,15 +487,29 @@ static void WriteUnified(Stream stream, long value) ...@@ -473,15 +487,29 @@ static void WriteUnified(Stream stream, long value)
WriteRaw(stream, value, withLengthPrefix: true); WriteRaw(stream, value, withLengthPrefix: true);
} }
void BeginReading()
{
bool keepReading;
do
{
keepReading = false;
int space = EnsureSpaceAndComputeBytesToRead();
multiplexer.Trace("Beginning async read...", physicalName);
var result = netStream.BeginRead(ioBuffer, ioBufferBytes, space, endRead, this);
if (result.CompletedSynchronously)
{
multiplexer.Trace("Completed synchronously: processing immediately", physicalName);
keepReading = EndReading(result);
}
} while (keepReading);
}
SocketMode ISocketCallback.Connected(Stream stream) SocketMode ISocketCallback.Connected(Stream stream)
{ {
try try
{ {
#if MONO var socketMode = SocketManager.DefaultSocketMode;
var socketMode = SocketMode.Async;
#else
var socketMode = SocketMode.Poll;
#endif
// disallow connection in some cases // disallow connection in some cases
OnDebugAbort(); OnDebugAbort();
...@@ -497,7 +525,7 @@ SocketMode ISocketCallback.Connected(Stream stream) ...@@ -497,7 +525,7 @@ SocketMode ISocketCallback.Connected(Stream stream)
#endif #endif
); );
ssl.AuthenticateAsClient(config.SslHost); ssl.AuthenticateAsClient(config.SslHost);
if(!ssl.IsEncrypted) if (!ssl.IsEncrypted)
{ {
RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure); RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure);
multiplexer.Trace("Encryption failure"); multiplexer.Trace("Encryption failure");
...@@ -524,6 +552,21 @@ SocketMode ISocketCallback.Connected(Stream stream) ...@@ -524,6 +552,21 @@ SocketMode ISocketCallback.Connected(Stream stream)
} }
} }
private bool EndReading(IAsyncResult result)
{
try
{
var tmp = netStream;
int bytesRead = tmp == null ? 0 : tmp.EndRead(result);
return ProcessReadBytes(bytesRead);
}
catch (Exception ex)
{
RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex);
return false;
}
}
int EnsureSpaceAndComputeBytesToRead() int EnsureSpaceAndComputeBytesToRead()
{ {
int space = ioBuffer.Length - ioBufferBytes; int space = ioBuffer.Length - ioBufferBytes;
...@@ -534,7 +577,11 @@ int EnsureSpaceAndComputeBytesToRead() ...@@ -534,7 +577,11 @@ int EnsureSpaceAndComputeBytesToRead()
} }
return space; return space;
} }
internal readonly byte[] ChannelPrefix;
void ISocketCallback.Error()
{
RecordConnectionFailed(ConnectionFailureType.SocketFailure);
}
void MatchResult(RawResult result) void MatchResult(RawResult result)
{ {
// check to see if it could be an out-of-band pubsub message // check to see if it could be an out-of-band pubsub message
...@@ -597,16 +644,22 @@ void MatchResult(RawResult result) ...@@ -597,16 +644,22 @@ void MatchResult(RawResult result)
bridge.CompleteSyncOrAsync(msg); bridge.CompleteSyncOrAsync(msg);
} }
} }
internal void OnInternalError(Exception exception, [CallerMemberName] string origin = null)
{
multiplexer.OnInternalError(exception, bridge.ServerEndPoint.EndPoint, connectionType, origin);
}
partial void OnCloseEcho(); partial void OnCloseEcho();
partial void OnCreateEcho(); partial void OnCreateEcho();
partial void OnDebugAbort(); partial void OnDebugAbort();
void ISocketCallback.OnHeartbeat()
{
try
{
bridge.OnHeartbeat(true); // all the fun code is here
}
catch (Exception ex)
{
OnInternalError(ex);
}
}
partial void OnWrapForLogging(ref Stream stream, string name); partial void OnWrapForLogging(ref Stream stream, string name);
private int ProcessBuffer(byte[] underlying, ref int offset, ref int count) private int ProcessBuffer(byte[] underlying, ref int offset, ref int count)
{ {
...@@ -630,40 +683,6 @@ private int ProcessBuffer(byte[] underlying, ref int offset, ref int count) ...@@ -630,40 +683,6 @@ private int ProcessBuffer(byte[] underlying, ref int offset, ref int count)
} while (result.HasValue); } while (result.HasValue);
return messageCount; return messageCount;
} }
void ISocketCallback.OnHeartbeat()
{
try
{
bridge.OnHeartbeat(true); // all the fun code is here
}
catch (Exception ex)
{
OnInternalError(ex);
}
}
void ISocketCallback.Read()
{
try
{
do
{
int space = EnsureSpaceAndComputeBytesToRead();
var tmp = netStream;
int bytesRead = tmp == null ? 0 : tmp.Read(ioBuffer, ioBufferBytes, space);
if (!ProcessReadBytes(bytesRead)) return; // EOF
} while (socketToken.Available != 0);
multiplexer.Trace("Buffer exhausted", physicalName);
// ^^^ note that the socket manager will call us again when there is something to do
}
catch (Exception ex)
{
RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex);
}
}
private bool ProcessReadBytes(int bytesRead) private bool ProcessReadBytes(int bytesRead)
{ {
if (bytesRead <= 0) if (bytesRead <= 0)
...@@ -694,48 +713,26 @@ private bool ProcessReadBytes(int bytesRead) ...@@ -694,48 +713,26 @@ private bool ProcessReadBytes(int bytesRead)
return true; return true;
} }
static readonly AsyncCallback endRead = result => void ISocketCallback.Read()
{
PhysicalConnection physical;
if (result.CompletedSynchronously || (physical = result.AsyncState as PhysicalConnection) == null) return;
physical.multiplexer.Trace("Completed synchronously: processing in callback", physical.physicalName);
if(physical.EndReading(result)) physical.BeginReading();
};
private bool EndReading(IAsyncResult result)
{ {
try try
{ {
var tmp = netStream;
int bytesRead = tmp == null ? 0 : tmp.EndRead(result);
return ProcessReadBytes(bytesRead);
} catch(Exception ex)
{
RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex);
return false;
}
}
void ISocketCallback.StartReading()
{
BeginReading();
}
void BeginReading()
{
bool keepReading;
do do
{ {
keepReading = false;
int space = EnsureSpaceAndComputeBytesToRead(); int space = EnsureSpaceAndComputeBytesToRead();
multiplexer.Trace("Beginning async read...", physicalName); var tmp = netStream;
var result = netStream.BeginRead(ioBuffer, ioBufferBytes, space, endRead, this); int bytesRead = tmp == null ? 0 : tmp.Read(ioBuffer, ioBufferBytes, space);
if (result.CompletedSynchronously)
if (!ProcessReadBytes(bytesRead)) return; // EOF
} while (socketToken.Available != 0);
multiplexer.Trace("Buffer exhausted", physicalName);
// ^^^ note that the socket manager will call us again when there is something to do
}
catch (Exception ex)
{ {
multiplexer.Trace("Completed synchronously: processing immediately", physicalName); RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex);
keepReading = EndReading(result);
} }
} while (keepReading);
} }
private RawResult ReadArray(byte[] buffer, ref int offset, ref int count) private RawResult ReadArray(byte[] buffer, ref int offset, ref int count)
{ {
var itemCount = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count); var itemCount = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count);
...@@ -802,6 +799,10 @@ private RawResult ReadLineTerminatedString(ResultType type, byte[] buffer, ref i ...@@ -802,6 +799,10 @@ private RawResult ReadLineTerminatedString(ResultType type, byte[] buffer, ref i
return RawResult.Nil; return RawResult.Nil;
} }
void ISocketCallback.StartReading()
{
BeginReading();
}
RawResult TryParseResult(byte[] buffer, ref int offset, ref int count) RawResult TryParseResult(byte[] buffer, ref int offset, ref int count)
{ {
if(count == 0) return RawResult.Nil; if(count == 0) return RawResult.Nil;
......
...@@ -45,6 +45,8 @@ public RawResult(RawResult[] arr) ...@@ -45,6 +45,8 @@ public RawResult(RawResult[] arr)
public bool IsError { get { return resultType == ResultType.Error; } } public bool IsError { get { return resultType == ResultType.Error; } }
public ResultType Type { get { return resultType; } } public ResultType Type { get { return resultType; } }
internal bool IsNull { get { return arr == null; } }
public override string ToString() public override string ToString()
{ {
if (arr == null) if (arr == null)
...@@ -65,29 +67,17 @@ public override string ToString() ...@@ -65,29 +67,17 @@ public override string ToString()
return "(unknown)"; return "(unknown)";
} }
} }
internal RedisKey AsRedisKey()
{
switch (resultType)
{
case ResultType.SimpleString:
case ResultType.BulkString:
return (RedisKey)GetBlob();
default:
throw new InvalidCastException("Cannot convert to RedisKey: " + resultType);
}
}
internal RedisChannel AsRedisChannel(byte[] channelPrefix) internal RedisChannel AsRedisChannel(byte[] channelPrefix)
{ {
switch(resultType) switch (resultType)
{ {
case ResultType.SimpleString: case ResultType.SimpleString:
case ResultType.BulkString: case ResultType.BulkString:
if(channelPrefix == null) if (channelPrefix == null)
{ {
return (RedisChannel)GetBlob(); return (RedisChannel)GetBlob();
} }
if(AssertStarts(channelPrefix)) if (AssertStarts(channelPrefix))
{ {
var src = (byte[])arr; var src = (byte[])arr;
...@@ -101,6 +91,17 @@ internal RedisChannel AsRedisChannel(byte[] channelPrefix) ...@@ -101,6 +91,17 @@ internal RedisChannel AsRedisChannel(byte[] channelPrefix)
} }
} }
internal RedisKey AsRedisKey()
{
switch (resultType)
{
case ResultType.SimpleString:
case ResultType.BulkString:
return (RedisKey)GetBlob();
default:
throw new InvalidCastException("Cannot convert to RedisKey: " + resultType);
}
}
internal RedisValue AsRedisValue() internal RedisValue AsRedisValue()
{ {
switch (resultType) switch (resultType)
...@@ -156,7 +157,6 @@ internal bool AssertStarts(byte[] expected) ...@@ -156,7 +157,6 @@ internal bool AssertStarts(byte[] expected)
} }
return true; return true;
} }
internal bool IsNull { get { return arr == null; } }
internal byte[] GetBlob() internal byte[] GetBlob()
{ {
var src = (byte[])arr; var src = (byte[])arr;
...@@ -182,16 +182,6 @@ internal bool GetBoolean() ...@@ -182,16 +182,6 @@ internal bool GetBoolean()
} }
} }
internal bool TryGetInt64(out long value)
{
if (arr == null)
{
value = 0;
return false;
}
return RedisValue.TryParseInt64(arr as byte[], offset, count, out value);
}
internal RawResult[] GetItems() internal RawResult[] GetItems()
{ {
return (RawResult[])arr; return (RawResult[])arr;
...@@ -251,19 +241,29 @@ internal string GetString() ...@@ -251,19 +241,29 @@ internal string GetString()
internal bool TryGetDouble(out double val) internal bool TryGetDouble(out double val)
{ {
if(arr == null) if (arr == null)
{ {
val = 0; val = 0;
return false; return false;
} }
long i64; long i64;
if(TryGetInt64(out i64)) if (TryGetInt64(out i64))
{ {
val = i64; val = i64;
return true; return true;
} }
return Format.TryParseDouble(GetString(), out val); return Format.TryParseDouble(GetString(), out val);
} }
internal bool TryGetInt64(out long value)
{
if (arr == null)
{
value = 0;
return false;
}
return RedisValue.TryParseInt64(arr as byte[], offset, count, out value);
}
} }
} }
...@@ -9,27 +9,13 @@ internal abstract partial class RedisBase : IRedis ...@@ -9,27 +9,13 @@ internal abstract partial class RedisBase : IRedis
internal readonly ConnectionMultiplexer multiplexer; internal readonly ConnectionMultiplexer multiplexer;
protected readonly object asyncState; protected readonly object asyncState;
ConnectionMultiplexer IRedisAsync.Multiplexer { get { return multiplexer; } }
internal RedisBase(ConnectionMultiplexer multiplexer, object asyncState) internal RedisBase(ConnectionMultiplexer multiplexer, object asyncState)
{ {
this.multiplexer = multiplexer; this.multiplexer = multiplexer;
this.asyncState = asyncState; this.asyncState = asyncState;
} }
private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlags flags) ConnectionMultiplexer IRedisAsync.Multiplexer { get { return multiplexer; } }
{
// do the best we can with available commands
var map = multiplexer.CommandMap;
if(map.IsAvailable(RedisCommand.PING))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING);
if(map.IsAvailable(RedisCommand.TIME))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME);
if (map.IsAvailable(RedisCommand.ECHO))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING);
// as our fallback, we'll do something odd... we'll treat a key like a value, out of sheer desperation
// note: this usually means: twemproxy - in which case we're fine anyway, since the proxy does the routing
return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId);
}
public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None)
{ {
var msg = GetTimerMessage(flags); var msg = GetTimerMessage(flags);
...@@ -59,15 +45,16 @@ public override string ToString() ...@@ -59,15 +45,16 @@ public override string ToString()
return multiplexer.ToString(); return multiplexer.ToString();
} }
public void Wait(Task task)
{
multiplexer.Wait(task);
}
public bool TryWait(Task task) public bool TryWait(Task task)
{ {
return task.Wait(multiplexer.TimeoutMilliseconds); return task.Wait(multiplexer.TimeoutMilliseconds);
} }
public void Wait(Task task)
{
multiplexer.Wait(task);
}
public T Wait<T>(Task<T> task) public T Wait<T>(Task<T> task)
{ {
return multiplexer.Wait(task); return multiplexer.Wait(task);
...@@ -126,7 +113,7 @@ protected void WhenAlwaysOrExistsOrNotExists(When when) ...@@ -126,7 +113,7 @@ protected void WhenAlwaysOrExistsOrNotExists(When when)
protected void WhenAlwaysOrNotExists(When when) protected void WhenAlwaysOrNotExists(When when)
{ {
switch(when) switch (when)
{ {
case When.Always: case When.Always:
case When.NotExists: case When.NotExists:
...@@ -135,5 +122,20 @@ protected void WhenAlwaysOrNotExists(When when) ...@@ -135,5 +122,20 @@ protected void WhenAlwaysOrNotExists(When when)
throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, NotExists"); throw new ArgumentException(when + " is not valid in this context; the permitted values are: Always, NotExists");
} }
} }
private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlags flags)
{
// do the best we can with available commands
var map = multiplexer.CommandMap;
if(map.IsAvailable(RedisCommand.PING))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING);
if(map.IsAvailable(RedisCommand.TIME))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME);
if (map.IsAvailable(RedisCommand.ECHO))
return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING);
// as our fallback, we'll do something odd... we'll treat a key like a value, out of sheer desperation
// note: this usually means: twemproxy - in which case we're fine anyway, since the proxy does the routing
return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId);
}
} }
} }
...@@ -9,6 +9,15 @@ namespace StackExchange.Redis ...@@ -9,6 +9,15 @@ namespace StackExchange.Redis
public struct RedisChannel : IEquatable<RedisChannel> public struct RedisChannel : IEquatable<RedisChannel>
{ {
internal static readonly RedisChannel[] EmptyArray = new RedisChannel[0];
private readonly byte[] value;
private RedisChannel(byte[] value)
{
this.value = value;
}
/// <summary> /// <summary>
/// Indicates whether the channel-name is either null or a zero-length value /// Indicates whether the channel-name is either null or a zero-length value
/// </summary> /// </summary>
...@@ -20,33 +29,6 @@ public bool IsNullOrEmpty ...@@ -20,33 +29,6 @@ public bool IsNullOrEmpty
} }
} }
internal RedisChannel Clone()
{
byte[] clone = value == null ? null : (byte[])value.Clone();
return clone;
}
internal bool Contains(byte value)
{
return this.value != null && Array.IndexOf(this.value, value) >= 0;
}
internal static bool AssertStarts(byte[] value, byte[] expected)
{
for (int i = 0; i < expected.Length; i++)
{
if (expected[i] != value[i]) return false;
}
return true;
}
internal static readonly RedisChannel[] EmptyArray = new RedisChannel[0];
private readonly byte[] value;
private RedisChannel(byte[] value)
{
this.value = value;
}
internal bool IsNull internal bool IsNull
{ {
get { return value == null; } get { return value == null; }
...@@ -178,12 +160,31 @@ public override string ToString() ...@@ -178,12 +160,31 @@ public override string ToString()
return ((string)this) ?? "(null)"; return ((string)this) ?? "(null)";
} }
internal static bool AssertStarts(byte[] value, byte[] expected)
{
for (int i = 0; i < expected.Length; i++)
{
if (expected[i] != value[i]) return false;
}
return true;
}
internal RedisChannel Assert() internal RedisChannel Assert()
{ {
if (IsNull) throw new ArgumentException("A null key is not valid in this context"); if (IsNull) throw new ArgumentException("A null key is not valid in this context");
return this; return this;
} }
internal RedisChannel Clone()
{
byte[] clone = value == null ? null : (byte[])value.Clone();
return clone;
}
internal bool Contains(byte value)
{
return this.value != null && Array.IndexOf(this.value, value) >= 0;
}
/// <summary> /// <summary>
/// Create a channel name from a String /// Create a channel name from a String
/// </summary> /// </summary>
......
...@@ -193,6 +193,15 @@ public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlag ...@@ -193,6 +193,15 @@ public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlag
return ExecuteAsync(msg, ResultProcessor.Int64); return ExecuteAsync(msg, ResultProcessor.Int64);
} }
public IEnumerable<KeyValuePair<RedisValue, RedisValue>> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None)
{
var scan = TryScan<KeyValuePair<RedisValue, RedisValue>>(key, pattern, pageSize, flags, RedisCommand.HSCAN, HashScanResultProcessor.Default);
if (scan != null) return scan;
if (pattern.IsNull) return HashGetAll(key, flags);
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
}
public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
WhenAlwaysOrNotExists(when); WhenAlwaysOrNotExists(when);
...@@ -362,6 +371,18 @@ public Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlag ...@@ -362,6 +371,18 @@ public Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlag
return ExecuteAsync(msg, ResultProcessor.Boolean); return ExecuteAsync(msg, ResultProcessor.Boolean);
} }
public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Db, flags, RedisCommand.RANDOMKEY);
return ExecuteSync(msg, ResultProcessor.RedisKey);
}
public Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Db, flags, RedisCommand.RANDOMKEY);
return ExecuteAsync(msg, ResultProcessor.RedisKey);
}
public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
WhenAlwaysOrNotExists(when); WhenAlwaysOrNotExists(when);
...@@ -660,19 +681,6 @@ public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, ...@@ -660,19 +681,6 @@ public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry,
{ {
return StringSetAsync(key, value, expiry, When.NotExists, flags); return StringSetAsync(key, value, expiry, When.NotExists, flags);
} }
public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Db, flags, RedisCommand.RANDOMKEY);
return ExecuteSync(msg, ResultProcessor.RedisKey);
}
public Task<RedisKey> KeyRandomAsync(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Db, flags, RedisCommand.RANDOMKEY);
return ExecuteAsync(msg, ResultProcessor.RedisKey);
}
public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{ {
var msg = new ScriptEvalMessage(Db, flags, RedisCommand.EVAL, script, keys ?? RedisKey.EmptyArray, values ?? RedisValue.EmptyArray); var msg = new ScriptEvalMessage(Db, flags, RedisCommand.EVAL, script, keys ?? RedisKey.EmptyArray, values ?? RedisValue.EmptyArray);
...@@ -866,44 +874,14 @@ public Task<long> SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags ...@@ -866,44 +874,14 @@ public Task<long> SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags
return ExecuteAsync(msg, ResultProcessor.Int64); return ExecuteAsync(msg, ResultProcessor.Int64);
} }
public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None) public IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None)
{ {
var scan = TryScan(key, pattern, pageSize, flags, RedisCommand.SSCAN, SetScanResultProcessor.Default); var scan = TryScan<RedisValue>(key, pattern, pageSize, flags, RedisCommand.SSCAN, SetScanResultProcessor.Default);
if (scan != null) return scan; if (scan != null) return scan;
if (pattern.IsNull) return SetMembers(key, flags); if (pattern.IsNull) return SetMembers(key, flags);
throw ExceptionFactory.NotSupported(true, RedisCommand.SSCAN); throw ExceptionFactory.NotSupported(true, RedisCommand.SSCAN);
} }
public IEnumerable<KeyValuePair<RedisValue,RedisValue>> HashScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None)
{
var scan = TryScan(key, pattern, pageSize, flags, RedisCommand.HSCAN, HashScanResultProcessor.Default);
if (scan != null) return scan;
if (pattern.IsNull) return HashGetAll(key, flags);
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
}
public IEnumerable<KeyValuePair<RedisValue, double>> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanIterator.DefaultPageSize, CommandFlags flags = CommandFlags.None)
{
var scan = TryScan(key, pattern, pageSize, flags, RedisCommand.ZSCAN, SortedSetScanResultProcessor.Default);
if (scan != null) return scan;
if (pattern.IsNull) return SortedSetRangeByRankWithScores(key, flags: flags);
throw ExceptionFactory.NotSupported(true, RedisCommand.ZSCAN);
}
private IEnumerable<T> TryScan<T>(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags, RedisCommand command, ResultProcessor<ScanIterator<T>.ScanResult> processor)
{
if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize");
if (!multiplexer.CommandMap.IsAvailable(command)) return null;
ServerEndPoint server;
var features = GetFeatures(Db, key, flags, out server);
if (!features.Scan) return null;
if (ScanIterator.IsNil(pattern)) pattern = (byte[])null;
return new ScanIterator<T>(this, server, key, pattern, pageSize, flags, command, processor).Read();
}
public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None) public RedisValue[] Sort(RedisKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, RedisValue by = default(RedisValue), RedisValue[] get = null, CommandFlags flags = CommandFlags.None)
{ {
var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags); var msg = GetSortedSetAddMessage(default(RedisKey), key, skip, take, order, sortType, by, get, flags);
...@@ -1003,6 +981,7 @@ public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity, ...@@ -1003,6 +981,7 @@ public long SortedSetLength(RedisKey key, double min = double.NegativeInfinity,
var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags);
return ExecuteSync(msg, ResultProcessor.Int64); return ExecuteSync(msg, ResultProcessor.Int64);
} }
public Task<long> SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) public Task<long> SortedSetLengthAsync(RedisKey key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None)
{ {
var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags); var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags);
...@@ -1117,6 +1096,15 @@ public Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, d ...@@ -1117,6 +1096,15 @@ public Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, d
return ExecuteAsync(msg, ResultProcessor.Int64); return ExecuteAsync(msg, ResultProcessor.Int64);
} }
public IEnumerable<KeyValuePair<RedisValue, double>> SortedSetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None)
{
var scan = TryScan<KeyValuePair<RedisValue, double>>(key, pattern, pageSize, flags, RedisCommand.ZSCAN, SortedSetScanResultProcessor.Default);
if (scan != null) return scan;
if (pattern.IsNull) return SortedSetRangeByRankWithScores(key, flags: flags);
throw ExceptionFactory.NotSupported(true, RedisCommand.ZSCAN);
}
public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{ {
var msg = Message.Create(Db, flags, RedisCommand.ZSCORE, key, member); var msg = Message.Create(Db, flags, RedisCommand.ZSCORE, key, member);
...@@ -1625,6 +1613,7 @@ private Message GetSortedSetLengthMessage(RedisKey key, double min, double max, ...@@ -1625,6 +1613,7 @@ private Message GetSortedSetLengthMessage(RedisKey key, double min, double max,
var to = GetRange(max, exclude, false); var to = GetRange(max, exclude, false);
return Message.Create(Db, flags, RedisCommand.ZCOUNT, key, from, to); return Message.Create(Db, flags, RedisCommand.ZCOUNT, key, from, to);
} }
private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores) private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores)
{ {
// usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count] // usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count]
...@@ -1668,6 +1657,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina ...@@ -1668,6 +1657,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina
} }
return Message.CreateInSlot(Db, slot, flags, RedisCommand.BITOP, values); return Message.CreateInSlot(Db, slot, flags, RedisCommand.BITOP, values);
} }
private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags) private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags)
{ {
// these ones are too bespoke to warrant custom Message implementations // these ones are too bespoke to warrant custom Message implementations
...@@ -1683,6 +1673,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina ...@@ -1683,6 +1673,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina
slot = serverSelectionStrategy.CombineSlot(slot, second); slot = serverSelectionStrategy.CombineSlot(slot, second);
return Message.CreateInSlot(Db, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() }); return Message.CreateInSlot(Db, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() });
} }
Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor<RedisValueWithExpiry> processor, out ServerEndPoint server) Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor<RedisValueWithExpiry> processor, out ServerEndPoint server)
{ {
if (this is IBatch) if (this is IBatch)
...@@ -1721,6 +1712,7 @@ private Message GetStringSetMessage(KeyValuePair<RedisKey, RedisValue>[] values, ...@@ -1721,6 +1712,7 @@ private Message GetStringSetMessage(KeyValuePair<RedisKey, RedisValue>[] values,
return Message.CreateInSlot(Db, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args); return Message.CreateInSlot(Db, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args);
} }
} }
Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None) Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None)
{ {
WhenAlwaysOrExistsOrNotExists(when); WhenAlwaysOrExistsOrNotExists(when);
...@@ -1757,6 +1749,7 @@ Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = n ...@@ -1757,6 +1749,7 @@ Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = n
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
Message IncrMessage(RedisKey key, long value, CommandFlags flags) Message IncrMessage(RedisKey key, long value, CommandFlags flags)
{ {
switch (value) switch (value)
...@@ -1786,77 +1779,19 @@ private RedisCommand SetOperationCommand(SetOperation operation, bool store) ...@@ -1786,77 +1779,19 @@ private RedisCommand SetOperationCommand(SetOperation operation, bool store)
} }
} }
private IEnumerable<T> TryScan<T>(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags, RedisCommand command, ResultProcessor<ScanIterator<T>.ScanResult> processor)
internal sealed class ScriptLoadMessage : Message
{
internal readonly string Script;
public ScriptLoadMessage(CommandFlags flags, string script) : base(-1, flags, RedisCommand.SCRIPT)
{
if (script == null) throw new ArgumentNullException("script");
this.Script = script;
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(RedisLiterals.LOAD);
physical.Write((RedisValue)Script);
}
}
private abstract class ScanResultProcessor<T> : ResultProcessor<ScanIterator<T>.ScanResult>
{ {
protected abstract T[] Parse(RawResult result); if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize");
if (!multiplexer.CommandMap.IsAvailable(command)) return null;
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) ServerEndPoint server;
{ var features = GetFeatures(Db, key, flags, out server);
switch (result.Type) if (!features.Scan) return null;
{
case ResultType.MultiBulk:
var arr = result.GetItems();
long i64;
if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64))
{
var sscanResult = new ScanIterator<T>.ScanResult(i64, Parse(arr[1]));
SetResult(message, sscanResult);
return true;
}
break;
}
return false;
}
}
sealed class SetScanResultProcessor : ScanResultProcessor<RedisValue>
{
public static readonly ResultProcessor<ScanIterator<RedisValue>.ScanResult> Default = new SetScanResultProcessor();
private SetScanResultProcessor() { }
protected override RedisValue[] Parse(RawResult result)
{
return result.GetItemsAsValues();
}
}
sealed class HashScanResultProcessor : ScanResultProcessor<KeyValuePair<RedisValue,RedisValue>>
{
public static readonly ResultProcessor<ScanIterator<KeyValuePair<RedisValue, RedisValue>>.ScanResult> Default = new HashScanResultProcessor();
private HashScanResultProcessor() { }
protected override KeyValuePair<RedisValue, RedisValue>[] Parse(RawResult result)
{
KeyValuePair<RedisValue, RedisValue>[] pairs;
if (!ValuePairInterleaved.TryParse(result, out pairs)) pairs = null;
return pairs;
}
}
sealed class SortedSetScanResultProcessor : ScanResultProcessor<KeyValuePair<RedisValue, double>>
{
public static readonly ResultProcessor<ScanIterator<KeyValuePair<RedisValue, double>>.ScanResult> Default = new SortedSetScanResultProcessor();
private SortedSetScanResultProcessor() { }
protected override KeyValuePair<RedisValue, double>[] Parse(RawResult result)
{
KeyValuePair<RedisValue, double>[] pairs;
if (!SortedSetWithScores.TryParse(result, out pairs)) pairs = null;
return pairs;
}
}
internal static class ScanIterator if (ScanUtils.IsNil(pattern)) pattern = (byte[])null;
return new ScanIterator<T>(this, server, key, pattern, pageSize, flags, command, processor).Read();
}
internal static class ScanUtils
{ {
public const int DefaultPageSize = 10; public const int DefaultPageSize = 10;
public static bool IsNil(RedisValue pattern) public static bool IsNil(RedisValue pattern)
...@@ -1867,29 +1802,25 @@ public static bool IsNil(RedisValue pattern) ...@@ -1867,29 +1802,25 @@ public static bool IsNil(RedisValue pattern)
return rawValue.Length == 1 && rawValue[0] == '*'; return rawValue.Length == 1 && rawValue[0] == '*';
} }
} }
internal class ScanIterator<T> internal class ScanIterator<T>
{ {
internal struct ScanResult private readonly RedisCommand command;
{
public readonly long Cursor;
public readonly T[] Values;
public ScanResult(long cursor, T[] values)
{
this.Cursor = cursor;
this.Values = values;
}
}
private readonly RedisDatabase database; private readonly RedisDatabase database;
private readonly CommandFlags flags; private readonly CommandFlags flags;
private readonly RedisCommand command;
private readonly RedisKey key; private readonly RedisKey key;
private readonly int pageSize; private readonly int pageSize;
private readonly RedisValue pattern; private readonly RedisValue pattern;
private readonly ServerEndPoint server;
private readonly ResultProcessor<ScanResult> processor; private readonly ResultProcessor<ScanResult> processor;
private readonly ServerEndPoint server;
public ScanIterator(RedisDatabase database, ServerEndPoint server, RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags, public ScanIterator(RedisDatabase database, ServerEndPoint server, RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags,
RedisCommand command, ResultProcessor<ScanResult> processor) RedisCommand command, ResultProcessor<ScanResult> processor)
{ {
...@@ -1902,6 +1833,7 @@ public ScanResult(long cursor, T[] values) ...@@ -1902,6 +1833,7 @@ public ScanResult(long cursor, T[] values)
this.command = command; this.command = command;
this.processor = processor; this.processor = processor;
} }
public IEnumerable<T> Read() public IEnumerable<T> Read()
{ {
var msg = CreateMessage(0, false); var msg = CreateMessage(0, false);
...@@ -1930,9 +1862,9 @@ public IEnumerable<T> Read() ...@@ -1930,9 +1862,9 @@ public IEnumerable<T> Read()
Message CreateMessage(long cursor, bool running) Message CreateMessage(long cursor, bool running)
{ {
if (cursor == 0 && running) return null; // end of the line if (cursor == 0 && running) return null; // end of the line
if (ScanIterator.IsNil(pattern)) if (ScanUtils.IsNil(pattern))
{ {
if (pageSize == ScanIterator.DefaultPageSize) if (pageSize == ScanUtils.DefaultPageSize)
{ {
return Message.Create(database.Database, flags, command, key, cursor); return Message.Create(database.Database, flags, command, key, cursor);
} }
...@@ -1943,7 +1875,7 @@ Message CreateMessage(long cursor, bool running) ...@@ -1943,7 +1875,7 @@ Message CreateMessage(long cursor, bool running)
} }
else else
{ {
if (pageSize == ScanIterator.DefaultPageSize) if (pageSize == ScanUtils.DefaultPageSize)
{ {
return Message.Create(database.Database, flags, command, key, cursor, RedisLiterals.MATCH, pattern); return Message.Create(database.Database, flags, command, key, cursor, RedisLiterals.MATCH, pattern);
} }
...@@ -1954,8 +1886,67 @@ Message CreateMessage(long cursor, bool running) ...@@ -1954,8 +1886,67 @@ Message CreateMessage(long cursor, bool running)
} }
} }
internal struct ScanResult
{
public readonly long Cursor;
public readonly T[] Values;
public ScanResult(long cursor, T[] values)
{
this.Cursor = cursor;
this.Values = values;
}
}
}
internal sealed class ScriptLoadMessage : Message
{
internal readonly string Script;
public ScriptLoadMessage(CommandFlags flags, string script) : base(-1, flags, RedisCommand.SCRIPT)
{
if (script == null) throw new ArgumentNullException("script");
this.Script = script;
} }
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(RedisLiterals.LOAD);
physical.Write((RedisValue)Script);
}
}
sealed class HashScanResultProcessor : ScanResultProcessor<KeyValuePair<RedisValue, RedisValue>>
{
public static readonly ResultProcessor<ScanIterator<KeyValuePair<RedisValue, RedisValue>>.ScanResult> Default = new HashScanResultProcessor();
private HashScanResultProcessor() { }
protected override KeyValuePair<RedisValue, RedisValue>[] Parse(RawResult result)
{
KeyValuePair<RedisValue, RedisValue>[] pairs;
if (!ValuePairInterleaved.TryParse(result, out pairs)) pairs = null;
return pairs;
}
}
private abstract class ScanResultProcessor<T> : ResultProcessor<ScanIterator<T>.ScanResult>
{
protected abstract T[] Parse(RawResult result);
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItems();
long i64;
if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64))
{
var sscanResult = new ScanIterator<T>.ScanResult(i64, Parse(arr[1]));
SetResult(message, sscanResult);
return true;
}
break;
}
return false;
}
}
private sealed class ScriptEvalMessage : Message, IMultiMessage private sealed class ScriptEvalMessage : Message, IMultiMessage
{ {
private readonly RedisKey[] keys; private readonly RedisKey[] keys;
...@@ -1984,7 +1975,7 @@ public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) ...@@ -1984,7 +1975,7 @@ public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
public IEnumerable<Message> GetMessages(PhysicalConnection connection) public IEnumerable<Message> GetMessages(PhysicalConnection connection)
{ {
this.hash = connection.Bridge.ServerEndPoint.GetScriptHash(script); this.hash = connection.Bridge.ServerEndPoint.GetScriptHash(script);
if(hash.IsNull) if (hash.IsNull)
{ {
var msg = new ScriptLoadMessage(Flags, script); var msg = new ScriptLoadMessage(Flags, script);
msg.SetInternalCall(); msg.SetInternalCall();
...@@ -1996,7 +1987,7 @@ public IEnumerable<Message> GetMessages(PhysicalConnection connection) ...@@ -1996,7 +1987,7 @@ public IEnumerable<Message> GetMessages(PhysicalConnection connection)
internal override void WriteImpl(PhysicalConnection physical) internal override void WriteImpl(PhysicalConnection physical)
{ {
if(hash.IsNull) if (hash.IsNull)
{ {
physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length); physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length);
physical.Write((RedisValue)script); physical.Write((RedisValue)script);
...@@ -2013,6 +2004,16 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -2013,6 +2004,16 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(values[i]); physical.Write(values[i]);
} }
} }
sealed class SetScanResultProcessor : ScanResultProcessor<RedisValue>
{
public static readonly ResultProcessor<ScanIterator<RedisValue>.ScanResult> Default = new SetScanResultProcessor();
private SetScanResultProcessor() { }
protected override RedisValue[] Parse(RawResult result)
{
return result.GetItemsAsValues();
}
}
sealed class SortedSetCombineAndStoreCommandMessage : Message.CommandKeyBase // ZINTERSTORE and ZUNIONSTORE have a very unusual signature sealed class SortedSetCombineAndStoreCommandMessage : Message.CommandKeyBase // ZINTERSTORE and ZUNIONSTORE have a very unusual signature
{ {
private readonly RedisKey[] keys; private readonly RedisKey[] keys;
...@@ -2044,6 +2045,18 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -2044,6 +2045,18 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(values[i]); physical.Write(values[i]);
} }
} }
sealed class SortedSetScanResultProcessor : ScanResultProcessor<KeyValuePair<RedisValue, double>>
{
public static readonly ResultProcessor<ScanIterator<KeyValuePair<RedisValue, double>>.ScanResult> Default = new SortedSetScanResultProcessor();
private SortedSetScanResultProcessor() { }
protected override KeyValuePair<RedisValue, double>[] Parse(RawResult result)
{
KeyValuePair<RedisValue, double>[] pairs;
if (!SortedSetWithScores.TryParse(result, out pairs)) pairs = null;
return pairs;
}
}
private class StringGetWithExpiryMessage : Message.CommandKeyBase, IMultiMessage private class StringGetWithExpiryMessage : Message.CommandKeyBase, IMultiMessage
{ {
private readonly RedisCommand ttlCommand; private readonly RedisCommand ttlCommand;
......
...@@ -146,6 +146,11 @@ public override string ToString() ...@@ -146,6 +146,11 @@ public override string ToString()
return ((string)this) ?? "(null)"; return ((string)this) ?? "(null)";
} }
internal RedisValue AsRedisValue()
{
return value;
}
internal RedisKey Assert() internal RedisKey Assert()
{ {
if (IsNull) throw new ArgumentException("A null key is not valid in this context"); if (IsNull) throw new ArgumentException("A null key is not valid in this context");
...@@ -191,10 +196,5 @@ internal RedisKey Assert() ...@@ -191,10 +196,5 @@ internal RedisKey Assert()
return BitConverter.ToString(arr); return BitConverter.ToString(arr);
} }
} }
internal RedisValue AsRedisValue()
{
return value;
}
} }
} }
...@@ -64,10 +64,8 @@ public static readonly RedisValue ...@@ -64,10 +64,8 @@ public static readonly RedisValue
Wildcard = "*"; Wildcard = "*";
public static readonly byte[] BytesOK = Encoding.UTF8.GetBytes("OK"); public static readonly byte[] BytesOK = Encoding.UTF8.GetBytes("OK");
public static readonly byte[] ByteWildcard = { (byte)'*' };
public static readonly byte[] BytesPONG = Encoding.UTF8.GetBytes("PONG"); public static readonly byte[] BytesPONG = Encoding.UTF8.GetBytes("PONG");
public static readonly byte[] ByteWildcard = { (byte)'*' };
internal static RedisValue Get(Bitwise operation) internal static RedisValue Get(Bitwise operation)
{ {
switch(operation) switch(operation)
......
...@@ -159,12 +159,11 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r ...@@ -159,12 +159,11 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r
internal abstract RedisKey[] AsRedisKeyArray(); internal abstract RedisKey[] AsRedisKeyArray();
internal abstract RedisResult[] AsRedisResultArray();
internal abstract RedisValue AsRedisValue(); internal abstract RedisValue AsRedisValue();
internal abstract RedisValue[] AsRedisValueArray(); internal abstract RedisValue[] AsRedisValueArray();
internal abstract RedisResult[] AsRedisResultArray();
internal abstract string AsString(); internal abstract string AsString();
internal abstract string[] AsStringArray(); internal abstract string[] AsStringArray();
private sealed class ArrayRedisResult : RedisResult private sealed class ArrayRedisResult : RedisResult
...@@ -250,6 +249,8 @@ internal override RedisKey AsRedisKey() ...@@ -250,6 +249,8 @@ internal override RedisKey AsRedisKey()
internal override RedisKey[] AsRedisKeyArray() { return Array.ConvertAll(value, x => x.AsRedisKey()); } internal override RedisKey[] AsRedisKeyArray() { return Array.ConvertAll(value, x => x.AsRedisKey()); }
internal override RedisResult[] AsRedisResultArray() { return value; }
internal override RedisValue AsRedisValue() internal override RedisValue AsRedisValue()
{ {
if (value.Length == 1) return value[0].AsRedisValue(); if (value.Length == 1) return value[0].AsRedisValue();
...@@ -264,8 +265,6 @@ internal override string AsString() ...@@ -264,8 +265,6 @@ internal override string AsString()
throw new InvalidCastException(); throw new InvalidCastException();
} }
internal override string[] AsStringArray() { return Array.ConvertAll(value, x => x.AsString()); } internal override string[] AsStringArray() { return Array.ConvertAll(value, x => x.AsString()); }
internal override RedisResult[] AsRedisResultArray() { return value; }
} }
private sealed class ErrorRedisResult : RedisResult private sealed class ErrorRedisResult : RedisResult
...@@ -309,13 +308,14 @@ public ErrorRedisResult(string value) ...@@ -309,13 +308,14 @@ public ErrorRedisResult(string value)
internal override RedisKey[] AsRedisKeyArray() { throw new RedisServerException(value); } internal override RedisKey[] AsRedisKeyArray() { throw new RedisServerException(value); }
internal override RedisResult[] AsRedisResultArray() { throw new RedisServerException(value); }
internal override RedisValue AsRedisValue() { throw new RedisServerException(value); } internal override RedisValue AsRedisValue() { throw new RedisServerException(value); }
internal override RedisValue[] AsRedisValueArray() { throw new RedisServerException(value); } internal override RedisValue[] AsRedisValueArray() { throw new RedisServerException(value); }
internal override string AsString() { throw new RedisServerException(value); } internal override string AsString() { throw new RedisServerException(value); }
internal override string[] AsStringArray() { throw new RedisServerException(value); } internal override string[] AsStringArray() { throw new RedisServerException(value); }
internal override RedisResult[] AsRedisResultArray() { throw new RedisServerException(value); }
} }
private sealed class SingleRedisResult : RedisResult private sealed class SingleRedisResult : RedisResult
...@@ -358,13 +358,14 @@ public SingleRedisResult(RedisValue value) ...@@ -358,13 +358,14 @@ public SingleRedisResult(RedisValue value)
internal override RedisKey[] AsRedisKeyArray() { return new[] { AsRedisKey() }; } internal override RedisKey[] AsRedisKeyArray() { return new[] { AsRedisKey() }; }
internal override RedisResult[] AsRedisResultArray() { throw new InvalidCastException(); }
internal override RedisValue AsRedisValue() { return value; } internal override RedisValue AsRedisValue() { return value; }
internal override RedisValue[] AsRedisValueArray() { return new[] { AsRedisValue() }; } internal override RedisValue[] AsRedisValueArray() { return new[] { AsRedisValue() }; }
internal override string AsString() { return (string)value; } internal override string AsString() { return (string)value; }
internal override string[] AsStringArray() { return new[] { AsString() }; } internal override string[] AsStringArray() { return new[] { AsString() }; }
internal override RedisResult[] AsRedisResultArray() { throw new InvalidCastException(); }
} }
} }
} }
...@@ -7,119 +7,126 @@ ...@@ -7,119 +7,126 @@
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal sealed class RedisSubscriber : RedisBase, ISubscriber partial class ConnectionMultiplexer
{
internal RedisSubscriber(ConnectionMultiplexer multiplexer, object asyncState) : base(multiplexer, asyncState)
{ {
}
public override TimeSpan Ping(CommandFlags flags = CommandFlags.None) private readonly Dictionary<RedisChannel, Subscription> subscriptions = new Dictionary<RedisChannel, Subscription>();
{
// can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to...
RedisValue channel = Guid.NewGuid().ToByteArray();
var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel);
return ExecuteSync(msg, ResultProcessor.ResponseTimer);
}
public override Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None) internal static bool TryCompleteHandler<T>(EventHandler<T> handler, object sender, T args, bool isAsync) where T : EventArgs
{ {
// can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... if (handler == null) return true;
RedisValue channel = Guid.NewGuid().ToByteArray(); if (isAsync)
var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel);
return ExecuteAsync(msg, ResultProcessor.ResponseTimer);
}
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{ {
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel"); foreach (EventHandler<T> sub in handler.GetInvocationList())
var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{ {
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel"); try
var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message); { sub.Invoke(sender, args); }
return ExecuteAsync(msg, ResultProcessor.Int64); catch
{ }
} }
return true;
public Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
return multiplexer.AddSubscription(channel, handler, flags, asyncState);
} }
return false;
public Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
return multiplexer.RemoveSubscription(channel, handler, flags, asyncState);
} }
public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None) internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
{ {
return multiplexer.RemoveAllSubscriptions(flags, asyncState); if (handler != null)
}
public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
{ {
var task = SubscribeAsync(channel, handler, flags); lock (subscriptions)
if((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
public void Unsubscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{ {
var task = UnsubscribeAsync(channel, handler, flags); Subscription sub;
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task); if (subscriptions.TryGetValue(channel, out sub))
{
sub.Add(handler);
} }
public void UnsubscribeAll(CommandFlags flags = CommandFlags.None) else
{ {
var task = UnsubscribeAllAsync(flags); sub = new Subscription(handler);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task); subscriptions.Add(channel, sub);
var task = sub.SubscribeToServer(this, channel, flags, asyncState, false);
if (task != null) return task;
} }
public bool IsConnected(RedisChannel channel = default(RedisChannel)) }
{ }
return multiplexer.SubscriberConnected(channel); return CompletedTask<bool>.Default(asyncState);
} }
public EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None) internal ServerEndPoint GetSubscribedServer(RedisChannel channel)
{ {
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); if (!channel.IsNullOrEmpty)
msg.SetInternalCall(); {
return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
return sub.GetOwner();
}
}
}
return null;
} }
public Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) internal void OnMessage(RedisChannel subscription, RedisChannel channel, RedisValue payload)
{ {
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); ICompletable completable = null;
msg.SetInternalCall(); lock (subscriptions)
return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); {
Subscription sub;
if (subscriptions.TryGetValue(subscription, out sub))
{
completable = sub.ForInvoke(channel, payload);
}
}
if (completable != null) unprocessableCompletionManager.CompleteSyncOrAsync(completable);
} }
public EndPoint SubscribedEndpoint(RedisChannel channel) internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState)
{ {
var server = multiplexer.GetSubscribedServer(channel); Task last = CompletedTask<bool>.Default(asyncState);
return server == null ? null : server.EndPoint; lock (subscriptions)
{
foreach (var pair in subscriptions)
{
pair.Value.Remove(null); // always wipes
var task = pair.Value.UnsubscribeFromServer(pair.Key, flags, asyncState, false);
if (task != null) last = task;
} }
subscriptions.Clear();
}
return last;
} }
partial class ConnectionMultiplexer
{
internal ServerEndPoint GetSubscribedServer(RedisChannel channel) internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
{
if (!channel.IsNullOrEmpty)
{ {
lock (subscriptions) lock (subscriptions)
{ {
Subscription sub; Subscription sub;
if (subscriptions.TryGetValue(channel, out sub)) if (subscriptions.TryGetValue(channel, out sub))
{ {
return sub.GetOwner(); if (sub.Remove(handler))
{
subscriptions.Remove(channel);
var task = sub.UnsubscribeFromServer(channel, flags, asyncState, false);
if (task != null) return task;
} }
} }
} }
return null; return CompletedTask<bool>.Default(asyncState);
}
internal void ResendSubscriptions(ServerEndPoint server)
{
if (server == null) return;
lock (subscriptions)
{
foreach (var pair in subscriptions)
{
pair.Value.Resubscribe(pair.Key, server);
}
}
} }
internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel)) internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel))
...@@ -133,6 +140,19 @@ internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel)) ...@@ -133,6 +140,19 @@ internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel))
internal long ValidateSubscriptions()
{
lock (subscriptions)
{
long count = 0;
foreach (var pair in subscriptions)
{
if (pair.Value.Validate(this, pair.Key)) count++;
}
return count;
}
}
private sealed class Subscription private sealed class Subscription
{ {
private Action<RedisChannel, RedisValue> handler; private Action<RedisChannel, RedisValue> handler;
...@@ -146,6 +166,12 @@ public void Add(Action<RedisChannel, RedisValue> value) ...@@ -146,6 +166,12 @@ public void Add(Action<RedisChannel, RedisValue> value)
{ {
handler += value; handler += value;
} }
public ICompletable ForInvoke(RedisChannel channel, RedisValue message)
{
var tmp = handler;
return tmp == null ? null : new MessageCompletable(channel, message, tmp);
}
public bool Remove(Action<RedisChannel, RedisValue> value) public bool Remove(Action<RedisChannel, RedisValue> value)
{ {
if (value == null) if (value == null)
...@@ -158,10 +184,16 @@ public bool Remove(Action<RedisChannel, RedisValue> value) ...@@ -158,10 +184,16 @@ public bool Remove(Action<RedisChannel, RedisValue> value)
return (handler -= value) == null; return (handler -= value) == null;
} }
} }
public ICompletable ForInvoke(RedisChannel channel, RedisValue message) public Task SubscribeToServer(ConnectionMultiplexer multiplexer, RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall)
{ {
var tmp = handler; var cmd = channel.Contains((byte)'*') ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE;
return tmp == null ? null : new MessageCompletable(channel, message, tmp); var selected = multiplexer.SelectServer(-1, cmd, CommandFlags.DemandMaster, default(RedisKey));
if (selected == null || Interlocked.CompareExchange(ref owner, selected, null) != null) return null;
var msg = Message.Create(-1, flags, cmd, channel);
return selected.QueueDirectAsync(msg, ResultProcessor.TrackSubscriptions, asyncState);
} }
public Task UnsubscribeFromServer(RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall) public Task UnsubscribeFromServer(RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall)
...@@ -179,21 +211,9 @@ internal ServerEndPoint GetOwner() ...@@ -179,21 +211,9 @@ internal ServerEndPoint GetOwner()
{ {
return Interlocked.CompareExchange(ref owner, null, null); return Interlocked.CompareExchange(ref owner, null, null);
} }
public Task SubscribeToServer(ConnectionMultiplexer multiplexer, RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall)
{
var cmd = channel.Contains((byte)'*') ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE;
var selected = multiplexer.SelectServer(-1, cmd, CommandFlags.DemandMaster, default(RedisKey));
if (selected == null || Interlocked.CompareExchange(ref owner, selected, null) != null) return null;
var msg = Message.Create(-1, flags, cmd, channel);
return selected.QueueDirectAsync(msg, ResultProcessor.TrackSubscriptions, asyncState);
}
internal void Resubscribe(RedisChannel channel, ServerEndPoint server) internal void Resubscribe(RedisChannel channel, ServerEndPoint server)
{ {
if(server != null && Interlocked.CompareExchange(ref owner, server, server) == server) if (server != null && Interlocked.CompareExchange(ref owner, server, server) == server)
{ {
var cmd = channel.Contains((byte)'*') ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE; var cmd = channel.Contains((byte)'*') ? RedisCommand.PSUBSCRIBE : RedisCommand.SUBSCRIBE;
var msg = Message.Create(-1, CommandFlags.FireAndForget, cmd, channel); var msg = Message.Create(-1, CommandFlags.FireAndForget, cmd, channel);
...@@ -208,7 +228,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel) ...@@ -208,7 +228,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel)
var oldOwner = Interlocked.CompareExchange(ref owner, null, null); var oldOwner = Interlocked.CompareExchange(ref owner, null, null);
if (oldOwner != null && !oldOwner.IsSelectable(RedisCommand.PSUBSCRIBE)) if (oldOwner != null && !oldOwner.IsSelectable(RedisCommand.PSUBSCRIBE))
{ {
if(UnsubscribeFromServer(channel, CommandFlags.FireAndForget, null, true) != null) if (UnsubscribeFromServer(channel, CommandFlags.FireAndForget, null, true) != null)
{ {
changed = true; changed = true;
} }
...@@ -216,7 +236,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel) ...@@ -216,7 +236,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel)
} }
if (oldOwner == null) if (oldOwner == null)
{ {
if(SubscribeToServer(multiplexer, channel, CommandFlags.FireAndForget, null, true) != null) if (SubscribeToServer(multiplexer, channel, CommandFlags.FireAndForget, null, true) != null)
{ {
changed = true; changed = true;
} }
...@@ -226,119 +246,104 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel) ...@@ -226,119 +246,104 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel)
} }
}
internal void OnMessage(RedisChannel subscription, RedisChannel channel, RedisValue payload) internal sealed class RedisSubscriber : RedisBase, ISubscriber
{
ICompletable completable = null;
lock(subscriptions)
{ {
Subscription sub; internal RedisSubscriber(ConnectionMultiplexer multiplexer, object asyncState) : base(multiplexer, asyncState)
if(subscriptions.TryGetValue(subscription, out sub))
{ {
completable = sub.ForInvoke(channel, payload);
} }
}
if (completable != null) unprocessableCompletionManager.CompleteSyncOrAsync(completable); public EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None)
}
internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
{
if (handler != null)
{
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
sub.Add(handler);
} else
{ {
sub = new Subscription(handler); var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel);
subscriptions.Add(channel, sub); msg.SetInternalCall();
var task = sub.SubscribeToServer(this, channel, flags, asyncState, false); return ExecuteSync(msg, ResultProcessor.ConnectionIdentity);
if (task != null) return task;
} }
public Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel);
msg.SetInternalCall();
return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity);
} }
}
return CompletedTask<bool>.Default(asyncState);
}
private readonly Dictionary<RedisChannel, Subscription> subscriptions = new Dictionary<RedisChannel, Subscription>();
internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState) public bool IsConnected(RedisChannel channel = default(RedisChannel))
{ {
lock (subscriptions) return multiplexer.SubscriberConnected(channel);
}
public override TimeSpan Ping(CommandFlags flags = CommandFlags.None)
{ {
Subscription sub; // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to...
if(subscriptions.TryGetValue(channel, out sub)) RedisValue channel = Guid.NewGuid().ToByteArray();
var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel);
return ExecuteSync(msg, ResultProcessor.ResponseTimer);
}
public override Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
{ {
if (sub.Remove(handler)) // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to...
RedisValue channel = Guid.NewGuid().ToByteArray();
var msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel);
return ExecuteAsync(msg, ResultProcessor.ResponseTimer);
}
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{ {
subscriptions.Remove(channel); if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
var task = sub.UnsubscribeFromServer(channel, flags, asyncState, false); var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message);
if (task != null) return task; return ExecuteSync(msg, ResultProcessor.Int64);
} }
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message);
return ExecuteAsync(msg, ResultProcessor.Int64);
} }
public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
{
var task = SubscribeAsync(channel, handler, flags);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
} }
return CompletedTask<bool>.Default(asyncState);
public Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
return multiplexer.AddSubscription(channel, handler, flags, asyncState);
} }
internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState) public EndPoint SubscribedEndpoint(RedisChannel channel)
{
Task last = CompletedTask<bool>.Default(asyncState);
lock (subscriptions)
{
foreach(var pair in subscriptions)
{ {
pair.Value.Remove(null); // always wipes var server = multiplexer.GetSubscribedServer(channel);
var task = pair.Value.UnsubscribeFromServer(pair.Key, flags, asyncState, false); return server == null ? null : server.EndPoint;
if (task != null) last = task;
}
subscriptions.Clear();
}
return last;
} }
internal long ValidateSubscriptions() public void Unsubscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{
lock(subscriptions)
{
long count = 0;
foreach(var pair in subscriptions)
{ {
if (pair.Value.Validate(this, pair.Key)) count++; var task = UnsubscribeAsync(channel, handler, flags);
} if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
return count;
}
} }
internal void ResendSubscriptions(ServerEndPoint server)
{ public void UnsubscribeAll(CommandFlags flags = CommandFlags.None)
if (server == null) return;
lock(subscriptions)
{
foreach(var pair in subscriptions)
{ {
pair.Value.Resubscribe(pair.Key, server); var task = UnsubscribeAllAsync(flags);
} if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
} }
internal static bool TryCompleteHandler<T>(EventHandler<T> handler, object sender, T args, bool isAsync) where T : EventArgs public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None)
{
if (handler == null) return true;
if (isAsync)
{
foreach (EventHandler<T> sub in handler.GetInvocationList())
{ {
try return multiplexer.RemoveAllSubscriptions(flags, asyncState);
{ sub.Invoke(sender, args); }
catch
{ }
}
return true;
} }
return false;
public Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
return multiplexer.RemoveSubscription(channel, handler, flags, asyncState);
} }
} }
} }
...@@ -172,13 +172,6 @@ public TransactionMessage(int db, CommandFlags flags, List<ConditionResult> cond ...@@ -172,13 +172,6 @@ public TransactionMessage(int db, CommandFlags flags, List<ConditionResult> cond
this.conditions = (conditions == null || conditions.Count == 0) ? NixConditions : conditions.ToArray(); this.conditions = (conditions == null || conditions.Count == 0) ? NixConditions : conditions.ToArray();
} }
public override void AppendStormLog(StringBuilder sb)
{
base.AppendStormLog(sb);
if (conditions.Length != 0) sb.Append(", ").Append(conditions.Length).Append(" conditions");
sb.Append(", ").Append(operations.Length).Append(" operations");
}
public QueuedMessage[] InnerOperations { get { return operations; } } public QueuedMessage[] InnerOperations { get { return operations; } }
public bool IsAborted public bool IsAborted
...@@ -186,6 +179,12 @@ public bool IsAborted ...@@ -186,6 +179,12 @@ public bool IsAborted
get { return command != RedisCommand.EXEC; } get { return command != RedisCommand.EXEC; }
} }
public override void AppendStormLog(StringBuilder sb)
{
base.AppendStormLog(sb);
if (conditions.Length != 0) sb.Append(", ").Append(conditions.Length).Append(" conditions");
sb.Append(", ").Append(operations.Length).Append(" operations");
}
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{ {
int slot = ServerSelectionStrategy.NoSlot; int slot = ServerSelectionStrategy.NoSlot;
......
...@@ -20,32 +20,35 @@ abstract class ResultProcessor ...@@ -20,32 +20,35 @@ abstract class ResultProcessor
Tracer = new TracerProcessor(false), Tracer = new TracerProcessor(false),
EstablishConnection = new TracerProcessor(true); EstablishConnection = new TracerProcessor(true);
public static readonly ResultProcessor<double>
Double = new DoubleProcessor();
public static readonly ResultProcessor<double?>
NullableDouble = new NullableDoubleProcessor();
public static readonly ResultProcessor<byte[]> public static readonly ResultProcessor<byte[]>
ByteArray = new ByteArrayProcessor(), ByteArray = new ByteArrayProcessor(),
ScriptLoad = new ScriptLoadProcessor(); ScriptLoad = new ScriptLoadProcessor();
public static readonly ResultProcessor<ClusterConfiguration> public static readonly ResultProcessor<ClusterConfiguration>
ClusterNodes = new ClusterNodesProcessor(); ClusterNodes = new ClusterNodesProcessor();
public static readonly ResultProcessor<EndPoint> public static readonly ResultProcessor<EndPoint>
ConnectionIdentity = new ConnectionIdentityProcessor(); ConnectionIdentity = new ConnectionIdentityProcessor();
public static readonly ResultProcessor<DateTime> public static readonly ResultProcessor<DateTime>
DateTime = new DateTimeProcessor(); DateTime = new DateTimeProcessor();
public static readonly ResultProcessor<double>
Double = new DoubleProcessor();
public static readonly ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]> public static readonly ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]>
Info = new InfoProcessor(); Info = new InfoProcessor();
public static readonly ResultProcessor<long> public static readonly ResultProcessor<long>
Int64 = new Int64Processor(); Int64 = new Int64Processor();
public static readonly ResultProcessor<double?>
NullableDouble = new NullableDoubleProcessor();
public static readonly ResultProcessor<long?> public static readonly ResultProcessor<long?>
NullableInt64 = new NullableInt64Processor(); NullableInt64 = new NullableInt64Processor();
public static readonly ResultProcessor<RedisChannel[]>
RedisChannelArray = new RedisChannelArrayProcessor();
public static readonly ResultProcessor<RedisKey> public static readonly ResultProcessor<RedisKey>
RedisKey = new RedisKeyProcessor(); RedisKey = new RedisKeyProcessor();
...@@ -60,13 +63,15 @@ abstract class ResultProcessor ...@@ -60,13 +63,15 @@ abstract class ResultProcessor
public static readonly ResultProcessor<RedisValue[]> public static readonly ResultProcessor<RedisValue[]>
RedisValueArray = new RedisValueArrayProcessor(); RedisValueArray = new RedisValueArrayProcessor();
public static readonly ResultProcessor<RedisChannel[]>
RedisChannelArray = new RedisChannelArrayProcessor();
public static readonly ResultProcessor<TimeSpan> public static readonly ResultProcessor<TimeSpan>
ResponseTimer = new TimingProcessor(); ResponseTimer = new TimingProcessor();
public static readonly ResultProcessor<RedisResult>
ScriptResult = new ScriptResultProcessor();
public static readonly SortedSetWithScoresProcessor
SortedSetWithScores = new SortedSetWithScoresProcessor();
public static readonly ResultProcessor<string> public static readonly ResultProcessor<string>
String = new StringProcessor(), String = new StringProcessor(),
ClusterNodesRaw = new ClusterNodesRawProcessor(); ClusterNodesRaw = new ClusterNodesRawProcessor();
...@@ -77,13 +82,6 @@ public static readonly TimeSpanProcessor ...@@ -77,13 +82,6 @@ public static readonly TimeSpanProcessor
TimeSpanFromSeconds = new TimeSpanProcessor(false); TimeSpanFromSeconds = new TimeSpanProcessor(false);
public static readonly ValuePairInterleavedProcessor public static readonly ValuePairInterleavedProcessor
ValuePairInterleaved = new ValuePairInterleavedProcessor(); ValuePairInterleaved = new ValuePairInterleavedProcessor();
public static readonly SortedSetWithScoresProcessor
SortedSetWithScores = new SortedSetWithScoresProcessor();
public static readonly ResultProcessor<RedisResult>
ScriptResult = new ScriptResultProcessor();
static readonly byte[] MOVED = Encoding.UTF8.GetBytes("MOVED "), ASK = Encoding.UTF8.GetBytes("ASK "); static readonly byte[] MOVED = Encoding.UTF8.GetBytes("MOVED "), ASK = Encoding.UTF8.GetBytes("ASK ");
public void ConnectionFail(Message message, ConnectionFailureType fail, Exception innerException) public void ConnectionFail(Message message, ConnectionFailureType fail, Exception innerException)
...@@ -172,19 +170,47 @@ private void UnexpectedResponse(Message message, RawResult result) ...@@ -172,19 +170,47 @@ private void UnexpectedResponse(Message message, RawResult result)
ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message == null ? "n/a" : message.Command.ToString()) +": " + result.ToString()); ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message == null ? "n/a" : message.Command.ToString()) +": " + result.ToString());
} }
public sealed class TrackSubscriptionsProcessor : ResultProcessor<bool> public sealed class TimeSpanProcessor : ResultProcessor<TimeSpan?>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) private readonly bool isMilliseconds;
public TimeSpanProcessor(bool isMilliseconds)
{ {
if(result.Type == ResultType.MultiBulk) this.isMilliseconds = isMilliseconds;
}
public bool TryParse(RawResult result, out TimeSpan? expiry)
{ {
var items = result.GetItems(); switch (result.Type)
long count;
if (items.Length >= 3 && items[2].TryGetInt64(out count))
{ {
connection.SubscriptionCount = count; case ResultType.Integer:
long time;
if (result.TryGetInt64(out time))
{
if (time < 0)
{
expiry = null;
}
else if (isMilliseconds)
{
expiry = TimeSpan.FromMilliseconds(time);
}
else
{
expiry = TimeSpan.FromSeconds(time);
}
return true; return true;
} }
break;
}
expiry = null;
return false;
}
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
TimeSpan? expiry;
if (TryParse(result, out expiry))
{
SetResult(message, expiry);
return true;
} }
return false; return false;
} }
...@@ -247,6 +273,23 @@ internal override void WriteImpl(PhysicalConnection physical) ...@@ -247,6 +273,23 @@ internal override void WriteImpl(PhysicalConnection physical)
} }
} }
public sealed class TrackSubscriptionsProcessor : ResultProcessor<bool>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
if(result.Type == ResultType.MultiBulk)
{
var items = result.GetItems();
long count;
if (items.Length >= 3 && items[2].TryGetInt64(out count))
{
connection.SubscriptionCount = count;
return true;
}
}
return false;
}
}
internal sealed class DemandZeroOrOneProcessor : ResultProcessor<bool> internal sealed class DemandZeroOrOneProcessor : ResultProcessor<bool>
{ {
static readonly byte[] zero = { (byte)'0' }, one = { (byte)'1' }; static readonly byte[] zero = { (byte)'0' }, one = { (byte)'1' };
...@@ -277,6 +320,97 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -277,6 +320,97 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
} }
internal sealed class ScriptLoadProcessor : ResultProcessor<byte[]>
{
// note that top-level error messages still get handled by SetResult, but nested errors
// (is that a thing?) will be wrapped in the RedisResult
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.BulkString:
var hash = result.GetBlob();
var sl = message as RedisDatabase.ScriptLoadMessage;
if (sl != null)
{
connection.Bridge.ServerEndPoint.AddScript(sl.Script, hash);
}
SetResult(message, hash);
return true;
}
return false;
}
}
internal sealed class SortedSetWithScoresProcessor : ValuePairInterleavedProcessorBase<RedisValue, double>
{
protected override RedisValue ParseKey(RawResult key) { return key.AsRedisValue(); }
protected override double ParseValue(RawResult value)
{
double val;
return value.TryGetDouble(out val) ? val : double.NaN;
}
}
internal sealed class ValuePairInterleavedProcessor : ValuePairInterleavedProcessorBase<RedisValue, RedisValue>
{
protected override RedisValue ParseKey(RawResult key) { return key.AsRedisValue(); }
protected override RedisValue ParseValue(RawResult key) { return key.AsRedisValue(); }
}
internal abstract class ValuePairInterleavedProcessorBase<TKey, TValue> : ResultProcessor<KeyValuePair<TKey, TValue>[]>
{
static readonly KeyValuePair<TKey, TValue>[] nix = new KeyValuePair<TKey, TValue>[0];
public bool TryParse(RawResult result, out KeyValuePair<TKey, TValue>[] pairs)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItems();
if (arr == null)
{
pairs = null;
}
else
{
int count = arr.Length / 2;
if (count == 0)
{
pairs = nix;
}
else
{
pairs = new KeyValuePair<TKey, TValue>[count];
int offset = 0;
for (int i = 0; i < pairs.Length; i++)
{
var setting = ParseKey(arr[offset++]);
var value = ParseValue(arr[offset++]);
pairs[i] = new KeyValuePair<TKey, TValue>(setting, value);
}
}
}
return true;
default:
pairs = null;
return false;
}
}
protected abstract TKey ParseKey(RawResult key);
protected abstract TValue ParseValue(RawResult value);
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
KeyValuePair<TKey, TValue>[] arr;
if (TryParse(result, out arr))
{
SetResult(message, arr);
return true;
}
return false;
}
}
sealed class AutoConfigureProcessor : ResultProcessor<bool> sealed class AutoConfigureProcessor : ResultProcessor<bool>
{ {
static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY "); static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY ");
...@@ -426,58 +560,6 @@ static string Extract(string line, string prefix) ...@@ -426,58 +560,6 @@ static string Extract(string line, string prefix)
return null; return null;
} }
} }
sealed class DoubleProcessor : ResultProcessor<double>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.Integer:
long i64;
if (result.TryGetInt64(out i64))
{
SetResult(message, i64);
return true;
}
break;
case ResultType.SimpleString:
case ResultType.BulkString:
double val;
if (result.TryGetDouble(out val))
{
SetResult(message, val);
return true;
}
break;
}
return false;
}
}
sealed class NullableDoubleProcessor : ResultProcessor<double?>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.Integer:
case ResultType.SimpleString:
case ResultType.BulkString:
if(result.IsNull)
{
SetResult(message, null);
return true;
}
double val;
if (result.TryGetDouble(out val))
{
SetResult(message, val);
return true;
}
break;
}
return false;
}
}
sealed class BooleanProcessor : ResultProcessor<bool> sealed class BooleanProcessor : ResultProcessor<bool>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
...@@ -490,10 +572,11 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -490,10 +572,11 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (result.Type) switch (result.Type)
{ {
case ResultType.SimpleString: case ResultType.SimpleString:
if(result.Assert(RedisLiterals.BytesOK)) if (result.Assert(RedisLiterals.BytesOK))
{ {
SetResult(message, true); SetResult(message, true);
} else }
else
{ {
SetResult(message, result.GetBoolean()); SetResult(message, result.GetBoolean());
} }
...@@ -504,7 +587,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -504,7 +587,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return true; return true;
case ResultType.MultiBulk: case ResultType.MultiBulk:
var items = result.GetItems(); var items = result.GetItems();
if(items.Length == 1) if (items.Length == 1)
{ // treat an array of 1 like a single reply (for example, SCRIPT EXISTS) { // treat an array of 1 like a single reply (for example, SCRIPT EXISTS)
SetResult(message, items[0].GetBoolean()); SetResult(message, items[0].GetBoolean());
return true; return true;
...@@ -529,28 +612,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -529,28 +612,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
} }
internal sealed class ScriptLoadProcessor : ResultProcessor<byte[]>
{
// note that top-level error messages still get handled by SetResult, but nested errors
// (is that a thing?) will be wrapped in the RedisResult
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.BulkString:
var hash = result.GetBlob();
var sl = message as RedisDatabase.ScriptLoadMessage;
if (sl != null)
{
connection.Bridge.ServerEndPoint.AddScript(sl.Script, hash);
}
SetResult(message, hash);
return true;
}
return false;
}
}
sealed class ClusterNodesProcessor : ResultProcessor<ClusterConfiguration> sealed class ClusterNodesProcessor : ResultProcessor<ClusterConfiguration>
{ {
internal static ClusterConfiguration Parse(PhysicalConnection connection, string nodes) internal static ClusterConfiguration Parse(PhysicalConnection connection, string nodes)
...@@ -605,6 +666,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -605,6 +666,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return true; return true;
} }
} }
sealed class DateTimeProcessor : ResultProcessor<DateTime> sealed class DateTimeProcessor : ResultProcessor<DateTime>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
...@@ -613,7 +675,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -613,7 +675,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (result.Type) switch (result.Type)
{ {
case ResultType.Integer: case ResultType.Integer:
if(result.TryGetInt64(out unixTime)) if (result.TryGetInt64(out unixTime))
{ {
var time = RedisBase.UnixEpoch.AddSeconds(unixTime); var time = RedisBase.UnixEpoch.AddSeconds(unixTime);
SetResult(message, time); SetResult(message, time);
...@@ -622,7 +684,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -622,7 +684,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
break; break;
case ResultType.MultiBulk: case ResultType.MultiBulk:
var arr = result.GetItems(); var arr = result.GetItems();
switch(arr.Length) switch (arr.Length)
{ {
case 1: case 1:
if (arr[0].TryGetInt64(out unixTime)) if (arr[0].TryGetInt64(out unixTime))
...@@ -633,7 +695,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -633,7 +695,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
break; break;
case 2: case 2:
if(arr[0].TryGetInt64(out unixTime) && arr[1].TryGetInt64(out micros)) if (arr[0].TryGetInt64(out unixTime) && arr[1].TryGetInt64(out micros))
{ {
var time = RedisBase.UnixEpoch.AddSeconds(unixTime).AddTicks(micros * 10); // datetime ticks are 100ns var time = RedisBase.UnixEpoch.AddSeconds(unixTime).AddTicks(micros * 10); // datetime ticks are 100ns
SetResult(message, time); SetResult(message, time);
...@@ -647,7 +709,34 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -647,7 +709,34 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
} }
sealed class ExpectBasicStringProcessor : ResultProcessor<bool> sealed class DoubleProcessor : ResultProcessor<double>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.Integer:
long i64;
if (result.TryGetInt64(out i64))
{
SetResult(message, i64);
return true;
}
break;
case ResultType.SimpleString:
case ResultType.BulkString:
double val;
if (result.TryGetDouble(out val))
{
SetResult(message, val);
return true;
}
break;
}
return false;
}
}
sealed class ExpectBasicStringProcessor : ResultProcessor<bool>
{ {
private readonly byte[] expected; private readonly byte[] expected;
public ExpectBasicStringProcessor(string value) public ExpectBasicStringProcessor(string value)
...@@ -668,6 +757,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -668,6 +757,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
sealed class InfoProcessor : ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]> sealed class InfoProcessor : ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
...@@ -727,6 +817,32 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -727,6 +817,32 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
sealed class NullableDoubleProcessor : ResultProcessor<double?>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.Integer:
case ResultType.SimpleString:
case ResultType.BulkString:
if(result.IsNull)
{
SetResult(message, null);
return true;
}
double val;
if (result.TryGetDouble(out val))
{
SetResult(message, val);
return true;
}
break;
}
return false;
}
}
sealed class NullableInt64Processor : ResultProcessor<long?> sealed class NullableInt64Processor : ResultProcessor<long?>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
...@@ -753,6 +869,35 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -753,6 +869,35 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
} }
sealed class RedisChannelArrayProcessor : ResultProcessor<RedisChannel[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItems();
RedisChannel[] final;
if (arr.Length == 0)
{
final = RedisChannel.EmptyArray;
}
else
{
final = new RedisChannel[arr.Length];
byte[] channelPrefix = connection.ChannelPrefix;
for (int i = 0; i < final.Length; i++)
{
final[i] = result.AsRedisChannel(channelPrefix);
}
}
SetResult(message, final);
return true;
}
return false;
}
}
sealed class RedisKeyArrayProcessor : ResultProcessor<RedisKey[]> sealed class RedisKeyArrayProcessor : ResultProcessor<RedisKey[]>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
...@@ -817,50 +962,48 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -817,50 +962,48 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
sealed class RedisChannelArrayProcessor : ResultProcessor<RedisChannel[]> sealed class RedisValueProcessor : ResultProcessor<RedisValue>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
switch (result.Type) switch(result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItems();
RedisChannel[] final;
if (arr.Length == 0)
{
final = RedisChannel.EmptyArray;
}
else
{
final = new RedisChannel[arr.Length];
byte[] channelPrefix = connection.ChannelPrefix;
for (int i = 0; i < final.Length; i++)
{ {
final[i] = result.AsRedisChannel(channelPrefix); case ResultType.Integer:
} case ResultType.SimpleString:
} case ResultType.BulkString:
SetResult(message, final); SetResult(message, result.AsRedisValue());
return true; return true;
} }
return false; return false;
} }
} }
private class ScriptResultProcessor : ResultProcessor<RedisResult>
sealed class RedisValueProcessor : ResultProcessor<RedisValue>
{ {
static readonly byte[] NOSCRIPT = Encoding.UTF8.GetBytes("NOSCRIPT ");
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
{
if (result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT))
{ // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH")
connection.Bridge.ServerEndPoint.FlushScripts();
}
// and apply usual processing for the rest
return base.SetResult(connection, message, result);
}
// note that top-level error messages still get handled by SetResult, but nested errors
// (is that a thing?) will be wrapped in the RedisResult
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
switch(result.Type) var value = Redis.RedisResult.TryCreate(connection, result);
if (value != null)
{ {
case ResultType.Integer: SetResult(message, value);
case ResultType.SimpleString:
case ResultType.BulkString:
SetResult(message, result.AsRedisValue());
return true; return true;
} }
return false; return false;
} }
} }
sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase<string, string> sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase<string, string>
{ {
protected override string ParseKey(RawResult key) { return key.GetString(); } protected override string ParseKey(RawResult key) { return key.GetString(); }
...@@ -882,129 +1025,39 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -882,129 +1025,39 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
public sealed class TimeSpanProcessor : ResultProcessor<TimeSpan?> private class TracerProcessor : ResultProcessor<bool>
{
private readonly bool isMilliseconds;
public TimeSpanProcessor(bool isMilliseconds)
{
this.isMilliseconds = isMilliseconds;
}
public bool TryParse(RawResult result, out TimeSpan? expiry)
{
switch (result.Type)
{
case ResultType.Integer:
long time;
if (result.TryGetInt64(out time))
{
if (time < 0)
{
expiry = null;
}
else if (isMilliseconds)
{
expiry = TimeSpan.FromMilliseconds(time);
}
else
{
expiry = TimeSpan.FromSeconds(time);
}
return true;
}
break;
}
expiry = null;
return false;
}
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
TimeSpan? expiry;
if(TryParse(result, out expiry))
{ {
SetResult(message, expiry); static readonly byte[]
return true; authFail = Encoding.UTF8.GetBytes("ERR operation not permitted"),
} loading = Encoding.UTF8.GetBytes("LOADING ");
return false;
}
}
internal sealed class ValuePairInterleavedProcessor : ValuePairInterleavedProcessorBase<RedisValue, RedisValue> private readonly bool establishConnection;
{
protected override RedisValue ParseKey(RawResult key) { return key.AsRedisValue(); }
protected override RedisValue ParseValue(RawResult key) { return key.AsRedisValue(); }
}
internal sealed class SortedSetWithScoresProcessor : ValuePairInterleavedProcessorBase<RedisValue, double> public TracerProcessor(bool establishConnection)
{
protected override RedisValue ParseKey(RawResult key) { return key.AsRedisValue(); }
protected override double ParseValue(RawResult value)
{ {
double val; this.establishConnection = establishConnection;
return value.TryGetDouble(out val) ? val: double.NaN;
}
} }
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
internal abstract class ValuePairInterleavedProcessorBase<TKey, TValue> : ResultProcessor<KeyValuePair<TKey, TValue>[]>
{
static readonly KeyValuePair<TKey, TValue>[] nix = new KeyValuePair<TKey, TValue>[0];
public bool TryParse(RawResult result, out KeyValuePair<TKey, TValue>[] pairs)
{ {
switch (result.Type) var final = base.SetResult(connection, message, result);
if (result.IsError)
{ {
case ResultType.MultiBulk: if (result.Assert(authFail))
var arr = result.GetItems();
if (arr == null)
{ {
pairs = null; connection.RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure);
} }
else else if (result.AssertStarts(loading))
{
int count = arr.Length / 2;
if (count == 0)
{ {
pairs = nix; connection.RecordConnectionFailed(ConnectionFailureType.Loading);
} }
else else
{ {
pairs = new KeyValuePair<TKey, TValue>[count]; connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure);
int offset = 0;
for (int i = 0; i < pairs.Length; i++)
{
var setting = ParseKey(arr[offset++]);
var value = ParseValue(arr[offset++]);
pairs[i] = new KeyValuePair<TKey, TValue>(setting, value);
}
}
}
return true;
default:
pairs = null;
return false;
}
}
protected abstract TKey ParseKey(RawResult key);
protected abstract TValue ParseValue(RawResult value);
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
KeyValuePair<TKey, TValue>[] arr;
if(TryParse(result, out arr))
{
SetResult(message, arr);
return true;
} }
return false;
} }
return final;
} }
private class TracerProcessor : ResultProcessor<bool>
{
private readonly bool establishConnection;
public TracerProcessor(bool establishConnection)
{
this.establishConnection = establishConnection;
}
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
bool happy; bool happy;
...@@ -1038,56 +1091,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1038,56 +1091,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
static readonly byte[] expected = RedisLiterals.BytesPONG, authFail = Encoding.UTF8.GetBytes("ERR operation not permitted"),
loading = Encoding.UTF8.GetBytes("LOADING ");
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
{
var final = base.SetResult(connection, message, result);
if (result.IsError)
{
if (result.Assert(authFail))
{
connection.RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure);
}
else if (result.AssertStarts(loading))
{
connection.RecordConnectionFailed(ConnectionFailureType.Loading);
}
else
{
connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure);
}
}
return final;
}
}
private class ScriptResultProcessor : ResultProcessor<RedisResult>
{
static readonly byte[] NOSCRIPT = Encoding.UTF8.GetBytes("NOSCRIPT ");
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
{
if(result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT))
{ // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH")
connection.Bridge.ServerEndPoint.FlushScripts();
}
// and apply usual processing for the rest
return base.SetResult(connection, message, result);
}
// note that top-level error messages still get handled by SetResult, but nested errors
// (is that a thing?) will be wrapped in the RedisResult
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
var value = Redis.RedisResult.TryCreate(connection, result);
if(value != null)
{
SetResult(message, value);
return true;
}
return false;
}
} }
} }
internal abstract class ResultProcessor<T> : ResultProcessor internal abstract class ResultProcessor<T> : ResultProcessor
......
using System; using System.Net;
using System.Net;
using System.Text; using System.Text;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -27,17 +26,15 @@ internal ServerCounters(EndPoint endpoint) ...@@ -27,17 +26,15 @@ internal ServerCounters(EndPoint endpoint)
/// </summary> /// </summary>
public ConnectionCounters Interactive { get; private set; } public ConnectionCounters Interactive { get; private set; }
/// <summary>
/// Counters associated with the subscription (pub-sub) connection
/// </summary>
public ConnectionCounters Subscription { get; private set; }
/// <summary> /// <summary>
/// Counters associated with other ambient activity /// Counters associated with other ambient activity
/// </summary> /// </summary>
public ConnectionCounters Other { get; private set; } public ConnectionCounters Other { get; private set; }
/// <summary>
/// Counters associated with the subscription (pub-sub) connection
/// </summary>
public ConnectionCounters Subscription { get; private set; }
/// <summary> /// <summary>
/// Indicates the total number of outstanding items against this server /// Indicates the total number of outstanding items against this server
/// </summary> /// </summary>
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -30,6 +29,7 @@ internal sealed partial class ServerEndPoint : IDisposable ...@@ -30,6 +29,7 @@ internal sealed partial class ServerEndPoint : IDisposable
private readonly EndPoint endpoint; private readonly EndPoint endpoint;
private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal);
private readonly ConnectionMultiplexer multiplexer; private readonly ConnectionMultiplexer multiplexer;
private int databases, writeEverySeconds; private int databases, writeEverySeconds;
...@@ -86,8 +86,6 @@ public bool IsConnected ...@@ -86,8 +86,6 @@ public bool IsConnected
public bool IsSlave { get { return isSlave; } set { SetConfig(ref isSlave, value); } } public bool IsSlave { get { return isSlave; } set { SetConfig(ref isSlave, value); } }
public int WriteEverySeconds { get { return writeEverySeconds; } set { SetConfig(ref writeEverySeconds, value); } }
public long OperationCount public long OperationCount
{ {
get get
...@@ -109,6 +107,7 @@ public long OperationCount ...@@ -109,6 +107,7 @@ public long OperationCount
public Version Version { get { return version; } set { SetConfig(ref version, value); } } public Version Version { get { return version; } set { SetConfig(ref version, value); } }
public int WriteEverySeconds { get { return writeEverySeconds; } set { SetConfig(ref writeEverySeconds, value); } }
internal ConnectionMultiplexer Multiplexer { get { return multiplexer; } } internal ConnectionMultiplexer Multiplexer { get { return multiplexer; } }
public void ClearUnselectable(UnselectableFlags flags) public void ClearUnselectable(UnselectableFlags flags)
...@@ -160,6 +159,11 @@ public PhysicalBridge GetBridge(RedisCommand command, bool create = true) ...@@ -160,6 +159,11 @@ public PhysicalBridge GetBridge(RedisCommand command, bool create = true)
} }
} }
public RedisFeatures GetFeatures()
{
return new RedisFeatures(version);
}
public void SetClusterConfiguration(ClusterConfiguration configuration) public void SetClusterConfiguration(ClusterConfiguration configuration)
{ {
...@@ -219,6 +223,14 @@ internal void Activate(ConnectionType type) ...@@ -219,6 +223,14 @@ internal void Activate(ConnectionType type)
GetBridge(type, true); GetBridge(type, true);
} }
internal void AddScript(string script, byte[] hash)
{
lock (knownScripts)
{
knownScripts[script] = hash;
}
}
internal void AutoConfigure(PhysicalConnection connection) internal void AutoConfigure(PhysicalConnection connection)
{ {
if (serverType == ServerType.Twemproxy) if (serverType == ServerType.Twemproxy)
...@@ -299,6 +311,14 @@ internal Task Close() ...@@ -299,6 +311,14 @@ internal Task Close()
return result; return result;
} }
internal void FlushScripts()
{
lock (knownScripts)
{
knownScripts.Clear();
}
}
internal ServerCounters GetCounters() internal ServerCounters GetCounters()
{ {
var counters = new ServerCounters(endpoint); var counters = new ServerCounters(endpoint);
...@@ -309,6 +329,75 @@ internal ServerCounters GetCounters() ...@@ -309,6 +329,75 @@ internal ServerCounters GetCounters()
return counters; return counters;
} }
internal int GetOutstandingCount(RedisCommand command, out int inst, out int qu, out int qs, out int qc, out int wr, out int wq)
{
var bridge = GetBridge(command, false);
if (bridge == null)
{
return inst = qu = qs = qc = wr = wq = 0;
}
return bridge.GetOutstandingCount(out inst, out qu, out qs, out qc, out wr, out wq);
}
internal string GetProfile()
{
var sb = new StringBuilder();
sb.Append("Circular op-count snapshot; int:");
var tmp = interactive;
if (tmp != null) tmp.AppendProfile(sb);
sb.Append("; sub:");
tmp = subscription;
if (tmp != null) tmp.AppendProfile(sb);
return sb.ToString();
}
internal byte[] GetScriptHash(string script)
{
return (byte[])knownScripts[script];
}
internal string GetStormLog(RedisCommand command)
{
var bridge = GetBridge(command);
return bridge == null ? null : bridge.GetStormLog();
}
internal Message GetTracerMessage(bool assertIdentity)
{
// different configurations block certain commands, as can ad-hoc local configurations, so
// we'll do the best with what we have available.
// note that the muxer-ctor asserts that one of ECHO, PING, TIME of GET is available
// see also: TracerProcessor
var map = multiplexer.CommandMap;
Message msg;
const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.FireAndForget;
if (assertIdentity && map.IsAvailable(RedisCommand.ECHO))
{
msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId);
}
else if (map.IsAvailable(RedisCommand.PING))
{
msg = Message.Create(-1, flags, RedisCommand.PING);
}
else if (map.IsAvailable(RedisCommand.TIME))
{
msg = Message.Create(-1, flags, RedisCommand.TIME);
}
else if (!assertIdentity && map.IsAvailable(RedisCommand.ECHO))
{
// we'll use echo as a PING substitute if it is all we have (in preference to EXISTS)
msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId);
}
else
{
map.AssertAvailable(RedisCommand.EXISTS);
msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId);
}
msg.SetInternalCall();
return msg;
}
internal bool IsSelectable(RedisCommand command) internal bool IsSelectable(RedisCommand command)
{ {
var bridge = unselectableReasons == 0 ? GetBridge(command, false) : null; var bridge = unselectableReasons == 0 ? GetBridge(command, false) : null;
...@@ -391,6 +480,11 @@ internal void ReportNextFailure() ...@@ -391,6 +480,11 @@ internal void ReportNextFailure()
if (tmp != null) tmp.ReportNextFailure(); if (tmp != null) tmp.ReportNextFailure();
} }
internal Task<bool> SendTracer()
{
return QueueDirectAsync(GetTracerMessage(false), ResultProcessor.Tracer);
}
internal string Summary() internal string Summary()
{ {
var sb = new StringBuilder(Format.ToString(endpoint)) var sb = new StringBuilder(Format.ToString(endpoint))
...@@ -423,11 +517,6 @@ internal string Summary() ...@@ -423,11 +517,6 @@ internal string Summary()
} }
return sb.ToString(); return sb.ToString();
} }
public RedisFeatures GetFeatures()
{
return new RedisFeatures(version);
}
internal void WriteDirectOrQueueFireAndForget<T>(PhysicalConnection connection, Message message, ResultProcessor<T> processor) internal void WriteDirectOrQueueFireAndForget<T>(PhysicalConnection connection, Message message, ResultProcessor<T> processor)
{ {
if (message != null) if (message != null)
...@@ -521,94 +610,5 @@ private void SetConfig<T>(ref T field, T value, [CallerMemberName] string caller ...@@ -521,94 +610,5 @@ private void SetConfig<T>(ref T field, T value, [CallerMemberName] string caller
multiplexer.ReconfigureIfNeeded(endpoint, false, caller); multiplexer.ReconfigureIfNeeded(endpoint, false, caller);
} }
} }
internal Task<bool> SendTracer()
{
return QueueDirectAsync(GetTracerMessage(false), ResultProcessor.Tracer);
}
internal Message GetTracerMessage(bool assertIdentity)
{
// different configurations block certain commands, as can ad-hoc local configurations, so
// we'll do the best with what we have available.
// note that the muxer-ctor asserts that one of ECHO, PING, TIME of GET is available
// see also: TracerProcessor
var map = multiplexer.CommandMap;
Message msg;
const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.FireAndForget;
if (assertIdentity && map.IsAvailable(RedisCommand.ECHO))
{
msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId);
}
else if (map.IsAvailable(RedisCommand.PING))
{
msg = Message.Create(-1, flags, RedisCommand.PING);
}
else if (map.IsAvailable(RedisCommand.TIME))
{
msg = Message.Create(-1, flags, RedisCommand.TIME);
}
else if(!assertIdentity && map.IsAvailable(RedisCommand.ECHO))
{
// we'll use echo as a PING substitute if it is all we have (in preference to EXISTS)
msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)multiplexer.UniqueId);
}
else
{
map.AssertAvailable(RedisCommand.EXISTS);
msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId);
}
msg.SetInternalCall();
return msg;
}
internal int GetOutstandingCount(RedisCommand command, out int inst, out int qu, out int qs, out int qc, out int wr, out int wq)
{
var bridge = GetBridge(command, false);
if(bridge == null)
{
return inst = qu = qs = qc = wr = wq = 0;
}
return bridge.GetOutstandingCount(out inst, out qu, out qs, out qc, out wr, out wq);
}
internal string GetStormLog(RedisCommand command)
{
var bridge = GetBridge(command);
return bridge == null ? null : bridge.GetStormLog();
}
internal string GetProfile()
{
var sb = new StringBuilder();
sb.Append("Circular op-count snapshot; int:");
var tmp = interactive;
if (tmp != null) tmp.AppendProfile(sb);
sb.Append("; sub:");
tmp = subscription;
if (tmp != null) tmp.AppendProfile(sb);
return sb.ToString();
}
private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal);
internal byte[] GetScriptHash(string script)
{
return (byte[])knownScripts[script];
}
internal void AddScript(string script, byte[] hash)
{
lock(knownScripts)
{
knownScripts[script] = hash;
}
}
internal void FlushScripts()
{
lock(knownScripts)
{
knownScripts.Clear();
}
}
} }
} }
...@@ -6,6 +6,7 @@ namespace StackExchange.Redis ...@@ -6,6 +6,7 @@ namespace StackExchange.Redis
{ {
internal sealed class ServerSelectionStrategy internal sealed class ServerSelectionStrategy
{ {
public const int NoSlot = -1, MultipleSlots = -2;
private const int RedisClusterSlotCount = 16384; private const int RedisClusterSlotCount = 16384;
static readonly ushort[] crc16tab = static readonly ushort[] crc16tab =
{ {
...@@ -57,26 +58,6 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer) ...@@ -57,26 +58,6 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer)
public ServerType ServerType { get { return serverType; } set { serverType = value; } } public ServerType ServerType { get { return serverType; } set { serverType = value; } }
internal int TotalSlots { get { return RedisClusterSlotCount; } } internal int TotalSlots { get { return RedisClusterSlotCount; } }
public const int NoSlot = -1, MultipleSlots = -2;
internal int CombineSlot(int oldSlot, int newSlot)
{
if (oldSlot == MultipleSlots || newSlot == NoSlot) return oldSlot;
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
internal int CombineSlot(int oldSlot, RedisKey key)
{
byte[] blob = key.Value;
if (oldSlot == MultipleSlots || (blob = key.Value) == null) return oldSlot;
int newSlot = HashSlot(blob);
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
/// <summary> /// <summary>
/// Computes the hash-slot that would be used by the given key /// Computes the hash-slot that would be used by the given key
/// </summary> /// </summary>
...@@ -104,6 +85,7 @@ public unsafe int HashSlot(byte[] key) ...@@ -104,6 +85,7 @@ public unsafe int HashSlot(byte[] key)
} }
} }
} }
public ServerEndPoint Select(Message message) public ServerEndPoint Select(Message message)
{ {
if (message == null) throw new ArgumentNullException("message"); if (message == null) throw new ArgumentNullException("message");
...@@ -128,38 +110,6 @@ public ServerEndPoint Select(int db, RedisCommand command, RedisKey key, Command ...@@ -128,38 +110,6 @@ public ServerEndPoint Select(int db, RedisCommand command, RedisKey key, Command
return Select(slot, command, flags); return Select(slot, command, flags);
} }
private ServerEndPoint Select(int slot, RedisCommand command, CommandFlags flags)
{
flags = Message.GetMasterSlaveFlags(flags); // only intersted in master/slave preferences
ServerEndPoint[] arr;
if (slot == NoSlot || (arr = map) == null) return Any(command, flags);
ServerEndPoint endpoint = arr[slot], testing;
// but: ^^^ is the MASTER slots; if we want a slave, we need to do some thinking
if (endpoint != null)
{
switch (flags)
{
case CommandFlags.DemandSlave:
return FindSlave(endpoint, command) ?? Any(command, flags);
case CommandFlags.PreferSlave:
testing = FindSlave(endpoint, command);
if (testing != null) return testing;
break;
case CommandFlags.DemandMaster:
return FindMaster(endpoint, command) ?? Any(command, flags);
case CommandFlags.PreferMaster:
testing = FindMaster(endpoint, command);
if (testing != null) return testing;
break;
}
if (endpoint.IsSelectable(command)) return endpoint;
}
return Any(command, flags);
}
public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved) public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved)
{ {
try try
...@@ -225,6 +175,21 @@ public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isM ...@@ -225,6 +175,21 @@ public bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isM
} }
} }
internal int CombineSlot(int oldSlot, int newSlot)
{
if (oldSlot == MultipleSlots || newSlot == NoSlot) return oldSlot;
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
internal int CombineSlot(int oldSlot, RedisKey key)
{
byte[] blob = key.Value;
if (oldSlot == MultipleSlots || (blob = key.Value) == null) return oldSlot;
int newSlot = HashSlot(blob);
if (oldSlot == NoSlot) return newSlot;
return oldSlot == newSlot ? oldSlot : MultipleSlots;
}
internal int CountCoveredSlots() internal int CountCoveredSlots()
{ {
var arr = map; var arr = map;
...@@ -284,7 +249,7 @@ private ServerEndPoint FindSlave(ServerEndPoint endpoint, RedisCommand command) ...@@ -284,7 +249,7 @@ private ServerEndPoint FindSlave(ServerEndPoint endpoint, RedisCommand command)
private ServerEndPoint[] MapForMutation() private ServerEndPoint[] MapForMutation()
{ {
var arr = map; var arr = map;
if(arr == null) if (arr == null)
{ {
lock (this) lock (this)
{ {
...@@ -294,5 +259,37 @@ private ServerEndPoint[] MapForMutation() ...@@ -294,5 +259,37 @@ private ServerEndPoint[] MapForMutation()
} }
return arr; return arr;
} }
private ServerEndPoint Select(int slot, RedisCommand command, CommandFlags flags)
{
flags = Message.GetMasterSlaveFlags(flags); // only intersted in master/slave preferences
ServerEndPoint[] arr;
if (slot == NoSlot || (arr = map) == null) return Any(command, flags);
ServerEndPoint endpoint = arr[slot], testing;
// but: ^^^ is the MASTER slots; if we want a slave, we need to do some thinking
if (endpoint != null)
{
switch (flags)
{
case CommandFlags.DemandSlave:
return FindSlave(endpoint, command) ?? Any(command, flags);
case CommandFlags.PreferSlave:
testing = FindSlave(endpoint, command);
if (testing != null) return testing;
break;
case CommandFlags.DemandMaster:
return FindMaster(endpoint, command) ?? Any(command, flags);
case CommandFlags.PreferMaster:
testing = FindMaster(endpoint, command);
if (testing != null) return testing;
break;
}
if (endpoint.IsSelectable(command)) return endpoint;
}
return Any(command, flags);
}
} }
} }
#if MONO
namespace StackExchange.Redis
{
partial class SocketManager
{
internal const SocketMode DefaultSocketMode = SocketMode.Async;
partial void OnAddRead(System.Net.Sockets.Socket socket, ISocketCallback callback)
{
throw new System.NotSupportedException();
}
}
}
#endif
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
#if !MONO
namespace StackExchange.Redis
{
partial class SocketManager
{
internal const SocketMode DefaultSocketMode = SocketMode.Poll;
static readonly IntPtr[] EmptyPointers = new IntPtr[0];
static readonly WaitCallback HelpProcessItems = state =>
{
var qdsl = state as QueueDrainSyncLock;
if (qdsl != null && qdsl.Consume())
{
var mgr = qdsl.Manager;
mgr.ProcessItems();
qdsl.Pulse();
}
};
private static ParameterizedThreadStart read = state => ((SocketManager)state).Read();
readonly Queue<IntPtr> readQueue = new Queue<IntPtr>(), errorQueue = new Queue<IntPtr>();
private readonly Dictionary<IntPtr, SocketPair> socketLookup = new Dictionary<IntPtr, SocketPair>();
private int readerCount;
[DllImport("ws2_32.dll", SetLastError = true)]
internal static extern int select([In] int ignoredParameter, [In, Out] IntPtr[] readfds, [In, Out] IntPtr[] writefds, [In, Out] IntPtr[] exceptfds, [In] ref TimeValue timeout);
private static void ProcessItems(Dictionary<IntPtr, SocketPair> socketLookup, Queue<IntPtr> queue, CallbackOperation operation)
{
if (queue == null) return;
while (true)
{
// get the next item (note we could be competing with a worker here, hence lock)
IntPtr handle;
lock (queue)
{
if (queue.Count == 0) break;
handle = queue.Dequeue();
}
SocketPair pair;
lock (socketLookup)
{
if (!socketLookup.TryGetValue(handle, out pair)) continue;
}
var callback = pair.Callback;
if (callback != null)
{
try
{
switch (operation)
{
case CallbackOperation.Read: callback.Read(); break;
case CallbackOperation.Error: callback.Error(); break;
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
}
}
partial void OnAddRead(Socket socket, ISocketCallback callback)
{
if (socket == null) throw new ArgumentNullException("socket");
if (callback == null) throw new ArgumentNullException("callback");
lock (socketLookup)
{
if (isDisposed) throw new ObjectDisposedException(name);
var handle = socket.Handle;
if (handle == IntPtr.Zero) throw new ObjectDisposedException("socket");
socketLookup.Add(handle, new SocketPair(socket, callback));
if (socketLookup.Count == 1)
{
Monitor.PulseAll(socketLookup);
if (Interlocked.CompareExchange(ref readerCount, 0, 0) == 0)
StartReader();
}
}
}
partial void OnDispose()
{
lock (socketLookup)
{
isDisposed = true;
socketLookup.Clear();
Monitor.PulseAll(socketLookup);
}
}
partial void OnShutdown(Socket socket)
{
lock (socketLookup)
{
socketLookup.Remove(socket.Handle);
}
}
private void ProcessItems()
{
ProcessItems(socketLookup, readQueue, CallbackOperation.Read);
ProcessItems(socketLookup, errorQueue, CallbackOperation.Error);
}
private void Read()
{
bool weAreReader = false;
try
{
weAreReader = Interlocked.CompareExchange(ref readerCount, 1, 0) == 0;
if (weAreReader) ReadImpl();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Trace.WriteLine(ex);
}
finally
{
if (weAreReader) Interlocked.Exchange(ref readerCount, 0);
}
}
private void ReadImpl()
{
List<IntPtr> dead = null, active = new List<IntPtr>();
IntPtr[] readSockets = EmptyPointers, errorSockets = EmptyPointers;
long lastHeartbeat = Environment.TickCount;
SocketPair[] allSocketPairs = null;
while (true)
{
active.Clear();
if (dead != null) dead.Clear();
// this check is actually a pace-maker; sometimes the Timer callback stalls for
// extended periods of time, which can cause socket disconnect
long now = Environment.TickCount;
if (unchecked(now - lastHeartbeat) >= 15000)
{
lastHeartbeat = now;
lock (socketLookup)
{
if (allSocketPairs == null || allSocketPairs.Length != socketLookup.Count)
allSocketPairs = new SocketPair[socketLookup.Count];
socketLookup.Values.CopyTo(allSocketPairs, 0);
}
foreach (var pair in allSocketPairs)
{
var callback = pair.Callback;
if (callback != null) try { callback.OnHeartbeat(); } catch { }
}
}
lock (socketLookup)
{
if (isDisposed) return;
if (socketLookup.Count == 0)
{
// if empty, give it a few seconds chance before exiting
Monitor.Wait(socketLookup, TimeSpan.FromSeconds(20));
if (socketLookup.Count == 0) return; // nothing new came in, so exit
}
foreach (var pair in socketLookup)
{
var socket = pair.Value.Socket;
if (socket.Handle == pair.Key && socket.Connected)
if (pair.Value.Socket.Connected)
{
active.Add(pair.Key);
}
else
{
(dead ?? (dead = new List<IntPtr>())).Add(pair.Key);
}
}
if (dead != null && dead.Count != 0)
{
foreach (var socket in dead) socketLookup.Remove(socket);
}
}
int pollingSockets = active.Count;
if (pollingSockets == 0)
{
// nobody had actual sockets; just sleep
Thread.Sleep(10);
continue;
}
if (readSockets.Length < active.Count + 1)
{
ConnectionMultiplexer.TraceWithoutContext("Resizing socket array for " + active.Count + " sockets");
readSockets = new IntPtr[active.Count + 6]; // leave so space for growth
errorSockets = new IntPtr[active.Count + 6];
}
readSockets[0] = errorSockets[0] = (IntPtr)active.Count;
active.CopyTo(readSockets, 1);
active.CopyTo(errorSockets, 1);
int ready;
try
{
var timeout = new TimeValue(1000);
ready = select(0, readSockets, null, errorSockets, ref timeout);
if (ready <= 0)
{
continue; // -ve typically means a socket was disposed just before; just retry
}
ConnectionMultiplexer.TraceWithoutContext((int)readSockets[0] != 0, "Read sockets: " + (int)readSockets[0]);
ConnectionMultiplexer.TraceWithoutContext((int)errorSockets[0] != 0, "Error sockets: " + (int)errorSockets[0]);
}
catch (Exception ex)
{ // this typically means a socket was disposed just before; just retry
Trace.WriteLine(ex.Message);
continue;
}
int queueCount = (int)readSockets[0];
lock (readQueue)
{
for (int i = 1; i <= queueCount; i++)
{
readQueue.Enqueue(readSockets[i]);
}
}
queueCount = (int)errorSockets[0];
lock (errorQueue)
{
for (int i = 1; i <= queueCount; i++)
{
errorQueue.Enqueue(errorSockets[i]);
}
}
if (ready >= 5) // number of sockets we should attempt to process by ourself before asking for help
{
// seek help, work in parallel, then synchronize
var obj = new QueueDrainSyncLock(this);
lock (obj)
{
ThreadPool.QueueUserWorkItem(HelpProcessItems, obj);
ProcessItems();
if (!obj.Consume())
{ // then our worker arrived and picked up work; we need
// to let it finish; note that if it *didn't* get that far
// yet, the Consume() call will mean that it never tries
Monitor.Wait(obj);
}
}
}
else
{
// just do it ourself
ProcessItems();
}
}
}
private void StartReader()
{
var thread = new Thread(read, 32 * 1024); // don't need a huge stack
thread.Name = name + ":Read";
thread.IsBackground = true;
thread.Priority = ThreadPriority.AboveNormal; // time critical
thread.Start(this);
}
[StructLayout(LayoutKind.Sequential)]
internal struct TimeValue
{
public int Seconds;
public int Microseconds;
public TimeValue(int microSeconds)
{
Seconds = (int)(microSeconds / 1000000L);
Microseconds = (int)(microSeconds % 1000000L);
}
}
struct SocketPair
{
public readonly ISocketCallback Callback;
public readonly Socket Socket;
public SocketPair(Socket socket, ISocketCallback callback)
{
this.Socket = socket;
this.Callback = callback;
}
}
sealed class QueueDrainSyncLock
{
private readonly SocketManager manager;
private int workers;
public QueueDrainSyncLock(SocketManager manager)
{
this.manager = manager;
}
public SocketManager Manager { get { return manager; } }
internal bool Consume()
{
return Interlocked.CompareExchange(ref workers, 1, 0) == 0;
}
internal void Pulse()
{
lock (this)
{
Monitor.PulseAll(this);
}
}
}
}
}
#endif
\ No newline at end of file
...@@ -9,15 +9,75 @@ ...@@ -9,15 +9,75 @@
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal enum SocketMode
{
Abort,
Poll,
Async
}
/// <summary>
/// Allows callbacks from SocketManager as work is discovered
/// </summary>
internal interface ISocketCallback
{
/// <summary>
/// Indicates that a socket has connected
/// </summary>
SocketMode Connected(Stream stream);
/// <summary>
/// Indicates that the socket has signalled an error condition
/// </summary>
void Error();
void OnHeartbeat();
/// <summary>
/// Indicates that data is available on the socket, and that the consumer should read synchronously from the socket while there is data
/// </summary>
void Read();
/// <summary>
/// Indicates that we cannot know whether data is available, and that the consume should commence reading asynchronously
/// </summary>
void StartReading();
}
internal struct SocketToken
{
internal readonly Socket Socket;
public SocketToken(Socket socket)
{
this.Socket = socket;
}
public int Available { get { return Socket == null ? 0 : Socket.Available; } }
public bool HasValue { get { return Socket != null; } }
}
/// <summary> /// <summary>
/// A SocketManager monitors multiple sockets for availability of data; this is done using /// A SocketManager monitors multiple sockets for availability of data; this is done using
/// the Socket.Select API and a dedicated reader-thread, which allows for fast responses /// the Socket.Select API and a dedicated reader-thread, which allows for fast responses
/// even when the system is under ambient load. /// even when the system is under ambient load.
/// </summary> /// </summary>
public sealed class SocketManager : IDisposable public sealed partial class SocketManager : IDisposable
{
private static readonly ParameterizedThreadStart writeAllQueues = context =>
{ {
try { ((SocketManager)context).WriteAllQueues(); } catch { }
};
private static readonly WaitCallback writeOneQueue = context =>
{
try { ((SocketManager)context).WriteOneQueue(); } catch { }
};
private readonly string name; private readonly string name;
private readonly Queue<PhysicalBridge> writeQueue = new Queue<PhysicalBridge>();
bool isDisposed;
/// <summary> /// <summary>
/// Creates a new (optionally named) SocketManager instance /// Creates a new (optionally named) SocketManager instance
/// </summary> /// </summary>
...@@ -35,50 +95,36 @@ public SocketManager(string name = null) ...@@ -35,50 +95,36 @@ public SocketManager(string name = null)
dedicatedWriter.Start(this); // will self-exit when disposed dedicatedWriter.Start(this); // will self-exit when disposed
} }
private enum CallbackOperation
{
Read,
Error
}
/// <summary> /// <summary>
/// Gets the name of this SocketManager instance /// Gets the name of this SocketManager instance
/// </summary> /// </summary>
public string Name { get { return name; } } public string Name { get { return name; } }
bool isDisposed;
struct SocketPair
{
public readonly Socket Socket;
public readonly ISocketCallback Callback;
public SocketPair(Socket socket, ISocketCallback callback)
{
this.Socket = socket;
this.Callback = callback;
}
}
#if !MONO
/// <summary> /// <summary>
/// Adds a new socket and callback to the manager /// Releases all resources associated with this instance
/// </summary> /// </summary>
private void AddRead(Socket socket, ISocketCallback callback) public void Dispose()
{
if (socket == null) throw new ArgumentNullException("socket");
if (callback == null) throw new ArgumentNullException("callback");
lock (socketLookup)
{ {
if (isDisposed) throw new ObjectDisposedException(name); lock (writeQueue)
var handle = socket.Handle;
if(handle == IntPtr.Zero) throw new ObjectDisposedException("socket");
socketLookup.Add(handle, new SocketPair(socket, callback));
if (socketLookup.Count == 1)
{ {
Monitor.PulseAll(socketLookup); // make sure writer threads know to exit
if (Interlocked.CompareExchange(ref readerCount, 0, 0) == 0) isDisposed = true;
StartReader(); Monitor.PulseAll(writeQueue);
} }
OnDispose();
} }
internal SocketToken BeginConnect(EndPoint endpoint, ISocketCallback callback)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
socket.BeginConnect(endpoint, EndConnect, Tuple.Create(socket, callback));
return new SocketToken(socket);
} }
private readonly Dictionary<IntPtr, SocketPair> socketLookup = new Dictionary<IntPtr, SocketPair>();
#endif
internal void RequestWrite(PhysicalBridge bridge, bool forced) internal void RequestWrite(PhysicalBridge bridge, bool forced)
{ {
...@@ -99,336 +145,69 @@ internal void RequestWrite(PhysicalBridge bridge, bool forced) ...@@ -99,336 +145,69 @@ internal void RequestWrite(PhysicalBridge bridge, bool forced)
} }
} }
#if !MONO internal void Shutdown(SocketToken token)
private int readerCount;
private void StartReader()
{ {
var thread = new Thread(read, 32 * 1024); // don't need a huge stack Shutdown(token.Socket);
thread.Name = name + ":Read";
thread.IsBackground = true;
thread.Priority = ThreadPriority.AboveNormal; // time critical
thread.Start(this);
} }
private static ParameterizedThreadStart read = state => ((SocketManager)state).Read(); private void EndConnect(IAsyncResult ar)
private void Read()
{ {
bool weAreReader = false; Tuple<Socket, ISocketCallback> tuple = null;
try try
{ {
weAreReader = Interlocked.CompareExchange(ref readerCount, 1, 0) == 0; tuple = (Tuple<Socket, ISocketCallback>)ar.AsyncState;
if (weAreReader) ReadImpl(); var socket = tuple.Item1;
} var callback = tuple.Item2;
catch (Exception ex) socket.EndConnect(ar);
{ var netStream = new NetworkStream(socket, false);
Debug.WriteLine(ex); var socketMode = callback == null ? SocketMode.Abort : callback.Connected(netStream);
Trace.WriteLine(ex); switch (socketMode)
}
finally
{
if (weAreReader) Interlocked.Exchange(ref readerCount, 0);
}
}
readonly Queue<IntPtr> readQueue = new Queue<IntPtr>(), errorQueue = new Queue<IntPtr>();
static readonly IntPtr[] EmptyPointers = new IntPtr[0];
private void ReadImpl()
{
List<IntPtr> dead = null, active = new List<IntPtr>();
IntPtr[] readSockets = EmptyPointers, errorSockets = EmptyPointers;
long lastHeartbeat = Environment.TickCount;
SocketPair[] allSocketPairs = null;
while (true)
{
active.Clear();
if (dead != null) dead.Clear();
// this check is actually a pace-maker; sometimes the Timer callback stalls for
// extended periods of time, which can cause socket disconnect
long now = Environment.TickCount;
if (unchecked(now - lastHeartbeat) >= 15000)
{
lastHeartbeat = now;
lock(socketLookup)
{
if(allSocketPairs == null || allSocketPairs.Length != socketLookup.Count)
allSocketPairs = new SocketPair[socketLookup.Count];
socketLookup.Values.CopyTo(allSocketPairs, 0);
}
foreach(var pair in allSocketPairs)
{
var callback = pair.Callback;
if (callback != null) try { callback.OnHeartbeat(); } catch { }
}
}
lock (socketLookup)
{
if (isDisposed) return;
if (socketLookup.Count == 0)
{
// if empty, give it a few seconds chance before exiting
Monitor.Wait(socketLookup, TimeSpan.FromSeconds(20));
if (socketLookup.Count == 0) return; // nothing new came in, so exit
}
foreach (var pair in socketLookup)
{
var socket = pair.Value.Socket;
if(socket.Handle == pair.Key && socket.Connected)
if (pair.Value.Socket.Connected)
{
active.Add(pair.Key);
}
else
{
(dead ?? (dead = new List<IntPtr>())).Add(pair.Key);
}
}
if (dead != null && dead.Count != 0)
{ {
foreach (var socket in dead) socketLookup.Remove(socket); case SocketMode.Poll:
OnAddRead(socket, callback);
break;
case SocketMode.Async:
try
{ callback.StartReading(); }
catch
{ Shutdown(socket); }
break;
default:
Shutdown(socket);
break;
} }
} }
int pollingSockets = active.Count; catch
if (pollingSockets == 0)
{ {
// nobody had actual sockets; just sleep if (tuple != null)
Thread.Sleep(10);
continue;
}
if (readSockets.Length < active.Count + 1)
{ {
ConnectionMultiplexer.TraceWithoutContext("Resizing socket array for " + active.Count + " sockets");
readSockets = new IntPtr[active.Count + 6]; // leave so space for growth
errorSockets = new IntPtr[active.Count + 6];
}
readSockets[0] = errorSockets[0] = (IntPtr)active.Count;
active.CopyTo(readSockets, 1);
active.CopyTo(errorSockets, 1);
int ready;
try try
{ { tuple.Item2.Error(); }
var timeout = new TimeValue(1000);
ready = select(0, readSockets, null, errorSockets, ref timeout);
if (ready <= 0)
{
continue; // -ve typically means a socket was disposed just before; just retry
}
ConnectionMultiplexer.TraceWithoutContext((int)readSockets[0] != 0, "Read sockets: " + (int)readSockets[0]);
ConnectionMultiplexer.TraceWithoutContext((int)errorSockets[0] != 0, "Error sockets: " + (int)errorSockets[0]);
}
catch (Exception ex) catch (Exception ex)
{ // this typically means a socket was disposed just before; just retry
Trace.WriteLine(ex.Message);
continue;
}
int queueCount = (int)readSockets[0];
lock(readQueue)
{ {
for (int i = 1; i <= queueCount; i++) Trace.WriteLine(ex);
{
readQueue.Enqueue(readSockets[i]);
}
}
queueCount = (int)errorSockets[0];
lock (errorQueue)
{
for (int i = 1; i <= queueCount; i++)
{
errorQueue.Enqueue(errorSockets[i]);
}
}
if (ready >= 5) // number of sockets we should attempt to process by ourself before asking for help
{
// seek help, work in parallel, then synchronize
var obj = new QueueDrainSyncLock(this);
lock (obj)
{
ThreadPool.QueueUserWorkItem(HelpProcessItems, obj);
ProcessItems();
if (!obj.Consume())
{ // then our worker arrived and picked up work; we need
// to let it finish; note that if it *didn't* get that far
// yet, the Consume() call will mean that it never tries
Monitor.Wait(obj);
}
}
}
else
{
// just do it ourself
ProcessItems();
}
}
}
sealed class QueueDrainSyncLock
{
private int workers;
public QueueDrainSyncLock(SocketManager manager)
{
this.manager = manager;
}
private readonly SocketManager manager;
public SocketManager Manager { get { return manager; } }
internal bool Consume()
{
return Interlocked.CompareExchange(ref workers, 1, 0) == 0;
}
internal void Pulse()
{
lock (this)
{
Monitor.PulseAll(this);
}
} }
} }
[DllImport("ws2_32.dll", SetLastError = true)]
internal static extern int select([In] int ignoredParameter, [In, Out] IntPtr[] readfds, [In, Out] IntPtr[] writefds, [In, Out] IntPtr[] exceptfds, [In] ref TimeValue timeout);
[StructLayout(LayoutKind.Sequential)]
internal struct TimeValue
{
public int Seconds;
public int Microseconds;
public TimeValue(int microSeconds)
{
Seconds = (int)(microSeconds / 1000000L);
Microseconds = (int)(microSeconds % 1000000L);
} }
} }
static readonly WaitCallback HelpProcessItems = state =>
{
var qdsl = state as QueueDrainSyncLock;
if (qdsl != null && qdsl.Consume())
{
var mgr = qdsl.Manager;
mgr.ProcessItems();
qdsl.Pulse();
}
};
private void ProcessItems() /// <summary>
{ /// Adds a new socket and callback to the manager
ProcessItems(socketLookup, readQueue, CallbackOperation.Read); /// </summary>
ProcessItems(socketLookup, errorQueue, CallbackOperation.Error); partial void OnAddRead(Socket socket, ISocketCallback callback);
} partial void OnDispose();
partial void OnShutdown(Socket socket);
#endif
private void Shutdown(Socket socket) private void Shutdown(Socket socket)
{ {
if (socket != null) if (socket != null)
{ {
#if !MONO OnShutdown(socket);
lock (socketLookup)
{
socketLookup.Remove(socket.Handle);
}
#endif
try { socket.Shutdown(SocketShutdown.Both); } catch { } try { socket.Shutdown(SocketShutdown.Both); } catch { }
try { socket.Close(); } catch { } try { socket.Close(); } catch { }
try { socket.Dispose(); } catch { } try { socket.Dispose(); } catch { }
} }
} }
internal void Shutdown(SocketToken token)
{
Shutdown(token.Socket);
}
private static void ProcessItems(Dictionary<IntPtr, SocketPair> socketLookup, Queue<IntPtr> queue, CallbackOperation operation)
{
if (queue == null) return;
while (true)
{
// get the next item (note we could be competing with a worker here, hence lock)
IntPtr handle;
lock (queue)
{
if (queue.Count == 0) break;
handle = queue.Dequeue();
}
SocketPair pair;
lock (socketLookup)
{
if (!socketLookup.TryGetValue(handle, out pair)) continue;
}
var callback = pair.Callback;
if (callback != null)
{
#if VERBOSE
var watch = Stopwatch.StartNew();
#endif
try
{
switch (operation)
{
case CallbackOperation.Read: callback.Read(); break;
case CallbackOperation.Error: callback.Error(); break;
}
}
catch (Exception ex)
{
Trace.WriteLine(ex);
}
#if VERBOSE
watch.Stop();
ConnectionMultiplexer.TraceWithoutContext(string.Format("{0}: {1}ms on {2}", operation, watch.ElapsedMilliseconds, callback));
#endif
}
}
}
private enum CallbackOperation
{
Read,
Error
}
/// <summary>
/// Releases all resources associated with this instance
/// </summary>
public void Dispose()
{
lock (writeQueue)
{
// make sure writer threads know to exit
isDisposed = true;
Monitor.PulseAll(writeQueue);
}
#if !MONO
lock (socketLookup)
{
isDisposed = true;
socketLookup.Clear();
Monitor.PulseAll(socketLookup);
}
#endif
}
private readonly Queue<PhysicalBridge> writeQueue = new Queue<PhysicalBridge>();
private static readonly ParameterizedThreadStart writeAllQueues = context =>
{
try { ((SocketManager)context).WriteAllQueues(); } catch { }
};
private static readonly WaitCallback writeOneQueue = context =>
{
try { ((SocketManager)context).WriteOneQueue(); } catch { }
};
private void WriteAllQueues() private void WriteAllQueues()
{ {
...@@ -473,6 +252,7 @@ private void WriteAllQueues() ...@@ -473,6 +252,7 @@ private void WriteAllQueues()
} }
} }
} }
private void WriteOneQueue() private void WriteOneQueue()
{ {
PhysicalBridge bridge; PhysicalBridge bridge;
...@@ -505,96 +285,5 @@ private void WriteOneQueue() ...@@ -505,96 +285,5 @@ private void WriteOneQueue()
} }
} while (keepGoing); } while (keepGoing);
} }
internal SocketToken BeginConnect(EndPoint endpoint, ISocketCallback callback)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
socket.BeginConnect(endpoint, EndConnect, Tuple.Create(socket, callback));
return new SocketToken(socket);
}
private void EndConnect(IAsyncResult ar)
{
Tuple<Socket, ISocketCallback> tuple = null;
try
{
tuple = (Tuple<Socket, ISocketCallback>)ar.AsyncState;
var socket = tuple.Item1;
var callback = tuple.Item2;
socket.EndConnect(ar);
var netStream = new NetworkStream(socket, false);
var socketMode = callback == null ? SocketMode.Abort : callback.Connected(netStream);
switch (socketMode)
{
#if !MONO
case SocketMode.Poll:
AddRead(socket, callback);
break;
#endif
case SocketMode.Async:
try { callback.StartReading(); }
catch { Shutdown(socket); }
break;
default:
Shutdown(socket);
break;
}
}
catch
{
if (tuple != null)
{
try { tuple.Item2.Error(); }
catch (Exception ex)
{
Trace.WriteLine(ex);
}
}
}
}
}
/// <summary>
/// Allows callbacks from SocketManager as work is discovered
/// </summary>
internal interface ISocketCallback
{
/// <summary>
/// Indicates that a socket has connected
/// </summary>
SocketMode Connected(Stream stream);
/// <summary>
/// Indicates that data is available on the socket, and that the consumer should read synchronously from the socket while there is data
/// </summary>
void Read();
/// <summary>
/// Indicates that we cannot know whether data is available, and that the consume should commence reading asynchronously
/// </summary>
void StartReading();
/// <summary>
/// Indicates that the socket has signalled an error condition
/// </summary>
void Error();
void OnHeartbeat();
}
internal enum SocketMode
{
Abort,
#if !MONO
Poll,
#endif
Async
}
internal struct SocketToken
{
internal readonly Socket Socket;
public SocketToken(Socket socket)
{
this.Socket = socket;
}
public int Available { get { return Socket == null ? 0 : Socket.Available; } }
public bool HasValue { get { return Socket != null; } }
} }
} }
...@@ -81,6 +81,14 @@ static TaskSource() ...@@ -81,6 +81,14 @@ static TaskSource()
IsSyncSafe = t => false; // assume: not IsSyncSafe = t => false; // assume: not
} }
/// <summary>
/// Create a new TaskCompletion source
/// </summary>
public static TaskCompletionSource<T> Create<T>(object asyncState)
{
return new TaskCompletionSource<T>(asyncState);
}
/// <summary> /// <summary>
/// Create a new TaskCompletionSource that will not allow result-setting threads to be hijacked /// Create a new TaskCompletionSource that will not allow result-setting threads to be hijacked
/// </summary> /// </summary>
...@@ -90,12 +98,5 @@ public static TaskCompletionSource<T> CreateDenyExecSync<T>(object asyncState) ...@@ -90,12 +98,5 @@ public static TaskCompletionSource<T> CreateDenyExecSync<T>(object asyncState)
DenyExecSync(source.Task); DenyExecSync(source.Task);
return source; return source;
} }
/// <summary>
/// Create a new TaskCompletion source
/// </summary>
public static TaskCompletionSource<T> Create<T>(object asyncState)
{
return new TaskCompletionSource<T>(asyncState);
}
} }
} }
@rd /s /q StackExchange.Redis\bin\mono @rd /s /q StackExchange.Redis\bin\mono 1>nul 2>nul
@rd /s /q BasicTest\bin\mono @rd /s /q BasicTest\bin\mono 1>nul 2>nul
@md StackExchange.Redis\bin\mono @md StackExchange.Redis\bin\mono 1>nul 2>nul
@md BasicTest\bin\mono @md BasicTest\bin\mono 1>nul 2>nul
@echo Building StackExchange.Redis.dll ... @echo Building StackExchange.Redis.dll ...
@call mcs -recurse:StackExchange.Redis\*.cs -out:StackExchange.Redis\bin\mono\StackExchange.Redis.dll -target:library -unsafe+ -o+ -r:System.IO.Compression.dll -d:MONO @call mcs -recurse:StackExchange.Redis\*.cs -out:StackExchange.Redis\bin\mono\StackExchange.Redis.dll -target:library -unsafe+ -o+ -r:System.IO.Compression.dll -d:MONO
@echo Building BasicTest.exe ... @echo Building BasicTest.exe ...
......
@rd /s /q StackExchange.Redis\bin\Release 1>nul 2>nul
@rd /s /q BasicTest\bin\Release 1>nul 2>nul
@md StackExchange.Redis\bin\Release 1>nul 2>nul
@md BasicTest\bin\Release 1>nul 2>nul
@echo Building StackExchange.Redis.dll ...
@call csc /out:StackExchange.Redis\bin\Release\StackExchange.Redis.dll /target:library /unsafe+ /o+ /r:System.IO.Compression.dll /recurse:StackExchange.Redis\*.cs
@echo Building BasicTest.exe ...
@call csc /out:BasicTest\bin\Release\BasicTest.exe /target:exe -o+ /r:StackExchange.Redis\bin\Release\StackExchange.Redis.dll BasicTest\Program.cs
@copy StackExchange.Redis\bin\Release\*.* BasicTest\bin\Release > nul
@echo .
@echo Running basic test (.NET) ...
@call BasicTest\bin\Release\BasicTest.exe 100000
\ No newline at end of file
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