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)
{
try
{
((IRedisServerDebug)muxer.GetServer(pair.EndPoint)).SimulateConnectionFailure();
muxer.GetServer(pair.EndPoint).SimulateConnectionFailure();
} catch(Exception ex)
{
Log(ex.Message);
......
......@@ -24,7 +24,7 @@ public void AsyncTasksReportFailureIfServerUnavailable()
using(var conn = Create(allowAdmin: true))
{
var server = (IRedisServerDebug)conn.GetServer(PrimaryServer, PrimaryPort);
var server = conn.GetServer(PrimaryServer, PrimaryPort);
RedisKey key = Me();
var db = conn.GetDatabase();
......
......@@ -521,7 +521,7 @@ public void TestQuit(bool preserveOrder)
string key = Guid.NewGuid().ToString();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, key, flags: CommandFlags.FireAndForget);
((IRedisDebug)GetServer(muxer)).Quit(CommandFlags.FireAndForget);
GetServer(muxer).Quit(CommandFlags.FireAndForget);
var watch = Stopwatch.StartNew();
try
{
......@@ -550,7 +550,7 @@ public void TestSevered(bool preserveOrder)
string key = Guid.NewGuid().ToString();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, key, flags: CommandFlags.FireAndForget);
((IRedisServerDebug)GetServer(muxer)).SimulateConnectionFailure();
GetServer(muxer).SimulateConnectionFailure();
var watch = Stopwatch.StartNew();
db.Ping();
watch.Stop();
......
......@@ -146,19 +146,19 @@ public void IntentionalWrongServer()
#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");
var node = config.Nodes.FirstOrDefault(x => !x.IsSlave && x.NodeId != rightMasterNode.NodeId);
Assert.IsNotNull(node);
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");
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");
} catch (RedisServerException ex)
{
......@@ -170,7 +170,7 @@ public void IntentionalWrongServer()
Assert.IsNotNull(node);
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");
}
......@@ -178,12 +178,12 @@ public void IntentionalWrongServer()
Assert.IsNotNull(node);
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");
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");
}
catch (RedisServerException ex)
......
......@@ -116,7 +116,7 @@ public void ClientName()
var conn = muxer.GetDatabase();
conn.Ping();
#if DEBUG
var name = ((IRedisDebug)GetServer(muxer)).ClientGetName();
var name = GetServer(muxer).ClientGetName();
Assert.AreEqual("TestRig", name);
#endif
}
......
......@@ -41,7 +41,7 @@ public void ShutdownRaisesConnectionFailedAndRestore()
#if DEBUG
conn.AllowConnect = false;
var server = (IRedisServerDebug)conn.GetServer(PrimaryServer, PrimaryPort);
var server = conn.GetServer(PrimaryServer, PrimaryPort);
SetExpectedAmbientFailureCount(2);
server.SimulateConnectionFailure();
......
......@@ -355,7 +355,7 @@ public void SubscriptionsSurviveConnectionFailure()
Assert.AreEqual(1, server.GetCounters().Subscription.SocketCount, "sockets");
#if DEBUG
((IRedisServerDebug)server).SimulateConnectionFailure();
server.SimulateConnectionFailure();
SetExpectedAmbientFailureCount(2);
#endif
......
......@@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
monobuild.bash = monobuild.bash
monobuild.cmd = monobuild.cmd
netbuild.cmd = netbuild.cmd
StackExchange.Redis.nuspec = StackExchange.Redis.nuspec
EndProjectSection
EndProject
......
......@@ -136,6 +136,9 @@
<Compile Include="StackExchange\Redis\ServerType.cs" />
<Compile Include="StackExchange\Redis\SetOperation.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\StringSplits.cs" />
<Compile Include="StackExchange\Redis\TaskSource.cs" />
......@@ -143,6 +146,11 @@
<Compile Include="StackExchange\Redis\ShutdownMode.cs" />
<Compile Include="StackExchange\Redis\SaveType.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="StackExchange\Redis\SocketManager.Poll.cs">
<DependentUpon>SocketManager.cs</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
......
......@@ -16,32 +16,6 @@ public sealed class ClientInfo
/// </summary>
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>
/// total duration of the connection in seconds
/// </summary>
......@@ -73,6 +47,19 @@ public int Port
/// </summary>
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>
/// idle time of the connection in seconds
/// </summary>
......@@ -93,6 +80,18 @@ public int Port
/// </summary>
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>
/// The raw content from redis
/// </summary>
......
......@@ -41,6 +41,22 @@ private SlotRange(short from, short to)
/// </summary>
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>
/// Try to parse a string as a range
/// </summary>
......@@ -93,22 +109,6 @@ public override bool Equals(object obj)
}
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>
/// Indicates whether two ranges are equal
/// </summary>
......@@ -122,7 +122,8 @@ public bool Equals(SlotRange range)
/// </summary>
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>
......
......@@ -16,6 +16,7 @@ sealed partial class CompletionManager
private readonly string name;
int activeAsyncWorkerThread = 0;
long completedSync, completedAsync, failedAsync;
public CompletionManager(ConnectionMultiplexer multiplexer, string name)
{
......@@ -110,8 +111,6 @@ private static void ProcessAsyncCompletionQueue(object state)
}
partial void OnCompletedAsync();
int activeAsyncWorkerThread = 0;
private void ProcessAsyncCompletionQueueImpl()
{
int currentThread = Environment.CurrentManagedThreadId;
......
......@@ -12,8 +12,6 @@ public abstract class Condition
private Condition() { }
internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy);
/// <summary>
/// Enforces that the given hash-field must have the specified value
/// </summary>
......@@ -53,43 +51,44 @@ public static Condition HashNotExists(RedisKey key, RedisValue hashField)
}
/// <summary>
/// Enforces that the given key must have the specified value
/// Enforces that the given key must exist
/// </summary>
public static Condition StringEqual(RedisKey key, RedisValue value)
public static Condition KeyExists(RedisKey key)
{
if (value.IsNull) return KeyNotExists(key);
return new EqualsCondition(key, RedisValue.Null, true, value);
return new ExistsCondition(key, RedisValue.Null, true);
}
/// <summary>
/// Enforces that the given key must exist
/// Enforces that the given key must not exist
/// </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>
/// Enforces that the given key must not have the specified value
/// Enforces that the given key must have the specified value
/// </summary>
public static Condition StringNotEqual(RedisKey key, RedisValue value)
public static Condition StringEqual(RedisKey key, RedisValue value)
{
if (value.IsNull) return KeyExists(key);
return new EqualsCondition(key, RedisValue.Null, false, value);
if (value.IsNull) return KeyNotExists(key);
return new EqualsCondition(key, RedisValue.Null, true, value);
}
/// <summary>
/// Enforces that the given key must not exist
/// Enforces that the given key must not have the specified value
/// </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 IEnumerable<Message> CreateMessages(int db, ResultBox resultBox);
internal abstract int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy);
internal abstract bool TryValidate(RawResult result, out bool value);
internal sealed class ConditionProcessor : ResultProcessor<bool>
......@@ -159,15 +158,12 @@ public ExistsCondition(RedisKey key, RedisValue hashField, bool expectedResult)
this.expectedResult = expectedResult;
}
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
+ (expectedResult ? " exists" : " does not exists");
}
internal override void CheckCommands(CommandMap commandMap)
{
commandMap.AssertAvailable(hashField.IsNull ? RedisCommand.EXISTS : RedisCommand.HEXISTS);
......@@ -182,6 +178,11 @@ internal override IEnumerable<Message> CreateMessages(int db, ResultBox resultBo
message.SetSource(ConditionProcessor.Default, resultBox);
yield return message;
}
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
internal override bool TryValidate(RawResult result, out bool value)
{
bool parsed;
......@@ -210,11 +211,6 @@ public EqualsCondition(RedisKey key, RedisValue hashField, bool expectedEqual, R
this.expectedValue = expectedValue;
}
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
public override string ToString()
{
return (hashField.IsNull ? key.ToString() : key + " > " + hashField)
......@@ -236,6 +232,11 @@ internal sealed override IEnumerable<Message> CreateMessages(int db, ResultBox r
message.SetSource(ConditionProcessor.Default, resultBox);
yield return message;
}
internal override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(key);
}
internal override bool TryValidate(RawResult result, out bool value)
{
switch (result.Type)
......
......@@ -38,62 +38,53 @@ public sealed class ConfigurationOptions : ICloneable
ConfigChannelPrefix = "configChannel=", AbortOnConnectFailPrefix = "abortConnect=", ResolveDnsPrefix = "resolveDns=",
ChannelPrefixPrefix = "channelPrefix=", ProxyPrefix = "proxy=";
private readonly EndPointCollection endpoints = new EndPointCollection();
/// <summary>
/// Automatically encodes and decodes channels
/// </summary>
public RedisChannel ChannelPrefix { get;set; }
private bool? allowAdmin, abortOnConnectFail, resolveDns;
private Proxy? proxy;
private CommandMap commandMap;
private string clientName, serviceName, password, tieBreaker, sslHost, configChannel;
private Version defaultVersion;
private int? keepAlive, syncTimeout, connectTimeout, writeBuffer;
private readonly EndPointCollection endpoints = new EndPointCollection();
private bool? allowAdmin, abortOnConnectFail, resolveDns;
private string clientName, serviceName, password, tieBreaker, sslHost, configChannel;
private CommandMap commandMap;
private Version defaultVersion;
private int? keepAlive, syncTimeout, connectTimeout, writeBuffer;
private Proxy? proxy;
/// <summary>
/// A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication; note
/// that this cannot be specified in the configuration-string.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event LocalCertificateSelectionCallback CertificateSelection;
public event LocalCertificateSelectionCallback CertificateSelection;
/// <summary>
/// A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party; note
/// that this cannot be specified in the configuration-string.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
public event RemoteCertificateValidationCallback CertificateValidation;
/// <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; }
public event RemoteCertificateValidationCallback CertificateValidation;
/// <summary>
/// Indicates whether admin operations should be allowed
/// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException
/// </summary>
public Proxy Proxy { get { return proxy.GetValueOrDefault(); } set { proxy = value; } }
public bool AbortOnConnectFail { get { return abortOnConnectFail ?? true; } set { abortOnConnectFail = value; } }
/// <summary>
/// Indicates whether admin operations should be allowed
/// </summary>
public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } }
public bool AllowAdmin { get { return allowAdmin.GetValueOrDefault(); } set { allowAdmin = value; } }
/// <summary>
/// Indicates whether endpoints should be resolved via DNS before connecting
/// Automatically encodes and decodes channels
/// </summary>
public bool ResolveDns { get { return resolveDns.GetValueOrDefault(); } set { resolveDns = value; } }
public RedisChannel ChannelPrefix { get;set; }
/// <summary>
/// The client name to user for all connections
/// </summary>
public string ClientName { get { return clientName; } set { clientName = value; } }
public string ClientName { get { return clientName; } set { clientName = value; } }
/// <summary>
/// The command-map associated with this configuration
/// </summary>
......@@ -102,7 +93,7 @@ public CommandMap CommandMap
get
{
if (commandMap != null) return commandMap;
switch(Proxy)
switch (Proxy)
{
case Redis.Proxy.Twemproxy:
return CommandMap.Twemproxy;
......@@ -110,47 +101,63 @@ public CommandMap CommandMap
return CommandMap.Default;
}
}
set {
set
{
if (value == null) throw new ArgumentNullException("value");
commandMap = value;
}
}
}
/// <summary>
/// Channel to use for broadcasting and listening for configuration change notification
/// </summary>
public string ConfigurationChannel { get { return configChannel ?? DefaultConfigurationChannel; } set { configChannel = value; } }
public string ConfigurationChannel { get { return configChannel ?? DefaultConfigurationChannel; } set { configChannel = value; } }
/// <summary>
/// Specifies the time in milliseconds that should be allowed for connection
/// </summary>
public int ConnectTimeout { get { return connectTimeout ?? SyncTimeout; } set { connectTimeout = value; } }
public int ConnectTimeout { get { return connectTimeout ?? SyncTimeout; } set { connectTimeout = value; } }
/// <summary>
/// The server version to assume
/// </summary>
public Version DefaultVersion { get { return defaultVersion ?? RedisFeatures.v2_0_0; } set { defaultVersion = value; } }
public Version DefaultVersion { get { return defaultVersion ?? RedisFeatures.v2_0_0; } set { defaultVersion = value; } }
/// <summary>
/// The endpoints defined for this configuration
/// </summary>
public EndPointCollection EndPoints { get { return endpoints; } }
public EndPointCollection EndPoints { get { return endpoints; } }
/// <summary>
/// Specifies the time in seconds at which connections should be pinged to ensure validity
/// </summary>
public int KeepAlive { get { return keepAlive.GetValueOrDefault(-1); } set { keepAlive = value; } }
public int KeepAlive { get { return keepAlive.GetValueOrDefault(-1); } set { keepAlive = value; } }
/// <summary>
/// The password to use to authenticate with the server
/// </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>
/// The service name used to resolve a service via sentinel
/// </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>
/// The target-host to use when validating SSL certificate; setting a value here enables SSL mode
/// </summary>
......@@ -174,12 +181,6 @@ public CommandMap CommandMap
// 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; } }
/// <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>
/// Parse the configuration from a comma-delimited configuration string
/// </summary>
......
using System;
using System.Text;
using System.Text;
namespace StackExchange.Redis
{
......@@ -43,41 +42,15 @@ public bool IsEmpty
}
/// <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>
public long OperationCount { get; internal set; }
public long NonPreferredEndpointCount { get; internal set; }
/// <summary>
/// The number of subscriptions (with and without patterns) currently held against this connection
/// The number of operations performed on this connection
/// </summary>
public long Subscriptions { 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;
}
public long OperationCount { get; internal set; }
/// <summary>
/// Operations that have been requested, but which have not yet been sent to the server
......@@ -99,6 +72,11 @@ internal bool Any()
/// </summary>
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>
/// Indicates the total number of outstanding items against this connection
/// </summary>
......@@ -108,13 +86,6 @@ internal bool Any()
/// Indicates the total number of writers items against this connection
/// </summary>
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>
/// See Object.ToString()
......@@ -126,6 +97,31 @@ public override string 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)
{
sb.Append("ops=").Append(OperationCount).Append(", qu=").Append(PendingUnsentItems)
......
......@@ -9,8 +9,8 @@ namespace StackExchange.Redis
/// </summary>
public sealed class ConnectionFailedEventArgs : EventArgs, ICompletable
{
private readonly EndPoint endpoint;
private readonly ConnectionType connectionType;
private readonly EndPoint endpoint;
private readonly Exception exception;
private readonly ConnectionFailureType failureType;
private readonly EventHandler<ConnectionFailedEventArgs> handler;
......@@ -26,21 +26,20 @@ internal ConnectionFailedEventArgs(EventHandler<ConnectionFailedEventArgs> handl
}
/// <summary>
/// Gets the failing server-endpoint
/// Gets the connection-type of the failing connection
/// </summary>
public EndPoint EndPoint
public ConnectionType ConnectionType
{
get { return endpoint; }
get { return connectionType; }
}
/// <summary>
/// Gets the connection-type of the failing connection
/// Gets the failing server-endpoint
/// </summary>
public ConnectionType ConnectionType
public EndPoint EndPoint
{
get { return connectionType; }
get { return endpoint; }
}
/// <summary>
/// Gets the exception if available (this can be null)
/// </summary>
......@@ -56,16 +55,16 @@ public ConnectionFailureType FailureType
{
get { return failureType; }
}
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb)
{
sb.Append("event, connection-failed: ");
if (endpoint == null) sb.Append("n/a");
else sb.Append(Format.ToString(endpoint));
}
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
}
}
using System;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
namespace StackExchange.Redis
{
......@@ -25,10 +20,7 @@ public static long GetAllocationCount()
Interlocked.Increment(ref ResultBox.allocations);
}
}
/// <summary>
/// Additional IRedisServer methods for debugging
/// </summary>
public interface IRedisServerDebug : IServer
partial interface IServer
{
/// <summary>
/// Show what is in the pending (unsent) queue
......@@ -65,10 +57,7 @@ public interface IRedisServerDebug : IServer
/// <remarks>http://redis.io/commands/client-pause</remarks>
void Hang(TimeSpan duration, CommandFlags flags = CommandFlags.None);
}
/// <summary>
/// Additional IRedis methods for debugging
/// </summary>
public interface IRedisDebug : IRedis, IRedisDebugAsync
partial interface IRedis
{
/// <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.
......@@ -83,10 +72,8 @@ public interface IRedisDebug : IRedis, IRedisDebugAsync
/// <remarks>http://redis.io/commands/quit</remarks>
void Quit(CommandFlags flags = CommandFlags.None);
}
/// <summary>
/// Additional IRedisAsync methods for debugging
/// </summary>
public interface IRedisDebugAsync : IRedisAsync
partial interface IRedisAsync
{
/// <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.
......@@ -95,15 +82,15 @@ public interface IRedisDebugAsync : IRedisAsync
/// <returns>The connection name, or a null string if no name is set.</returns>
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);
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);
return ExecuteAsync(msg, ResultProcessor.String);
......@@ -130,23 +117,23 @@ internal string ListPending(int maxCount)
}
}
partial class RedisServer : IRedisServerDebug
partial class RedisServer
{
void IRedisServerDebug.SimulateConnectionFailure()
void IServer.SimulateConnectionFailure()
{
server.SimulateConnectionFailure();
}
string IRedisServerDebug.ListPending(int maxCount)
string IServer.ListPending(int maxCount)
{
return server.ListPending(maxCount);
}
void IRedisServerDebug.Crash()
void IServer.Crash()
{
// using DB-0 because we also use "DEBUG OBJECT", which is db-centric
var msg = Message.Create(0, CommandFlags.FireAndForget, RedisCommand.DEBUG, RedisLiterals.SEGFAULT);
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);
ExecuteSync(msg, ResultProcessor.DemandOK);
......
......@@ -9,13 +9,6 @@ namespace StackExchange.Redis
/// </summary>
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>
/// Format an endpoint
/// </summary>
......@@ -24,6 +17,13 @@ public static string ToString(EndPoint 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>
/// Adds a new endpoint to the list
/// </summary>
......
......@@ -26,10 +26,6 @@ public EndPoint EndPoint
{
get { return endpoint; }
}
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb)
{
sb.Append("event, endpoint: ");
......@@ -37,5 +33,9 @@ void ICompletable.AppendStormLog(StringBuilder sb)
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
{
internal static class ExceptionFactory
{
const string DataCommandKey = "redis-command",
DataServerKey = "redis-server";
internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
string s = GetLabel(includeDetail, command, message);
......@@ -11,42 +14,26 @@ internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand c
if (includeDetail) AddDetail(ex, message, server, s);
return ex;
}
static string GetLabel(bool includeDetail, RedisCommand command, Message message)
{
return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.Command.ToString());
}
internal static Exception NoConnectionAvailable(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
internal static Exception CommandDisabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
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);
return ex;
}
const string DataCommandKey = "redis-command",
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)
internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint server)
{
string s = GetLabel(includeDetail, command, message);
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);
var ex = new RedisConnectionException(failureType, message);
if (includeDetail) AddDetail(ex, null, server, null);
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");
if (includeDetail) AddDetail(ex, message, null, null);
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;
}
......@@ -65,14 +52,6 @@ internal static Exception DatabaseRequired(bool includeDetail, RedisCommand comm
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)
{
string s = GetLabel(includeDetail, command, message);
......@@ -81,17 +60,18 @@ internal static Exception MasterOnly(bool includeDetail, RedisCommand command, M
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);
if (includeDetail) AddDetail(ex, message, server, null);
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, null, null);
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);
if (includeDetail) AddDetail(ex, null, server, null);
string s = GetLabel(includeDetail, command, message);
var ex = new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, "No connection is available to service this operation: " + s);
if (includeDetail) AddDetail(ex, message, server, s);
return ex;
}
......@@ -102,5 +82,28 @@ internal static Exception NotSupported(bool includeDetail, RedisCommand command)
if (includeDetail) AddDetail(ex, null, null, s);
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
{
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)
{
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)
{
if (bool.TryParse(s, out value)) return true;
......@@ -32,39 +34,20 @@ public static bool TryParseBoolean(string s, out bool value)
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 true;
}
return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
return value.ToString(NumberFormatInfo.InvariantInfo);
}
internal static string ToString(double value)
......@@ -76,6 +59,7 @@ internal static string ToString(double value)
}
return value.ToString("G17", NumberFormatInfo.InvariantInfo);
}
internal static string ToString(object value)
{
return Convert.ToString(value, CultureInfo.InvariantCulture);
......@@ -119,11 +103,31 @@ internal static bool TryGetHostPort(EndPoint endpoint, out string host, out int
port = 0;
return false;
}
internal static EndPoint ParseEndPoint(string host, int port)
internal static bool TryParseDouble(string s, out double value)
{
IPAddress ip;
if (IPAddress.TryParse(host, out ip)) return new IPEndPoint(ip, port);
return new DnsEndPoint(host, port);
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 true;
}
return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out value);
}
internal static EndPoint TryParseEndPoint(string endpoint)
{
......
......@@ -4,7 +4,8 @@ namespace StackExchange.Redis
{
interface ICompletable
{
bool TryComplete(bool isAsync);
void AppendStormLog(StringBuilder sb);
bool TryComplete(bool isAsync);
}
}
......@@ -125,6 +125,13 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/hlen</remarks>
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>
/// 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>
......@@ -214,6 +221,13 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/persist</remarks>
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>
/// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist.
/// </summary>
......@@ -376,14 +390,6 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// Takes a lock (specifying a token value) if it is not already taken
/// </summary>
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>
/// Execute a Lua script against the server
/// </summary>
......@@ -511,21 +517,7 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// </summary>
/// <returns>yields all elements of the set.</returns>
/// <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);
/// <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);
IEnumerable<RedisValue> SetScan(RedisKey key, RedisValue pattern = default(RedisValue), int pageSize = RedisDatabase.ScanUtils.DefaultPageSize, CommandFlags flags = CommandFlags.None);
/// <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
......@@ -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>
/// <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);
/// <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.
/// 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
/// <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);
/// <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.
/// 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
/// <remarks>http://redis.io/commands/zremrangebyscore</remarks>
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>
/// 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>
......
......@@ -200,6 +200,13 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/persist</remarks>
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>
/// Renames key to newkey. It returns an error when the source and destination names are the same, or when key does not exist.
/// </summary>
......@@ -362,17 +369,6 @@ public interface IDatabaseAsync : IRedisAsync
/// Takes a lock (specifying a token value) if it is not already taken
/// </summary>
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>
/// Execute a Lua script against the server
/// </summary>
......
......@@ -6,7 +6,7 @@ namespace StackExchange.Redis
/// <summary>
/// Common operations available to all redis connections
/// </summary>
public interface IRedis : IRedisAsync
public partial interface IRedis : IRedisAsync
{
/// <summary>
/// This command is often used to test if a connection is still alive, or to measure latency.
......
......@@ -6,14 +6,24 @@ namespace StackExchange.Redis
/// <summary>
/// Common operations available to all redis connections
/// </summary>
public interface IRedisAsync
public partial interface IRedisAsync
{
/// <summary>
/// Gets the multiplexer that created this instance
/// </summary>
ConnectionMultiplexer Multiplexer { get; }
/// <summary>
/// This command is often used to test if a connection is still alive, or to measure latency.
/// </summary>
/// <returns>The observed latency.</returns>
/// <remarks>http://redis.io/commands/ping</remarks>
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>
/// Wait for a given asynchronous operation to complete (or timeout)
/// </summary>
......@@ -27,15 +37,5 @@ public interface IRedisAsync
/// </summary>
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
/// <summary>
/// Provides configuration controls of a redis server
/// </summary>
public interface IServer : IRedis
public partial interface IServer : IRedis
{
/// <summary>
/// Gets the cluster configuration associated with this server, if known
......
......@@ -10,6 +10,18 @@ namespace StackExchange.Redis
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>
/// Indicates whether the instance can communicate with the server;
/// if a channel is specified, the existing subscription map is queried to
......@@ -35,22 +47,21 @@ public interface ISubscriber : IRedis
/// </summary>
/// <remarks>http://redis.io/commands/subscribe</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>
/// Subscribe to perform some operation when a change to the preferred/active node is broadcast.
/// </summary>
/// <remarks>http://redis.io/commands/subscribe</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>
/// 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
Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None);
/// <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>
/// Inidicate to which redis server we are actively subscribed for a given channel; returns null if
/// the channel is not actively subscribed
/// 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>
[IgnoreNamePrefix]
EndPoint SubscribedEndpoint(RedisChannel channel);
/// <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);
}
}
\ No newline at end of file
......@@ -9,13 +9,12 @@ namespace StackExchange.Redis
/// </summary>
public class InternalErrorEventArgs : EventArgs, ICompletable
{
private readonly object sender;
private readonly ConnectionType connectionType;
private readonly EndPoint endpoint;
private readonly Exception exception;
private readonly EventHandler<InternalErrorEventArgs> handler;
private readonly string origin;
private readonly object sender;
internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, object sender, EndPoint endpoint, ConnectionType connectionType, Exception exception, string origin)
{
this.handler = handler;
......@@ -26,21 +25,20 @@ internal InternalErrorEventArgs(EventHandler<InternalErrorEventArgs> handler, ob
this.origin = origin;
}
/// <summary>
/// Gets the failing server-endpoint (this can be null)
/// Gets the connection-type of the failing connection
/// </summary>
public EndPoint EndPoint
public ConnectionType ConnectionType
{
get { return endpoint; }
get { return connectionType; }
}
/// <summary>
/// Gets the connection-type of the failing connection
/// Gets the failing server-endpoint (this can be null)
/// </summary>
public ConnectionType ConnectionType
public EndPoint EndPoint
{
get { return connectionType; }
get { return endpoint; }
}
/// <summary>
/// Gets the exception if available (this can be null)
/// </summary>
......@@ -56,15 +54,15 @@ public string Origin
{
get { return origin; }
}
bool ICompletable.TryComplete(bool isAsync)
{
return ConnectionMultiplexer.TryCompleteHandler(handler, sender, this, isAsync);
}
void ICompletable.AppendStormLog(StringBuilder sb)
{
sb.Append("event, internal-error: ").Append(origin);
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;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace StackExchange.Redis
namespace StackExchange.Redis
{
#if LOGOUTPUT
sealed class LoggingTextStream : Stream
......@@ -144,4 +138,4 @@ public override void Write(byte[] buffer, int offset, int count)
}
}
#endif
}
}
......@@ -63,17 +63,11 @@ abstract class Message : ICompletable
public static readonly Message[] EmptyArray = new Message[0];
public readonly int Db;
internal const CommandFlags InternalCallFlag = (CommandFlags)128;
protected RedisCommand command;
private const CommandFlags AskingFlag = (CommandFlags)32;
internal const CommandFlags InternalCallFlag = (CommandFlags)128;
public virtual void AppendStormLog(StringBuilder sb)
{
if (Db >= 0) sb.Append(Db).Append(':');
sb.Append(CommandAndKey);
}
const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave;
private const CommandFlags UserSelectableFlags
= CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave
......@@ -127,6 +121,8 @@ protected Message(int db, CommandFlags flags, RedisCommand command)
public RedisCommand Command { get { return command; } }
public virtual string CommandAndKey { get { return Command.ToString(); } }
public CommandFlags Flags { get { return flags; } }
/// <summary>
......@@ -183,12 +179,6 @@ public bool IsInternalCall
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)
{
if (command == RedisCommand.SELECT)
......@@ -205,10 +195,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
{
return new CommandKeyKeyMessage(db, flags, command, key0, key1);
}
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);
}
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);
......@@ -228,10 +220,12 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
{
return new CommandChannelMessage(db, flags, command, channel);
}
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisChannel channel, RedisValue value)
{
return new CommandChannelValueMessage(db, flags, command, channel, value);
}
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisValue value, RedisChannel channel)
{
return new CommandValueChannelMessage(db, flags, command, value, channel);
......@@ -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);
}
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)
{
switch (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 bool IsMasterOnly()
{
......@@ -435,19 +439,6 @@ internal static CommandFlags GetMasterSlaveFlags(CommandFlags flags)
// for the purposes of the switch, we only care about two bits
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)
{
switch (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()
{
if (resultProcessor != null) resultProcessor.SetException(this, new TaskCanceledException());
......@@ -520,6 +518,15 @@ internal void SetNoRedirect()
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)
{ // note order here reversed to prevent overload resolution errors
this.resultBox = resultBox;
......@@ -550,19 +557,20 @@ internal void WriteTo(PhysicalConnection physical)
Fail(ConnectionFailureType.InternalFailure, ex);
}
}
internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
internal abstract class CommandChannelBase : Message
{
// 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;
protected readonly RedisChannel Channel;
public CommandChannelBase(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command)
{
this.Channel = channel.Assert();
}
public override string CommandAndKey { get { return Command + " " + Channel; } }
}
internal abstract class CommandKeyBase : Message
{
public override string CommandAndKey { get { return Command + " " + Key; } }
protected readonly RedisKey Key;
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
this.Key = key.Assert();
}
public override string CommandAndKey { get { return Command + " " + Key; } }
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{
return serverSelectionStrategy.HashSlot(Key);
}
}
internal abstract class CommandChannelBase : Message
{
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
sealed class CommandChannelMessage : CommandChannelBase
{
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;
}
public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, RedisChannel channel) : base(db, flags, command, channel)
{ }
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 2);
physical.Write(Key);
physical.Write(key1);
physical.WriteHeader(Command, 1);
physical.Write(Channel);
}
}
sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage
sealed class CommandChannelValueMessage : CommandChannelBase
{
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();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, 3);
physical.Write(Key);
physical.Write(key1);
physical.WriteHeader(Command, 2);
physical.Write(Channel);
physical.Write(value);
}
}
sealed class CommandKeyKeyKeyMessage : CommandKeyBase
{
private readonly RedisKey key1, key2;
......@@ -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)
{
physical.WriteHeader(Command, 1);
physical.WriteHeader(Command, 2);
physical.Write(Key);
physical.Write(key1);
}
}
sealed class CommandKeyKeysMessage : CommandKeyBase
{
private readonly RedisKey[] keys;
......@@ -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
{
private readonly RedisKey[] keys;
......@@ -736,88 +759,50 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(value);
}
}
sealed class CommandChannelMessage : CommandChannelBase
{
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
sealed class CommandKeyValuesKeyMessage : CommandKeyBase
{
private readonly RedisKey key1;
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++)
{
values[i].Assert();
}
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)
{
physical.WriteHeader(Command, values.Length + 1);
physical.WriteHeader(Command, values.Length + 2);
physical.Write(Key);
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 RedisKey key1;
public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key0, RedisValue[] values, RedisKey key1) : base(db, flags, command, key0)
public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue[] values) : base(db, flags, command, key)
{
for (int i = 0; i < values.Length; i++)
{
values[i].Assert();
}
this.values = values;
this.key1 = key1.Assert();
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(Command, values.Length + 2);
physical.WriteHeader(Command, values.Length + 1);
physical.Write(Key);
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)
physical.Write(value1);
}
}
sealed class CommandKeyValueValueValueMessage : CommandKeyBase
{
private readonly RedisValue value0, value1, value2;
......@@ -855,6 +841,7 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(value2);
}
}
sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase
{
private readonly RedisValue value0, value1, value2, value3;
......@@ -878,26 +865,70 @@ internal override void WriteImpl(PhysicalConnection physical)
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)
{
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);
sb.Append(" (").Append((string)value).Append(')');
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]);
}
}
}
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)
{
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)
{
physical.WriteHeader(Command, 2);
......@@ -987,34 +1018,5 @@ internal override void WriteImpl(PhysicalConnection physical)
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;
namespace StackExchange.Redis
......@@ -10,6 +9,8 @@ sealed partial class MessageQueue
regular = new Queue<Message>(),
high = new Queue<Message>();
public object SyncLock { get { return regular; } }
public Message Dequeue()
{
lock (regular)
......@@ -26,23 +27,6 @@ public Message Dequeue()
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)
{
lock (regular)
......@@ -70,6 +54,14 @@ public bool Push(Message message)
}
}
internal bool Any()
{
lock (regular)
{
return high.Count != 0 || regular.Count != 0;
}
}
internal int Count()
{
lock (regular)
......@@ -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)
{
lock(regular)
......
......@@ -2,34 +2,52 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
namespace StackExchange.Redis
{
enum WriteResult
{
QueueEmpty,
MoreWork,
CompetingWriter,
NoConnection,
}
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
ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING);
ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING);
private readonly CompletionManager completionManager;
private readonly ConnectionType connectionType;
private readonly ConnectionMultiplexer multiplexer;
internal readonly string Name;
readonly long[] profileLog = new long[ProfileLogSamples];
private readonly MessageQueue queue = new MessageQueue();
private readonly ServerEndPoint serverEndPoint;
int activeWriters = 0;
internal int inWriteQueue = 0;
private int beating;
int failConnectCount = 0;
volatile bool isDisposed;
long nonPreferredEndpointCount;
//private volatile int missedHeartbeats;
private long operationCount, socketCount;
private volatile PhysicalConnection physical;
long profileLastLog;
int profileLogIndex;
volatile bool reportNextFailure = true, reconfigureNextFailure = false;
private volatile int state = (int)State.Disconnected;
public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type)
......@@ -63,12 +81,6 @@ public bool IsConnected
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
{
get
......@@ -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)
{
completionManager.CompleteSyncOrAsync(operation);
......@@ -136,23 +155,43 @@ public bool TryEnqueue(Message message, bool isSlave)
}
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 (clone[i] != clone[i - 1])
{
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferMaster)
Interlocked.Increment(ref nonPreferredEndpointCount);
sb.Append("+").Append(clone[i] - clone[i - 1]);
}
else
}
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)");
}
internal bool ConfirmRemoveFromWriteQueue()
{
lock (queue.SyncLock)
{
if (queue.Count() == 0)
{
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.PreferSlave)
Interlocked.Increment(ref nonPreferredEndpointCount);
Interlocked.Exchange(ref inWriteQueue, 0);
return true;
}
}
return false;
}
long nonPreferredEndpointCount;
internal void GetCounters(ConnectionCounters counters)
{
......@@ -174,13 +213,18 @@ internal int GetOutstandingCount(out int inst, out int qu, out int qs, out int q
inst = (int)(Interlocked.Read(ref operationCount) - Interlocked.Read(ref profileLastLog));
qu = queue.Count();
var tmp = physical;
qs = tmp == null ? 0 : tmp.GetSentAwaitingResponseCount();
qs = tmp == null ? 0 : tmp.GetSentAwaitingResponseCount();
qc = completionManager.GetOutstandingCount();
wr = Interlocked.CompareExchange(ref activeWriters, 0, 0);
wq = Interlocked.CompareExchange(ref inWriteQueue, 0, 0);
return qu + qs + qc;
}
internal int GetPendingCount()
{
return queue.Count();
}
internal string GetStormLog()
{
var sb = new StringBuilder("Storm log for ").Append(Format.ToString(serverEndPoint.EndPoint)).Append(" / ").Append(connectionType)
......@@ -195,35 +239,7 @@ internal string GetStormLog()
sb.AppendLine();
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()
{
Interlocked.Increment(ref operationCount);
......@@ -248,11 +264,11 @@ internal void KeepAlive()
}
break;
}
if(msg != null)
if (msg != null)
{
msg.SetInternalCall();
multiplexer.Trace("Enqueue: " + msg);
if(!TryEnqueue(msg, serverEndPoint.IsSlave))
if (!TryEnqueue(msg, serverEndPoint.IsSlave))
{
OnInternalError(ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, msg.Command, msg, serverEndPoint));
}
......@@ -268,9 +284,12 @@ internal void OnConnected(PhysicalConnection connection)
}
else
{
try {
try
{
connection.Dispose();
} catch { }
}
catch
{ }
}
}
......@@ -335,12 +354,7 @@ internal void OnFullyEstablished(PhysicalConnection connection)
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)
{
bool runThisTime = false;
......@@ -376,7 +390,8 @@ internal void OnHeartbeat(bool ifConnectedOnly)
State 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
// 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)
}
}
private void OnInternalError(Exception exception, [CallerMemberName] string origin = null)
{
multiplexer.OnInternalError(exception, serverEndPoint.EndPoint, connectionType, origin);
}
internal void RemovePhysical(PhysicalConnection connection)
{
#pragma warning disable 0420
......@@ -443,12 +453,12 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave)
if (isDisposed) throw new ObjectDisposedException(Name);
if(!IsConnected)
if (!IsConnected)
{
return 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
// other threads manage to interleave - in fact, it would be desirable
// (to avoid a batch monopolising the connection)
......@@ -456,26 +466,13 @@ internal bool TryEnqueue(List<Message> messages, bool isSlave)
LogNonPreferred(message.Flags, isSlave);
}
Trace("Now pending: " + GetPendingCount());
if(reqWrite) // was empty before
if (reqWrite) // was empty before
{
multiplexer.RequestWrite(this, false);
}
return true;
}
internal bool ConfirmRemoveFromWriteQueue()
{
lock(queue.SyncLock)
{
if(queue.Count() == 0)
{
Interlocked.Exchange(ref inWriteQueue, 0);
return true;
}
}
return false;
}
/// <summary>
/// This writes a message **directly** to the output stream; note
/// that this ignores the queue, so should only be used *either*
......@@ -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
var oldState = (State)Interlocked.Exchange(ref state, (int)newState);
#pragma warning restore 0420
if (oldState != newState)
bool weAreWriter = false;
PhysicalConnection conn = null;
try
{
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();
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()
......@@ -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)
{
#pragma warning disable 0420
......@@ -560,7 +632,7 @@ private void Flush()
Trace(connectionType + " flushed");
tmp.Flush();
}
catch(Exception ex)
catch (Exception ex)
{
OnInternalError(ex);
}
......@@ -595,8 +667,26 @@ private PhysicalConnection GetConnection()
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)
{
int db = message.Db;
......@@ -700,89 +790,5 @@ private bool WriteMessageToServer(PhysicalConnection connection, Message message
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
......@@ -25,17 +24,20 @@ internal enum WorkState
internal sealed partial class PhysicalConnection : IDisposable, ISocketCallback
{
void ISocketCallback.Error()
{
RecordConnectionFailed(ConnectionFailureType.SocketFailure);
}
internal readonly byte[] ChannelPrefix;
private const int DefaultRedisDatabaseCount = 16;
public long SubscriptionCount { get;set; }
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");
static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select(
......@@ -53,20 +55,24 @@ private static readonly Message
private readonly ConnectionMultiplexer multiplexer;
// things sent to this physical, but not yet received
private readonly Queue<Message> outstanding = new Queue<Message>();
readonly string physicalName;
volatile int currentDatabase = 0;
ReadMode currentReadMode = ReadMode.NotSpecified;
int failureReported;
byte[] ioBuffer = new byte[512];
int ioBufferBytes = 0;
private Stream netStream, outStream;
long lastWriteTickCount, lastReadTickCount, lastBeatTickCount;
// things sent to this physical, but not yet received
private readonly Queue<Message> outstanding = new Queue<Message>();
private Stream netStream, outStream;
private SocketToken socketToken;
......@@ -87,13 +93,6 @@ public PhysicalConnection(PhysicalBridge bridge)
//socket.SendTimeout = socket.ReceiveTimeout = multiplexer.TimeoutMilliseconds;
OnCreateEcho();
}
public long LastWriteSecondsAgo
{
get
{
return unchecked(Environment.TickCount - Interlocked.Read(ref lastWriteTickCount)) / 1000;
}
}
private enum ReadMode : byte
{
......@@ -104,10 +103,19 @@ private enum ReadMode : byte
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 bool TransactionActive { get; internal set; }
public long SubscriptionCount { get; set; }
public bool TransactionActive { get; internal set; }
public void Dispose()
{
......@@ -127,56 +135,51 @@ public void Dispose()
if (socketToken.HasValue)
{
var socketManager = multiplexer.SocketManager;
if(socketManager !=null) socketManager.Shutdown(socketToken);
if (socketManager != null) socketManager.Shutdown(socketToken);
socketToken = default(SocketToken);
multiplexer.Trace("Disconnected", physicalName);
RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed);
}
OnCloseEcho();
}
long lastWriteTickCount, lastReadTickCount, lastBeatTickCount;
public void Flush()
{
outStream.Flush();
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)
{
IdentifyFailureType(innerException, ref failureType);
if(failureType == ConnectionFailureType.InternalFailure) OnInternalError(innerException, origin);
if (failureType == ConnectionFailureType.InternalFailure) OnInternalError(innerException, origin);
// stop anything new coming in...
bridge.Trace("Failed: " + failureType);
bool isCurrent;
PhysicalBridge.State oldState;
bridge.OnDisconnected(failureType, this, out isCurrent, out oldState);
if (isCurrent && Interlocked.CompareExchange(ref failureReported, 1, 0) == 0)
{
//try
//{
long now = Environment.TickCount, lastRead = Interlocked.Read(ref lastReadTickCount), lastWrite = Interlocked.Read(ref lastWriteTickCount),
lastBeat = Interlocked.Read(ref lastBeatTickCount);
long now = Environment.TickCount, lastRead = Interlocked.Read(ref lastReadTickCount), lastWrite = Interlocked.Read(ref lastWriteTickCount),
lastBeat = Interlocked.Read(ref lastBeatTickCount);
string message = failureType + " on " + Format.ToString(bridge.ServerEndPoint.EndPoint) + "/" + connectionType
+ ", input-buffer: " + ioBufferBytes + ", outstanding: " + GetSentAwaitingResponseCount()
+ ", last-read: " + unchecked(now - lastRead) / 1000 + "s ago, last-write: " + unchecked(now - lastWrite) / 1000 + "s ago, keep-alive: " + bridge.ServerEndPoint.WriteEverySeconds + "s, pending: "
+ bridge.GetPendingCount() + ", state: " + oldState + ", last-heartbeat: " + (lastBeat == 0 ? "never" : (unchecked(now - lastBeat) / 1000 + "s ago"))
+ (bridge.IsBeating ? " (mid-beat)" : "") + ", last-mbeat: " + multiplexer.LastHeartbeatSecondsAgo + "s ago, global: "
+ ConnectionMultiplexer.LastGlobalHeartbeatSecondsAgo + "s ago";
string message = failureType + " on " + Format.ToString(bridge.ServerEndPoint.EndPoint) + "/" + connectionType
+ ", input-buffer: " + ioBufferBytes + ", outstanding: " + GetSentAwaitingResponseCount()
+ ", last-read: " + unchecked(now - lastRead) / 1000 + "s ago, last-write: " + unchecked(now - lastWrite) / 1000 + "s ago, keep-alive: " + bridge.ServerEndPoint.WriteEverySeconds + "s, pending: "
+ bridge.GetPendingCount() + ", state: " + oldState + ", last-heartbeat: " + (lastBeat == 0 ? "never" : (unchecked(now - lastBeat) / 1000 + "s ago"))
+ (bridge.IsBeating ? " (mid-beat)" : "") + ", last-mbeat: " + multiplexer.LastHeartbeatSecondsAgo + "s ago, global: "
+ ConnectionMultiplexer.LastGlobalHeartbeatSecondsAgo + "s ago";
var ex = innerException == null
? new RedisConnectionException(failureType, message)
: new RedisConnectionException(failureType, message, innerException);
var ex = innerException == null
? new RedisConnectionException(failureType, message)
: new RedisConnectionException(failureType, message, innerException);
bridge.OnConnectionFailed(this, failureType, ex);
bridge.OnConnectionFailed(this, failureType, ex);
// throw ex;
//}
//catch (Exception caught)
......@@ -201,7 +204,7 @@ public void RecordConnectionFailed(ConnectionFailureType failureType, Exception
// burn the socket
var socketManager = multiplexer.SocketManager;
if(socketManager != null) socketManager.Shutdown(socketToken);
if (socketManager != null) socketManager.Shutdown(socketToken);
}
public override string ToString()
......@@ -237,30 +240,6 @@ internal void GetCounters(ConnectionCounters counters)
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)
{
var serverEndpoint = bridge.ServerEndPoint;
......@@ -320,6 +299,40 @@ internal Message GetSelectDatabaseCommand(int targetDatabase, Message message)
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()
{ // forces next db-specific command to issue a select
currentDatabase = -1;
......@@ -329,6 +342,7 @@ internal void Write(RedisKey key)
{
WriteUnified(outStream, key.Value);
}
internal void Write(RedisChannel channel)
{
WriteUnified(outStream, ChannelPrefix, channel.Value);
......@@ -358,7 +372,6 @@ internal void WriteHeader(RedisCommand command, int arguments)
WriteUnified(outStream, commandBytes);
}
static void WriteRaw(Stream stream, long value, bool withLengthPrefix = false)
{
if (value >= 0 && value <= 9)
......@@ -443,6 +456,7 @@ static void WriteUnified(Stream stream, byte[] value)
stream.Write(Crlf, 0, 2);
}
}
static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{
stream.WriteByte((byte)'$');
......@@ -450,7 +464,7 @@ static void WriteUnified(Stream stream, byte[] prefix, byte[] value)
{
WriteRaw(stream, -1); // note that not many things like this...
}
else if(prefix == null)
else if (prefix == null)
{
WriteRaw(stream, value.Length);
stream.Write(value, 0, value.Length);
......@@ -473,15 +487,29 @@ static void WriteUnified(Stream stream, long value)
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)
{
try
{
#if MONO
var socketMode = SocketMode.Async;
#else
var socketMode = SocketMode.Poll;
#endif
var socketMode = SocketManager.DefaultSocketMode;
// disallow connection in some cases
OnDebugAbort();
......@@ -497,7 +525,7 @@ SocketMode ISocketCallback.Connected(Stream stream)
#endif
);
ssl.AuthenticateAsClient(config.SslHost);
if(!ssl.IsEncrypted)
if (!ssl.IsEncrypted)
{
RecordConnectionFailed(ConnectionFailureType.AuthenticationFailure);
multiplexer.Trace("Encryption failure");
......@@ -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 space = ioBuffer.Length - ioBufferBytes;
......@@ -534,7 +577,11 @@ int EnsureSpaceAndComputeBytesToRead()
}
return space;
}
internal readonly byte[] ChannelPrefix;
void ISocketCallback.Error()
{
RecordConnectionFailed(ConnectionFailureType.SocketFailure);
}
void MatchResult(RawResult result)
{
// check to see if it could be an out-of-band pubsub message
......@@ -597,16 +644,22 @@ void MatchResult(RawResult result)
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 OnCreateEcho();
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);
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);
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)
{
if (bytesRead <= 0)
......@@ -694,48 +713,26 @@ private bool ProcessReadBytes(int bytesRead)
return true;
}
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 bool EndReading(IAsyncResult result)
void ISocketCallback.Read()
{
try
{
var tmp = netStream;
int bytesRead = tmp == null ? 0 : tmp.EndRead(result);
return ProcessReadBytes(bytesRead);
} catch(Exception ex)
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);
return false;
}
}
void ISocketCallback.StartReading()
{
BeginReading();
}
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);
}
private RawResult ReadArray(byte[] buffer, ref int offset, ref int count)
{
var itemCount = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count);
......@@ -802,6 +799,10 @@ private RawResult ReadLineTerminatedString(ResultType type, byte[] buffer, ref i
return RawResult.Nil;
}
void ISocketCallback.StartReading()
{
BeginReading();
}
RawResult TryParseResult(byte[] buffer, ref int offset, ref int count)
{
if(count == 0) return RawResult.Nil;
......
......@@ -45,6 +45,8 @@ public RawResult(RawResult[] arr)
public bool IsError { get { return resultType == ResultType.Error; } }
public ResultType Type { get { return resultType; } }
internal bool IsNull { get { return arr == null; } }
public override string ToString()
{
if (arr == null)
......@@ -65,32 +67,20 @@ public override string ToString()
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)
{
switch(resultType)
switch (resultType)
{
case ResultType.SimpleString:
case ResultType.BulkString:
if(channelPrefix == null)
if (channelPrefix == null)
{
return (RedisChannel)GetBlob();
}
if(AssertStarts(channelPrefix))
if (AssertStarts(channelPrefix))
{
var src = (byte[])arr;
byte[] copy = new byte[count - channelPrefix.Length];
Buffer.BlockCopy(src, offset + channelPrefix.Length, copy, 0, copy.Length);
return (RedisChannel)copy;
......@@ -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()
{
switch (resultType)
......@@ -156,7 +157,6 @@ internal bool AssertStarts(byte[] expected)
}
return true;
}
internal bool IsNull { get { return arr == null; } }
internal byte[] GetBlob()
{
var src = (byte[])arr;
......@@ -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()
{
return (RawResult[])arr;
......@@ -251,19 +241,29 @@ internal string GetString()
internal bool TryGetDouble(out double val)
{
if(arr == null)
if (arr == null)
{
val = 0;
return false;
}
long i64;
if(TryGetInt64(out i64))
if (TryGetInt64(out i64))
{
val = i64;
return true;
}
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
internal readonly ConnectionMultiplexer multiplexer;
protected readonly object asyncState;
ConnectionMultiplexer IRedisAsync.Multiplexer { get { return multiplexer; } }
internal RedisBase(ConnectionMultiplexer multiplexer, object asyncState)
{
this.multiplexer = multiplexer;
this.asyncState = asyncState;
}
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);
}
ConnectionMultiplexer IRedisAsync.Multiplexer { get { return multiplexer; } }
public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None)
{
var msg = GetTimerMessage(flags);
......@@ -59,15 +45,16 @@ public override string ToString()
return multiplexer.ToString();
}
public void Wait(Task task)
{
multiplexer.Wait(task);
}
public bool TryWait(Task task)
{
return task.Wait(multiplexer.TimeoutMilliseconds);
}
public void Wait(Task task)
{
multiplexer.Wait(task);
}
public T Wait<T>(Task<T> task)
{
return multiplexer.Wait(task);
......@@ -126,7 +113,7 @@ protected void WhenAlwaysOrExistsOrNotExists(When when)
protected void WhenAlwaysOrNotExists(When when)
{
switch(when)
switch (when)
{
case When.Always:
case When.NotExists:
......@@ -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");
}
}
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
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>
/// Indicates whether the channel-name is either null or a zero-length value
/// </summary>
......@@ -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
{
get { return value == null; }
......@@ -178,12 +160,31 @@ public override string ToString()
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()
{
if (IsNull) throw new ArgumentException("A null key is not valid in this context");
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>
/// Create a channel name from a String
/// </summary>
......
......@@ -193,6 +193,15 @@ public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlag
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)
{
WhenAlwaysOrNotExists(when);
......@@ -362,6 +371,18 @@ public Task<bool> KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlag
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)
{
WhenAlwaysOrNotExists(when);
......@@ -660,19 +681,6 @@ public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry,
{
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)
{
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
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 (pattern.IsNull) return SetMembers(key, flags);
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)
{
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,
var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags);
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)
{
var msg = GetSortedSetLengthMessage(key, min, max, exclude, flags);
......@@ -1117,6 +1096,15 @@ public Task<long> SortedSetRemoveRangeByScoreAsync(RedisKey key, double start, d
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)
{
var msg = Message.Create(Db, flags, RedisCommand.ZSCORE, key, member);
......@@ -1625,6 +1613,7 @@ private Message GetSortedSetLengthMessage(RedisKey key, double min, double max,
var to = GetRange(max, exclude, false);
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)
{
// usage: {ZRANGEBYSCORE|ZREVRANGEBYSCORE} key from to [WITHSCORES] [LIMIT offset count]
......@@ -1668,6 +1657,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina
}
return Message.CreateInSlot(Db, slot, flags, RedisCommand.BITOP, values);
}
private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags)
{
// these ones are too bespoke to warrant custom Message implementations
......@@ -1683,6 +1673,7 @@ private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destina
slot = serverSelectionStrategy.CombineSlot(slot, second);
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)
{
if (this is IBatch)
......@@ -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);
}
}
Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None)
{
WhenAlwaysOrExistsOrNotExists(when);
......@@ -1757,6 +1749,7 @@ Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = n
}
throw new NotSupportedException();
}
Message IncrMessage(RedisKey key, long value, CommandFlags flags)
{
switch (value)
......@@ -1785,78 +1778,20 @@ private RedisCommand SetOperationCommand(SetOperation operation, bool store)
default: throw new ArgumentOutOfRangeException("operation");
}
}
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>
private IEnumerable<T> TryScan<T>(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags, RedisCommand command, ResultProcessor<ScanIterator<T>.ScanResult> processor)
{
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)
{
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;
}
}
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;
}
}
ServerEndPoint server;
var features = GetFeatures(Db, key, flags, out server);
if (!features.Scan) return null;
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 static bool IsNil(RedisValue pattern)
......@@ -1867,29 +1802,25 @@ public static bool IsNil(RedisValue pattern)
return rawValue.Length == 1 && rawValue[0] == '*';
}
}
internal class ScanIterator<T>
{
internal struct ScanResult
{
public readonly long Cursor;
public readonly T[] Values;
public ScanResult(long cursor, T[] values)
{
this.Cursor = cursor;
this.Values = values;
}
}
private readonly RedisCommand command;
private readonly RedisDatabase database;
private readonly CommandFlags flags;
private readonly RedisCommand command;
private readonly RedisKey key;
private readonly int pageSize;
private readonly RedisValue pattern;
private readonly ServerEndPoint server;
private readonly ResultProcessor<ScanResult> processor;
private readonly ServerEndPoint server;
public ScanIterator(RedisDatabase database, ServerEndPoint server, RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags,
RedisCommand command, ResultProcessor<ScanResult> processor)
{
......@@ -1902,6 +1833,7 @@ public ScanResult(long cursor, T[] values)
this.command = command;
this.processor = processor;
}
public IEnumerable<T> Read()
{
var msg = CreateMessage(0, false);
......@@ -1930,9 +1862,9 @@ public IEnumerable<T> Read()
Message CreateMessage(long cursor, bool running)
{
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);
}
......@@ -1943,7 +1875,7 @@ Message CreateMessage(long cursor, bool running)
}
else
{
if (pageSize == ScanIterator.DefaultPageSize)
if (pageSize == ScanUtils.DefaultPageSize)
{
return Message.Create(database.Database, flags, command, key, cursor, RedisLiterals.MATCH, pattern);
}
......@@ -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 readonly RedisKey[] keys;
......@@ -1984,7 +1975,7 @@ public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
public IEnumerable<Message> GetMessages(PhysicalConnection connection)
{
this.hash = connection.Bridge.ServerEndPoint.GetScriptHash(script);
if(hash.IsNull)
if (hash.IsNull)
{
var msg = new ScriptLoadMessage(Flags, script);
msg.SetInternalCall();
......@@ -1996,7 +1987,7 @@ public IEnumerable<Message> GetMessages(PhysicalConnection connection)
internal override void WriteImpl(PhysicalConnection physical)
{
if(hash.IsNull)
if (hash.IsNull)
{
physical.WriteHeader(RedisCommand.EVAL, 2 + keys.Length + values.Length);
physical.Write((RedisValue)script);
......@@ -2005,7 +1996,7 @@ internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(RedisCommand.EVALSHA, 2 + keys.Length + values.Length);
physical.Write(hash);
}
}
physical.Write(keys.Length);
for (int i = 0; i < keys.Length; i++)
physical.Write(keys[i]);
......@@ -2013,6 +2004,16 @@ internal override void WriteImpl(PhysicalConnection physical)
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
{
private readonly RedisKey[] keys;
......@@ -2044,6 +2045,18 @@ internal override void WriteImpl(PhysicalConnection physical)
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 readonly RedisCommand ttlCommand;
......
......@@ -146,6 +146,11 @@ public override string ToString()
return ((string)this) ?? "(null)";
}
internal RedisValue AsRedisValue()
{
return value;
}
internal RedisKey Assert()
{
if (IsNull) throw new ArgumentException("A null key is not valid in this context");
......@@ -191,10 +196,5 @@ internal RedisKey Assert()
return BitConverter.ToString(arr);
}
}
internal RedisValue AsRedisValue()
{
return value;
}
}
}
......@@ -64,10 +64,8 @@ public static readonly RedisValue
Wildcard = "*";
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[] ByteWildcard = { (byte)'*' };
internal static RedisValue Get(Bitwise operation)
{
switch(operation)
......
......@@ -159,12 +159,11 @@ internal static RedisResult TryCreate(PhysicalConnection connection, RawResult r
internal abstract RedisKey[] AsRedisKeyArray();
internal abstract RedisResult[] AsRedisResultArray();
internal abstract RedisValue AsRedisValue();
internal abstract RedisValue[] AsRedisValueArray();
internal abstract RedisResult[] AsRedisResultArray();
internal abstract string AsString();
internal abstract string[] AsStringArray();
private sealed class ArrayRedisResult : RedisResult
......@@ -250,6 +249,8 @@ internal override RedisKey AsRedisKey()
internal override RedisKey[] AsRedisKeyArray() { return Array.ConvertAll(value, x => x.AsRedisKey()); }
internal override RedisResult[] AsRedisResultArray() { return value; }
internal override RedisValue AsRedisValue()
{
if (value.Length == 1) return value[0].AsRedisValue();
......@@ -264,8 +265,6 @@ internal override string AsString()
throw new InvalidCastException();
}
internal override string[] AsStringArray() { return Array.ConvertAll(value, x => x.AsString()); }
internal override RedisResult[] AsRedisResultArray() { return value; }
}
private sealed class ErrorRedisResult : RedisResult
......@@ -309,13 +308,14 @@ public ErrorRedisResult(string 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[] AsRedisValueArray() { throw new RedisServerException(value); }
internal override string AsString() { 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
......@@ -358,13 +358,14 @@ public SingleRedisResult(RedisValue value)
internal override RedisKey[] AsRedisKeyArray() { return new[] { AsRedisKey() }; }
internal override RedisResult[] AsRedisResultArray() { throw new InvalidCastException(); }
internal override RedisValue AsRedisValue() { return value; }
internal override RedisValue[] AsRedisValueArray() { return new[] { AsRedisValue() }; }
internal override string AsString() { return (string)value; }
internal override string[] AsStringArray() { return new[] { AsString() }; }
internal override RedisResult[] AsRedisResultArray() { throw new InvalidCastException(); }
}
}
}
......@@ -7,119 +7,126 @@
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)
{
// 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)
{
// 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)
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
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");
var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
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);
}
private readonly Dictionary<RedisChannel, Subscription> subscriptions = new Dictionary<RedisChannel, Subscription>();
public Task UnsubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
internal static bool TryCompleteHandler<T>(EventHandler<T> handler, object sender, T args, bool isAsync) where T : EventArgs
{
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
return multiplexer.RemoveSubscription(channel, handler, flags, asyncState);
if (handler == null) return true;
if (isAsync)
{
foreach (EventHandler<T> sub in handler.GetInvocationList())
{
try
{ sub.Invoke(sender, args); }
catch
{ }
}
return true;
}
return false;
}
public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None)
{
return multiplexer.RemoveAllSubscriptions(flags, asyncState);
}
public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags = CommandFlags.None)
internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
{
var task = SubscribeAsync(channel, handler, flags);
if((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
if (handler != null)
{
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
sub.Add(handler);
}
else
{
sub = new Subscription(handler);
subscriptions.Add(channel, sub);
var task = sub.SubscribeToServer(this, channel, flags, asyncState, false);
if (task != null) return task;
}
public void Unsubscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{
var task = UnsubscribeAsync(channel, handler, flags);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
public void UnsubscribeAll(CommandFlags flags = CommandFlags.None)
{
var task = UnsubscribeAllAsync(flags);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
}
return CompletedTask<bool>.Default(asyncState);
}
public bool IsConnected(RedisChannel channel = default(RedisChannel))
internal ServerEndPoint GetSubscribedServer(RedisChannel channel)
{
return multiplexer.SubscriberConnected(channel);
if (!channel.IsNullOrEmpty)
{
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
return sub.GetOwner();
}
}
}
return null;
}
public EndPoint IdentifyEndpoint(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);
msg.SetInternalCall();
return ExecuteSync(msg, ResultProcessor.ConnectionIdentity);
ICompletable completable = null;
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(subscription, out sub))
{
completable = sub.ForInvoke(channel, payload);
}
}
if (completable != null) unprocessableCompletionManager.CompleteSyncOrAsync(completable);
}
public Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None)
internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState)
{
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel);
msg.SetInternalCall();
return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity);
Task last = CompletedTask<bool>.Default(asyncState);
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;
}
public EndPoint SubscribedEndpoint(RedisChannel channel)
internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
{
var server = multiplexer.GetSubscribedServer(channel);
return server == null ? null : server.EndPoint;
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
if (sub.Remove(handler))
{
subscriptions.Remove(channel);
var task = sub.UnsubscribeFromServer(channel, flags, asyncState, false);
if (task != null) return task;
}
}
}
return CompletedTask<bool>.Default(asyncState);
}
}
partial class ConnectionMultiplexer
{
internal ServerEndPoint GetSubscribedServer(RedisChannel channel)
internal void ResendSubscriptions(ServerEndPoint server)
{
if (!channel.IsNullOrEmpty)
if (server == null) return;
lock (subscriptions)
{
lock (subscriptions)
foreach (var pair in subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
return sub.GetOwner();
}
pair.Value.Resubscribe(pair.Key, server);
}
}
return null;
}
internal bool SubscriberConnected(RedisChannel channel = default(RedisChannel))
......@@ -133,11 +140,24 @@ 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 Action<RedisChannel, RedisValue> handler;
private ServerEndPoint owner;
public Subscription(Action<RedisChannel, RedisValue> value)
{
handler = value;
......@@ -146,6 +166,12 @@ public void Add(Action<RedisChannel, RedisValue> 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)
{
if (value == null)
......@@ -158,10 +184,16 @@ public bool Remove(Action<RedisChannel, RedisValue> value)
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;
return tmp == null ? null : new MessageCompletable(channel, message, tmp);
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);
}
public Task UnsubscribeFromServer(RedisChannel channel, CommandFlags flags, object asyncState, bool internalCall)
......@@ -179,21 +211,9 @@ internal ServerEndPoint GetOwner()
{
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)
{
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 msg = Message.Create(-1, CommandFlags.FireAndForget, cmd, channel);
......@@ -208,7 +228,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel)
var oldOwner = Interlocked.CompareExchange(ref owner, null, null);
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;
}
......@@ -216,7 +236,7 @@ internal bool Validate(ConnectionMultiplexer multiplexer, RedisChannel channel)
}
if (oldOwner == null)
{
if(SubscribeToServer(multiplexer, channel, CommandFlags.FireAndForget, null, true) != null)
if (SubscribeToServer(multiplexer, channel, CommandFlags.FireAndForget, null, true) != null)
{
changed = true;
}
......@@ -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
{
internal RedisSubscriber(ConnectionMultiplexer multiplexer, object asyncState) : base(multiplexer, asyncState)
{
ICompletable completable = null;
lock(subscriptions)
{
Subscription sub;
if(subscriptions.TryGetValue(subscription, out sub))
{
completable = sub.ForInvoke(channel, payload);
}
}
if (completable != null) unprocessableCompletionManager.CompleteSyncOrAsync(completable);
}
internal Task AddSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
public EndPoint IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None)
{
if (handler != null)
{
lock (subscriptions)
{
Subscription sub;
if (subscriptions.TryGetValue(channel, out sub))
{
sub.Add(handler);
} else
{
sub = new Subscription(handler);
subscriptions.Add(channel, sub);
var task = sub.SubscribeToServer(this, channel, flags, asyncState, false);
if (task != null) return task;
}
}
}
return CompletedTask<bool>.Default(asyncState);
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel);
msg.SetInternalCall();
return ExecuteSync(msg, ResultProcessor.ConnectionIdentity);
}
private readonly Dictionary<RedisChannel, Subscription> subscriptions = new Dictionary<RedisChannel, Subscription>();
internal Task RemoveSubscription(RedisChannel channel, Action<RedisChannel, RedisValue> handler, CommandFlags flags, object asyncState)
public Task<EndPoint> IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None)
{
lock (subscriptions)
{
Subscription sub;
if(subscriptions.TryGetValue(channel, out sub))
{
if (sub.Remove(handler))
{
subscriptions.Remove(channel);
var task = sub.UnsubscribeFromServer(channel, flags, asyncState, false);
if (task != null) return task;
}
}
}
return CompletedTask<bool>.Default(asyncState);
var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel);
msg.SetInternalCall();
return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity);
}
public bool IsConnected(RedisChannel channel = default(RedisChannel))
{
return multiplexer.SubscriberConnected(channel);
}
internal Task RemoveAllSubscriptions(CommandFlags flags, object asyncState)
public override TimeSpan Ping(CommandFlags flags = CommandFlags.None)
{
Task last = CompletedTask<bool>.Default(asyncState);
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;
// 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);
}
internal long ValidateSubscriptions()
public override Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
{
lock(subscriptions)
{
long count = 0;
foreach(var pair in subscriptions)
{
if (pair.Value.Validate(this, pair.Key)) count++;
}
return count;
}
// 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);
}
internal void ResendSubscriptions(ServerEndPoint server)
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{
if (server == null) return;
lock(subscriptions)
{
foreach(var pair in subscriptions)
{
pair.Value.Resubscribe(pair.Key, server);
}
}
if (channel.IsNullOrEmpty) throw new ArgumentNullException("channel");
var msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, message);
return ExecuteSync(msg, ResultProcessor.Int64);
}
internal static bool TryCompleteHandler<T>(EventHandler<T> handler, object sender, T args, bool isAsync) where T : EventArgs
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None)
{
if (handler == null) return true;
if (isAsync)
{
foreach (EventHandler<T> sub in handler.GetInvocationList())
{
try
{ sub.Invoke(sender, args); }
catch
{ }
}
return true;
}
return false;
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);
}
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);
}
public EndPoint SubscribedEndpoint(RedisChannel channel)
{
var server = multiplexer.GetSubscribedServer(channel);
return server == null ? null : server.EndPoint;
}
public void Unsubscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handler = null, CommandFlags flags = CommandFlags.None)
{
var task = UnsubscribeAsync(channel, handler, flags);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
public void UnsubscribeAll(CommandFlags flags = CommandFlags.None)
{
var task = UnsubscribeAllAsync(flags);
if ((flags & CommandFlags.FireAndForget) == 0) Wait(task);
}
public Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None)
{
return multiplexer.RemoveAllSubscriptions(flags, asyncState);
}
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
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 bool IsAborted
......@@ -186,6 +179,12 @@ public bool IsAborted
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)
{
int slot = ServerSelectionStrategy.NoSlot;
......
......@@ -20,34 +20,37 @@ abstract class ResultProcessor
Tracer = new TracerProcessor(false),
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[]>
ByteArray = new ByteArrayProcessor(),
ScriptLoad = new ScriptLoadProcessor();
public static readonly ResultProcessor<ClusterConfiguration>
ClusterNodes = new ClusterNodesProcessor();
public static readonly ResultProcessor<EndPoint>
ConnectionIdentity = new ConnectionIdentityProcessor();
public static readonly ResultProcessor<DateTime>
DateTime = new DateTimeProcessor();
public static readonly ResultProcessor<double>
Double = new DoubleProcessor();
public static readonly ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]>
Info = new InfoProcessor();
public static readonly ResultProcessor<long>
Int64 = new Int64Processor();
public static readonly ResultProcessor<double?>
NullableDouble = new NullableDoubleProcessor();
public static readonly ResultProcessor<long?>
NullableInt64 = new NullableInt64Processor();
public static readonly ResultProcessor<RedisChannel[]>
RedisChannelArray = new RedisChannelArrayProcessor();
public static readonly ResultProcessor<RedisKey>
RedisKey = new RedisKeyProcessor();
RedisKey = new RedisKeyProcessor();
public static readonly ResultProcessor<RedisKey[]>
RedisKeyArray = new RedisKeyArrayProcessor();
......@@ -60,15 +63,17 @@ abstract class ResultProcessor
public static readonly ResultProcessor<RedisValue[]>
RedisValueArray = new RedisValueArrayProcessor();
public static readonly ResultProcessor<RedisChannel[]>
RedisChannelArray = new RedisChannelArrayProcessor();
public static readonly ResultProcessor<TimeSpan>
ResponseTimer = new TimingProcessor();
public static readonly ResultProcessor<RedisResult>
ScriptResult = new ScriptResultProcessor();
public static readonly SortedSetWithScoresProcessor
SortedSetWithScores = new SortedSetWithScoresProcessor();
public static readonly ResultProcessor<string>
String = new StringProcessor(),
String = new StringProcessor(),
ClusterNodesRaw = new ClusterNodesRawProcessor();
public static readonly ResultProcessor<KeyValuePair<string, string>[]>
StringPairInterleaved = new StringPairInterleavedProcessor();
......@@ -77,13 +82,6 @@ public static readonly TimeSpanProcessor
TimeSpanFromSeconds = new TimeSpanProcessor(false);
public static readonly 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 ");
public void ConnectionFail(Message message, ConnectionFailureType fail, Exception innerException)
......@@ -172,24 +170,52 @@ private void UnexpectedResponse(Message message, RawResult result)
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?>
{
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)
{
if(result.Type == ResultType.MultiBulk)
TimeSpan? expiry;
if (TryParse(result, out expiry))
{
var items = result.GetItems();
long count;
if (items.Length >= 3 && items[2].TryGetInt64(out count))
{
connection.SubscriptionCount = count;
return true;
}
SetResult(message, expiry);
return true;
}
return false;
}
}
public sealed class TimingProcessor : ResultProcessor<TimeSpan>
{
public static TimerMessage CreateMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value = default(RedisValue))
......@@ -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>
{
static readonly byte[] zero = { (byte)'0' }, one = { (byte)'1' };
......@@ -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>
{
static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY ");
......@@ -426,58 +560,6 @@ static string Extract(string line, string prefix)
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>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -490,21 +572,22 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (result.Type)
{
case ResultType.SimpleString:
if(result.Assert(RedisLiterals.BytesOK))
if (result.Assert(RedisLiterals.BytesOK))
{
SetResult(message, true);
} else
}
else
{
SetResult(message, result.GetBoolean());
}
return true;
case ResultType.Integer:
case ResultType.Integer:
case ResultType.BulkString:
SetResult(message, result.GetBoolean());
return true;
case ResultType.MultiBulk:
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)
SetResult(message, items[0].GetBoolean());
return true;
......@@ -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>
{
internal static ClusterConfiguration Parse(PhysicalConnection connection, string nodes)
......@@ -605,6 +666,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return true;
}
}
sealed class DateTimeProcessor : ResultProcessor<DateTime>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -613,7 +675,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (result.Type)
{
case ResultType.Integer:
if(result.TryGetInt64(out unixTime))
if (result.TryGetInt64(out unixTime))
{
var time = RedisBase.UnixEpoch.AddSeconds(unixTime);
SetResult(message, time);
......@@ -622,7 +684,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
break;
case ResultType.MultiBulk:
var arr = result.GetItems();
switch(arr.Length)
switch (arr.Length)
{
case 1:
if (arr[0].TryGetInt64(out unixTime))
......@@ -633,7 +695,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
break;
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
SetResult(message, time);
......@@ -647,6 +709,33 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
}
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;
......@@ -668,6 +757,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false;
}
}
sealed class InfoProcessor : ResultProcessor<IGrouping<string, KeyValuePair<string, string>>[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -727,6 +817,32 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
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?>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -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[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -817,35 +962,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false;
}
}
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 RedisValueProcessor : ResultProcessor<RedisValue>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......@@ -861,150 +977,87 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false;
}
}
sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase<string, string>
{
protected override string ParseKey(RawResult key) { return key.GetString(); }
protected override string ParseValue(RawResult key) { return key.GetString(); }
}
sealed class StringProcessor : ResultProcessor<string>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.Integer:
case ResultType.SimpleString:
case ResultType.BulkString:
SetResult(message, result.GetString());
return true;
}
return false;
}
}
public sealed class TimeSpanProcessor : ResultProcessor<TimeSpan?>
private class ScriptResultProcessor : ResultProcessor<RedisResult>
{
private readonly bool isMilliseconds;
public TimeSpanProcessor(bool isMilliseconds)
{
this.isMilliseconds = isMilliseconds;
}
public bool TryParse(RawResult result, out TimeSpan? expiry)
static readonly byte[] NOSCRIPT = Encoding.UTF8.GetBytes("NOSCRIPT ");
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
{
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;
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();
}
expiry = null;
return false;
// 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)
{
TimeSpan? expiry;
if(TryParse(result, out expiry))
var value = Redis.RedisResult.TryCreate(connection, result);
if (value != null)
{
SetResult(message, expiry);
SetResult(message, value);
return true;
}
return false;
}
}
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 sealed class SortedSetWithScoresProcessor : ValuePairInterleavedProcessorBase<RedisValue, double>
sealed class StringPairInterleavedProcessor : ValuePairInterleavedProcessorBase<string, string>
{
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;
}
protected override string ParseKey(RawResult key) { return key.GetString(); }
protected override string ParseValue(RawResult key) { return key.GetString(); }
}
internal abstract class ValuePairInterleavedProcessorBase<TKey, TValue> : ResultProcessor<KeyValuePair<TKey, TValue>[]>
sealed class StringProcessor : ResultProcessor<string>
{
static readonly KeyValuePair<TKey, TValue>[] nix = new KeyValuePair<TKey, TValue>[0];
public bool TryParse(RawResult result, out KeyValuePair<TKey, TValue>[] pairs)
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
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);
}
}
}
case ResultType.Integer:
case ResultType.SimpleString:
case ResultType.BulkString:
SetResult(message, result.GetString());
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;
}
}
private class TracerProcessor : ResultProcessor<bool>
{
static readonly byte[]
authFail = Encoding.UTF8.GetBytes("ERR operation not permitted"),
loading = Encoding.UTF8.GetBytes("LOADING ");
private readonly bool establishConnection;
public TracerProcessor(bool establishConnection)
{
this.establishConnection = establishConnection;
}
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;
}
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
bool happy;
......@@ -1038,56 +1091,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
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
......
using System;
using System.Net;
using System.Net;
using System.Text;
namespace StackExchange.Redis
......@@ -27,17 +26,15 @@ internal ServerCounters(EndPoint endpoint)
/// </summary>
public ConnectionCounters Interactive { get; private set; }
/// <summary>
/// Counters associated with the subscription (pub-sub) connection
/// </summary>
public ConnectionCounters Subscription { get; private set; }
/// <summary>
/// Counters associated with other ambient activity
/// </summary>
public ConnectionCounters Other { get; private set; }
/// <summary>
/// Counters associated with the subscription (pub-sub) connection
/// </summary>
public ConnectionCounters Subscription { get; private set; }
/// <summary>
/// Indicates the total number of outstanding items against this server
/// </summary>
......
......@@ -6,7 +6,6 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace StackExchange.Redis
......@@ -30,6 +29,7 @@ internal sealed partial class ServerEndPoint : IDisposable
private readonly EndPoint endpoint;
private readonly Hashtable knownScripts = new Hashtable(StringComparer.Ordinal);
private readonly ConnectionMultiplexer multiplexer;
private int databases, writeEverySeconds;
......@@ -86,8 +86,6 @@ public bool IsConnected
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
{
get
......@@ -109,6 +107,7 @@ public long OperationCount
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; } }
public void ClearUnselectable(UnselectableFlags flags)
......@@ -160,6 +159,11 @@ public PhysicalBridge GetBridge(RedisCommand command, bool create = true)
}
}
public RedisFeatures GetFeatures()
{
return new RedisFeatures(version);
}
public void SetClusterConfiguration(ClusterConfiguration configuration)
{
......@@ -219,6 +223,14 @@ internal void Activate(ConnectionType type)
GetBridge(type, true);
}
internal void AddScript(string script, byte[] hash)
{
lock (knownScripts)
{
knownScripts[script] = hash;
}
}
internal void AutoConfigure(PhysicalConnection connection)
{
if (serverType == ServerType.Twemproxy)
......@@ -299,6 +311,14 @@ internal Task Close()
return result;
}
internal void FlushScripts()
{
lock (knownScripts)
{
knownScripts.Clear();
}
}
internal ServerCounters GetCounters()
{
var counters = new ServerCounters(endpoint);
......@@ -309,6 +329,75 @@ internal ServerCounters GetCounters()
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)
{
var bridge = unselectableReasons == 0 ? GetBridge(command, false) : null;
......@@ -391,6 +480,11 @@ internal void ReportNextFailure()
if (tmp != null) tmp.ReportNextFailure();
}
internal Task<bool> SendTracer()
{
return QueueDirectAsync(GetTracerMessage(false), ResultProcessor.Tracer);
}
internal string Summary()
{
var sb = new StringBuilder(Format.ToString(endpoint))
......@@ -423,11 +517,6 @@ internal string Summary()
}
return sb.ToString();
}
public RedisFeatures GetFeatures()
{
return new RedisFeatures(version);
}
internal void WriteDirectOrQueueFireAndForget<T>(PhysicalConnection connection, Message message, ResultProcessor<T> processor)
{
if (message != null)
......@@ -521,94 +610,5 @@ private void SetConfig<T>(ref T field, T value, [CallerMemberName] string 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
{
internal sealed class ServerSelectionStrategy
{
public const int NoSlot = -1, MultipleSlots = -2;
private const int RedisClusterSlotCount = 16384;
static readonly ushort[] crc16tab =
{
......@@ -57,26 +58,6 @@ public ServerSelectionStrategy(ConnectionMultiplexer multiplexer)
public ServerType ServerType { get { return serverType; } set { serverType = value; } }
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>
/// Computes the hash-slot that would be used by the given key
/// </summary>
......@@ -104,6 +85,7 @@ public unsafe int HashSlot(byte[] key)
}
}
}
public ServerEndPoint Select(Message message)
{
if (message == null) throw new ArgumentNullException("message");
......@@ -121,44 +103,12 @@ public ServerEndPoint Select(Message message)
}
return Select(slot, message.Command, message.Flags);
}
public ServerEndPoint Select(int db, RedisCommand command, RedisKey key, CommandFlags flags)
{
int slot = serverType == ServerType.Cluster ? HashSlot(key) : NoSlot;
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)
{
......@@ -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()
{
var arr = map;
......@@ -284,7 +249,7 @@ private ServerEndPoint FindSlave(ServerEndPoint endpoint, RedisCommand command)
private ServerEndPoint[] MapForMutation()
{
var arr = map;
if(arr == null)
if (arr == null)
{
lock (this)
{
......@@ -294,5 +259,37 @@ private ServerEndPoint[] MapForMutation()
}
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
......@@ -8,16 +8,76 @@
using System.Threading;
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>
/// 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
/// even when the system is under ambient load.
/// </summary>
public sealed class SocketManager : IDisposable
{
private readonly string name;
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 Queue<PhysicalBridge> writeQueue = new Queue<PhysicalBridge>();
bool isDisposed;
/// <summary>
/// Creates a new (optionally named) SocketManager instance
/// </summary>
......@@ -33,52 +93,38 @@ public SocketManager(string name = null)
dedicatedWriter.Name = name + ":Write";
dedicatedWriter.IsBackground = true; // should not keep process alive
dedicatedWriter.Start(this); // will self-exit when disposed
}
/// <summary>
/// Gets the name of this SocketManager instance
/// </summary>
public string Name { get { return name; } }
bool isDisposed;
struct SocketPair
}
private enum CallbackOperation
{
public readonly Socket Socket;
public readonly ISocketCallback Callback;
public SocketPair(Socket socket, ISocketCallback callback)
{
this.Socket = socket;
this.Callback = callback;
}
Read,
Error
}
#if !MONO
/// <summary>
/// Adds a new socket and callback to the manager
/// Gets the name of this SocketManager instance
/// </summary>
private void AddRead(Socket socket, ISocketCallback callback)
public string Name { get { return name; } }
/// <summary>
/// Releases all resources associated with this instance
/// </summary>
public void Dispose()
{
if (socket == null) throw new ArgumentNullException("socket");
if (callback == null) throw new ArgumentNullException("callback");
lock (socketLookup)
lock (writeQueue)
{
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();
}
}
}
private readonly Dictionary<IntPtr, SocketPair> socketLookup = new Dictionary<IntPtr, SocketPair>();
#endif
// make sure writer threads know to exit
isDisposed = true;
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);
}
internal void RequestWrite(PhysicalBridge bridge, bool forced)
{
......@@ -99,337 +145,70 @@ internal void RequestWrite(PhysicalBridge bridge, bool forced)
}
}
#if !MONO
private int readerCount;
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);
}
private static ParameterizedThreadStart read = state => ((SocketManager)state).Read();
private void Read()
internal void Shutdown(SocketToken token)
{
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);
}
Shutdown(token.Socket);
}
readonly Queue<IntPtr> readQueue = new Queue<IntPtr>(), errorQueue = new Queue<IntPtr>();
static readonly IntPtr[] EmptyPointers = new IntPtr[0];
private void ReadImpl()
private void EndConnect(IAsyncResult ar)
{
List<IntPtr> dead = null, active = new List<IntPtr>();
IntPtr[] readSockets = EmptyPointers, errorSockets = EmptyPointers;
long lastHeartbeat = Environment.TickCount;
SocketPair[] allSocketPairs = null;
while (true)
Tuple<Socket, ISocketCallback> tuple = null;
try
{
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)
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)
{
for (int i = 1; i <= queueCount; i++)
{
errorQueue.Enqueue(errorSockets[i]);
}
case SocketMode.Poll:
OnAddRead(socket, callback);
break;
case SocketMode.Async:
try
{ callback.StartReading(); }
catch
{ Shutdown(socket); }
break;
default:
Shutdown(socket);
break;
}
if (ready >= 5) // number of sockets we should attempt to process by ourself before asking for help
}
catch
{
if (tuple != null)
{
// seek help, work in parallel, then synchronize
var obj = new QueueDrainSyncLock(this);
lock (obj)
try
{ tuple.Item2.Error(); }
catch (Exception ex)
{
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);
}
Trace.WriteLine(ex);
}
}
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;
}
/// <summary>
/// Adds a new socket and callback to the manager
/// </summary>
partial void OnAddRead(Socket socket, ISocketCallback callback);
partial void OnDispose();
partial void OnShutdown(Socket socket);
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()
{
ProcessItems(socketLookup, readQueue, CallbackOperation.Read);
ProcessItems(socketLookup, errorQueue, CallbackOperation.Error);
}
#endif
private void Shutdown(Socket socket)
{
if (socket != null)
{
#if !MONO
lock (socketLookup)
{
socketLookup.Remove(socket.Handle);
}
#endif
OnShutdown(socket);
try { socket.Shutdown(SocketShutdown.Both); } catch { }
try { socket.Close(); } 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()
{
while (true)
......@@ -472,7 +251,8 @@ private void WriteAllQueues()
break;
}
}
}
}
private void WriteOneQueue()
{
PhysicalBridge bridge;
......@@ -505,96 +285,5 @@ private void WriteOneQueue()
}
} 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()
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>
/// Create a new TaskCompletionSource that will not allow result-setting threads to be hijacked
/// </summary>
......@@ -90,12 +98,5 @@ public static TaskCompletionSource<T> CreateDenyExecSync<T>(object asyncState)
DenyExecSync(source.Task);
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 BasicTest\bin\mono
@md StackExchange.Redis\bin\mono
@md BasicTest\bin\mono
@rd /s /q StackExchange.Redis\bin\mono 1>nul 2>nul
@rd /s /q BasicTest\bin\mono 1>nul 2>nul
@md StackExchange.Redis\bin\mono 1>nul 2>nul
@md BasicTest\bin\mono 1>nul 2>nul
@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
@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