Commit 87cb5f21 authored by Marc Gravell's avatar Marc Gravell

Better detail (optional) in faults

parent 5ec10092
......@@ -56,7 +56,7 @@ internal void AppendDeltas(StringBuilder sb)
internal void AssertAvailable(RedisCommand command)
{
if (map[(int)command] == null) throw ExceptionFactory.CommandDisabled(command);
if (map[(int)command] == null) throw ExceptionFactory.CommandDisabled(false, command, null, null);
}
internal byte[] GetBytes(RedisCommand command)
......
......@@ -270,11 +270,11 @@ public void ExportConfiguration(Stream destination, ExportOptions options = Expo
internal void MakeMaster(ServerEndPoint server, ReplicationChangeOptions options, TextWriter log)
{
CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
if (!configuration.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(RedisCommand.SLAVEOF);
if (!configuration.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
if (server == null) throw new ArgumentNullException("server");
var srv = new RedisServer(this, server, null);
if (!srv.IsConnected) throw ExceptionFactory.NoConnectionAvailable(RedisCommand.SLAVEOF);
if (!srv.IsConnected) throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
if (log == null) log = TextWriter.Null;
CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
......@@ -396,7 +396,7 @@ internal void LogLocked(TextWriter log, string line, params object[] args)
internal void CheckMessage(Message message)
{
if (!configuration.AllowAdmin && message.IsAdmin)
throw ExceptionFactory.AdminModeNotEnabled(message.Command);
throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, message.Command, message, null);
CommandMap.AssertAvailable(message.Command);
}
......@@ -790,7 +790,7 @@ internal ServerEndPoint GetServerEndPoint(EndPoint endpoint)
private ConnectionMultiplexer(ConfigurationOptions configuration)
{
if (configuration == null) throw new ArgumentNullException("configuration");
ShowKeysInTimeout = true;
IncludeDetailInExceptions = true;
this.configuration = configuration;
this.CommandMap = configuration.CommandMap;
......@@ -1434,14 +1434,14 @@ private bool TryPushMessageToBridge<T>(Message message, ResultProcessor<T> proce
if (message.IsMasterOnly() && server.IsSlave)
{
throw ExceptionFactory.MasterOnly(message.Command);
throw ExceptionFactory.MasterOnly(IncludeDetailInExceptions, message.Command, message, server);
}
if (server.ServerType == ServerType.Cluster)
{
if (message.GetHashSlot(ServerSelectionStrategy) == ServerSelectionStrategy.MultipleSlots)
{
throw ExceptionFactory.MultiSlot();
throw ExceptionFactory.MultiSlot(IncludeDetailInExceptions, message);
}
}
if (!server.IsConnected)
......@@ -1455,7 +1455,8 @@ private bool TryPushMessageToBridge<T>(Message message, ResultProcessor<T> proce
if (message.Db >= 0)
{
int availableDatabases = server.Databases;
if (availableDatabases > 0 && message.Db >= availableDatabases) throw ExceptionFactory.DatabaseOutfRange(message.Db);
if (availableDatabases > 0 && message.Db >= availableDatabases) throw ExceptionFactory.DatabaseOutfRange(
IncludeDetailInExceptions, message.Db, message, server);
}
Trace("Queueing on server: " + message);
......@@ -1603,7 +1604,7 @@ internal Task<T> ExecuteAsyncImpl<T>(Message message, ResultProcessor<T> process
var source = ResultBox<T>.Get(tcs);
if (!TryPushMessageToBridge(message, processor, source, ref server))
{
ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(message.Command));
ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, message.Command, message, server));
}
return tcs.Task;
}
......@@ -1644,7 +1645,7 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser
{
if (!TryPushMessageToBridge(message, processor, source, ref server))
{
throw ExceptionFactory.NoConnectionAvailable(message.Command);
throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, message.Command, message, server);
}
if (Monitor.Wait(source, timeoutMilliseconds))
......@@ -1656,15 +1657,15 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser
Trace("Timeout performing " + message.ToString());
Interlocked.Increment(ref syncTimeouts);
string errMessage;
if (server == null)
if (server == null || !IncludeDetailInExceptions)
{
errMessage = "Timeout performing " + (ShowKeysInTimeout ? message.CommandAndKey : message.Command.ToString());
errMessage = "Timeout performing " + message.Command.ToString();
}
else
{
int inst, qu, qs, qc, wr, wq;
int queue = server.GetOutstandingCount(message.Command, out inst, out qu, out qs, out qc, out wr, out wq);
var sb = new StringBuilder("Timeout performing ").Append(ShowKeysInTimeout ? message.CommandAndKey : message.Command.ToString())
var sb = new StringBuilder("Timeout performing ").Append(message.CommandAndKey)
.Append(", inst: ").Append(inst)
.Append(", queue: ").Append(queue).Append(", qu=").Append(qu)
.Append(", qs=").Append(qs).Append(", qc=").Append(qc)
......@@ -1677,7 +1678,8 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser
else Interlocked.Exchange(ref stormLogSnapshot, log);
}
}
throw new TimeoutException(errMessage); // very important not to return "source" to the pool here
throw ExceptionFactory.Timeout(IncludeDetailInExceptions, errMessage, message, server);
// very important not to return "source" to the pool here
}
}
// snapshot these so that we can recycle the box
......@@ -1691,9 +1693,9 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser
}
/// <summary>
/// Should the key name be displayed when generating a timeout message?
/// Should exceptions include identifiable details? (key names, additional .Data annotations)
/// </summary>
public bool ShowKeysInTimeout { get; set; }
public bool IncludeDetailInExceptions { get; set; }
int haveStormLog = 0, stormLogThreshold = 15;
string stormLogSnapshot;
......
......@@ -218,7 +218,7 @@ internal void SimulateConnectionFailure()
{
if (!multiplexer.RawConfig.AllowAdmin)
{
throw ExceptionFactory.AdminModeNotEnabled(RedisCommand.DEBUG); // close enough
throw ExceptionFactory.AdminModeNotEnabled(multiplexer.IncludeDetailInExceptions, RedisCommand.DEBUG, null, serverEndPoint); // close enough
}
var tmp = physical;
if (tmp != null) tmp.RecordConnectionFailed(ConnectionFailureType.SocketFailure);
......
......@@ -4,41 +4,95 @@ namespace StackExchange.Redis
{
internal static class ExceptionFactory
{
internal static Exception AdminModeNotEnabled(RedisCommand command)
internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
return new RedisCommandException("This operation is not available unless admin mode is enabled: " + command.ToString());
string s = GetLabel(includeDetail, command, message);
var ex = new RedisCommandException("This operation is not available unless admin mode is enabled: " + s);
if (includeDetail) AddDetail(ex, message, server, s);
return ex;
}
internal static Exception NoConnectionAvailable(RedisCommand command)
static string GetLabel(bool includeDetail, RedisCommand command, Message message)
{
return new RedisConnectionException(ConnectionFailureType.UnableToResolvePhysicalConnection, "No connection is available to service this operation: " + command.ToString());
return message == null ? command.ToString() : (includeDetail ? message.CommandAndKey : message.Command.ToString());
}
internal static Exception CommandDisabled(RedisCommand command)
internal static Exception NoConnectionAvailable(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
return new RedisCommandException("This operation has been disabled in the command-map and cannot be used: " + command.ToString());
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;
}
internal static Exception MultiSlot()
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)
{
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);
return ex;
}
internal static Exception MultiSlot(bool includeDetail, Message message)
{
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 DatabaseOutfRange(bool includeDetail, int targetDatabase, Message message, ServerEndPoint server)
{
var ex = new RedisCommandException("The database does not exist on the server: " + targetDatabase);
if (includeDetail) AddDetail(ex, message, server, null);
return ex;
}
internal static Exception DatabaseRequired(bool includeDetail, RedisCommand command)
{
return new RedisCommandException("Multi-key operations must involve a single slot; keys can use 'hash tags' to help this, i.e. '{/users/12345}/account' and '{/users/12345}/contacts' will always be in the same slot");
string s = command.ToString();
var ex = new RedisCommandException("A target database is required for " + s);
if (includeDetail) AddDetail(ex, null, null, s);
return ex;
}
internal static Exception DatabaseOutfRange(int targetDatabase)
internal static Exception DatabaseNotRequired(bool includeDetail, RedisCommand command)
{
return new RedisCommandException("The database does not exist on the server: " + targetDatabase);
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 DatabaseRequired(RedisCommand command)
internal static Exception MasterOnly(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{
return new RedisCommandException("A target database is required for " + command.ToString());
string s = GetLabel(includeDetail, command, message);
var ex = new RedisCommandException("Command cannot be issued to a slave: " + s);
if (includeDetail) AddDetail(ex, message, server, s);
return ex;
}
internal static Exception DatabaseNotRequired(RedisCommand command)
internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server)
{
return new RedisCommandException("A target database is not required for " + command.ToString());
var ex = new TimeoutException(errorMessage);
if (includeDetail) AddDetail(ex, message, server, null);
return ex;
}
internal static Exception MasterOnly(RedisCommand command)
internal static Exception ConnectionFailure(bool includeDetail, ConnectionFailureType failureType, string message, ServerEndPoint server)
{
return new RedisCommandException(command + " cannot be issued to a slave");
var ex = new RedisConnectionException(failureType, message);
if (includeDetail) AddDetail(ex, null, server, null);
return ex;
}
}
}
......@@ -93,14 +93,14 @@ protected Message(int db, CommandFlags flags, RedisCommand command)
{
if (dbNeeded)
{
throw ExceptionFactory.DatabaseRequired(command);
throw ExceptionFactory.DatabaseRequired(false, command);
}
}
else
{
if (!dbNeeded)
{
throw ExceptionFactory.DatabaseNotRequired(command);
throw ExceptionFactory.DatabaseNotRequired(false, command);
}
}
......@@ -109,7 +109,7 @@ protected Message(int db, CommandFlags flags, RedisCommand command)
switch (GetMasterSlaveFlags(flags))
{
case CommandFlags.DemandSlave:
throw ExceptionFactory.MasterOnly(command);
throw ExceptionFactory.MasterOnly(false, command, null, null);
case CommandFlags.DemandMaster:
// already fine as-is
break;
......
......@@ -257,7 +257,7 @@ internal void KeepAlive()
multiplexer.Trace("Enqueue: " + msg);
if(!TryEnqueue(msg, serverEndPoint.IsSlave))
{
OnInternalError(ExceptionFactory.NoConnectionAvailable(msg.Command));
OnInternalError(ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, msg.Command, msg, serverEndPoint));
}
}
}
......@@ -605,7 +605,7 @@ private void SelectDatabase(PhysicalConnection connection, Message message)
int db = message.Db;
if (db >= 0)
{
var sel = connection.GetSelectDatabaseCommand(db);
var sel = connection.GetSelectDatabaseCommand(db, message);
if (sel != null)
{
connection.Enqueue(sel);
......@@ -624,7 +624,7 @@ private bool WriteMessageToServer(PhysicalConnection connection, Message message
bool isMasterOnly = message.IsMasterOnly();
if (isMasterOnly && serverEndPoint.IsSlave)
{
throw ExceptionFactory.MasterOnly(message.Command);
throw ExceptionFactory.MasterOnly(multiplexer.IncludeDetailInExceptions, message.Command, message, ServerEndPoint);
}
SelectDatabase(connection, message);
......
......@@ -286,7 +286,7 @@ internal Message GetReadModeCommand(bool isMasterOnly)
return null;
}
internal Message GetSelectDatabaseCommand(int targetDatabase)
internal Message GetSelectDatabaseCommand(int targetDatabase, Message message)
{
if (targetDatabase < 0) return null;
if (targetDatabase != currentDatabase)
......@@ -297,19 +297,19 @@ internal Message GetSelectDatabaseCommand(int targetDatabase)
if (!serverEndpoint.HasDatabases) // only db0 is available on cluster
{
if (targetDatabase != 0)
{ // should never see this, since the API doesn't allow it
{ // should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory
throw new RedisCommandException("Multiple databases are not supported on this server; cannot switch to database: " + targetDatabase);
}
return null;
}
if (TransactionActive)
{// should never see this, since the API doesn't allow it
{// should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory
throw new RedisCommandException("Multiple databases inside a transaction are not currently supported" + targetDatabase);
}
if (available != 0 && targetDatabase >= available) // we positively know it is out of range
{
throw ExceptionFactory.DatabaseOutfRange(targetDatabase);
throw ExceptionFactory.DatabaseOutfRange(multiplexer.IncludeDetailInExceptions, targetDatabase, message, serverEndpoint);
}
bridge.Trace("Switching database: " + targetDatabase);
currentDatabase = targetDatabase;
......@@ -351,7 +351,7 @@ internal void WriteHeader(RedisCommand command, int arguments)
var commandBytes = multiplexer.CommandMap.GetBytes(command);
if (commandBytes == null)
{
throw ExceptionFactory.CommandDisabled(command);
throw ExceptionFactory.CommandDisabled(multiplexer.IncludeDetailInExceptions, command, null, bridge.ServerEndPoint);
}
outStream.WriteByte((byte)'*');
WriteRaw(outStream, arguments + 1);
......@@ -742,7 +742,7 @@ private RawResult ReadArray(byte[] buffer, ref int offset, ref int count)
if (itemCount.HasValue)
{
long i64;
if (!itemCount.TryGetInt64(out i64)) throw new RedisConnectionException(ConnectionFailureType.ProtocolFailure, "Invalid array length");
if (!itemCount.TryGetInt64(out i64)) throw ExceptionFactory.ConnectionFailure(multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid array length", bridge.ServerEndPoint);
int itemCountActual = checked((int)i64);
if (itemCountActual == 0) return RawResult.EmptyArray;
......@@ -764,7 +764,7 @@ private RawResult ReadBulkString(byte[] buffer, ref int offset, ref int count)
if (prefix.HasValue)
{
long i64;
if (!prefix.TryGetInt64(out i64)) throw new RedisConnectionException(ConnectionFailureType.ProtocolFailure, "Invalid bulk string length");
if (!prefix.TryGetInt64(out i64)) throw ExceptionFactory.ConnectionFailure(multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid bulk string length", bridge.ServerEndPoint);
int bodySize = checked((int)i64);
if (bodySize < 0)
{
......@@ -774,7 +774,7 @@ private RawResult ReadBulkString(byte[] buffer, ref int offset, ref int count)
{
if (buffer[offset + bodySize] != '\r' || buffer[offset + bodySize + 1] != '\n')
{
throw new RedisConnectionException(ConnectionFailureType.ProtocolFailure, "Invalid bulk string terminator");
throw ExceptionFactory.ConnectionFailure(multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid bulk string terminator", bridge.ServerEndPoint);
}
var result = new RawResult(ResultType.BulkString, buffer, offset, bodySize);
offset += bodySize + 2;
......
......@@ -30,13 +30,13 @@ public void Execute()
if (server == null)
{
FailNoServer(snapshot);
throw ExceptionFactory.NoConnectionAvailable(message.Command);
throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, server);
}
var bridge = server.GetBridge(message.Command);
if (bridge == null)
{
FailNoServer(snapshot);
throw ExceptionFactory.NoConnectionAvailable(message.Command);
throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, server);
}
// identity a list
......
......@@ -1553,7 +1553,7 @@ private Message GetSortedSetAddMessage(RedisKey destination, RedisKey key, long
// because we are using STORE, we need to push this to a master
if (Message.GetMasterSlaveFlags(flags) == CommandFlags.DemandSlave)
{
throw ExceptionFactory.MasterOnly(RedisCommand.SORT);
throw ExceptionFactory.MasterOnly(multiplexer.IncludeDetailInExceptions, RedisCommand.SORT, null, null);
}
flags = Message.SetMasterSlaveFlags(flags, CommandFlags.DemandMaster);
values.Add(RedisLiterals.STORE);
......
......@@ -286,14 +286,14 @@ public Task<bool> ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFla
public void ScriptFlush(CommandFlags flags = CommandFlags.None)
{
if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(RedisCommand.SCRIPT);
if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server);
var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH);
ExecuteSync(msg, ResultProcessor.DemandOK);
}
public Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None)
{
if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(RedisCommand.SCRIPT);
if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server);
var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH);
return ExecuteAsync(msg, ResultProcessor.DemandOK);
}
......@@ -472,7 +472,7 @@ internal override Task<T> ExecuteAsync<T>(Message message, ResultProcessor<T> pr
// no need to deny exec-sync here; will be complete before they see if
var tcs = TaskSource.Create<T>(asyncState);
ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(message.Command));
ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, server));
return tcs.Task;
}
return base.ExecuteAsync<T>(message, processor, server);
......@@ -485,7 +485,7 @@ internal override T ExecuteSync<T>(Message message, ResultProcessor<T> processor
if (!server.IsConnected)
{
if (message == null || message.IsFireAndForget) return default(T);
throw ExceptionFactory.NoConnectionAvailable(message.Command);
throw ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, server);
}
return base.ExecuteSync<T>(message, processor, server);
}
......
......@@ -341,7 +341,7 @@ internal Task<T> QueueDirectAsync<T>(Message message, ResultProcessor<T> process
message.SetSource(processor, source);
if(!(bridge ?? GetBridge(message.Command)).TryEnqueue(message, isSlave))
{
ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(message.Command));
ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, this));
}
return tcs.Task;
}
......
......@@ -111,7 +111,7 @@ public ServerEndPoint Select(Message message)
if (serverType == ServerType.Cluster)
{
slot = message.GetHashSlot(this);
if (slot == MultipleSlots) throw ExceptionFactory.MultiSlot();
if (slot == MultipleSlots) throw ExceptionFactory.MultiSlot(multiplexer.IncludeDetailInExceptions, message);
}
return Select(slot, message.Command, message.Flags);
}
......
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