Commit c0c6db21 authored by Nick Craver's avatar Nick Craver Committed by Nick Craver

Exceptions: add timeout info everywhere

Note: this doesn't include command and key, which is still behind the IncludeDetailInExceptions flag
parent 157eab2e
...@@ -2089,7 +2089,7 @@ internal Exception GetException(WriteResult result, Message message, ServerEndPo ...@@ -2089,7 +2089,7 @@ internal Exception GetException(WriteResult result, Message message, ServerEndPo
} }
catch { } catch { }
return ExceptionFactory.Timeout(IncludeDetailInExceptions, "The timeout was reached before the message could be written to the output buffer, and it was not sent (" return ExceptionFactory.Timeout(this, "The timeout was reached before the message could be written to the output buffer, and it was not sent ("
+ Format.ToString(TimeoutMilliseconds) + "ms" + counters + ")", message, server); + Format.ToString(TimeoutMilliseconds) + "ms" + counters + ")", message, server);
case WriteResult.WriteFailure: case WriteResult.WriteFailure:
default: default:
...@@ -2146,71 +2146,7 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser ...@@ -2146,71 +2146,7 @@ internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, Ser
{ {
Trace("Timeout performing " + message); Trace("Timeout performing " + message);
Interlocked.Increment(ref syncTimeouts); Interlocked.Increment(ref syncTimeouts);
string errMessage; throw ExceptionFactory.Timeout(this, "Timeout performing " + message.Command.ToString(), message, server);
List<Tuple<string, string>> data = null;
if (server == null || !IncludeDetailInExceptions)
{
errMessage = "Timeout performing " + message.Command.ToString();
}
else
{
var sb = new StringBuilder("Timeout performing ").Append(message.CommandAndKey).Append(" (").Append(Format.ToString(TimeoutMilliseconds)).Append("ms)");
data = new List<Tuple<string, string>> { Tuple.Create("Message", message.CommandAndKey) };
void add(string lk, string sk, string v)
{
if (v != null)
{
data.Add(Tuple.Create(lk, v));
sb.Append(", ").Append(sk).Append(": ").Append(v);
}
}
server.GetOutstandingCount(message.Command, out int inst, out int qs, out int @in);
add("Instantaneous", "inst", inst.ToString());
add("Queue-Awaiting-Response", "qs", qs.ToString());
add("Inbound-Bytes", "in", @in.ToString());
add("Manager", "mgr", SocketManager?.GetState());
add("Client-Name", "clientName", ClientName);
add("Server-Endpoint", "serverEndpoint", server.EndPoint.ToString());
var hashSlot = message.GetHashSlot(ServerSelectionStrategy);
// only add keyslot if its a valid cluster key slot
if (hashSlot != ServerSelectionStrategy.NoSlot)
{
add("Key-HashSlot", "PerfCounterHelperkeyHashSlot", message.GetHashSlot(this.ServerSelectionStrategy).ToString());
}
int busyWorkerCount = PerfCounterHelper.GetThreadPoolStats(out string iocp, out string worker);
add("ThreadPool-IO-Completion", "IOCP", iocp);
add("ThreadPool-Workers", "WORKER", worker);
data.Add(Tuple.Create("Busy-Workers", busyWorkerCount.ToString()));
if (IncludePerformanceCountersInExceptions)
{
add("Local-CPU", "Local-CPU", PerfCounterHelper.GetSystemCpuPercent());
}
sb.Append(" (Please take a look at this article for some common client-side issues that can cause timeouts: ");
sb.Append(timeoutHelpLink);
sb.Append(")");
errMessage = sb.ToString();
if (StormLogThreshold >= 0 && qs >= StormLogThreshold && Interlocked.CompareExchange(ref haveStormLog, 1, 0) == 0)
{
var log = server.GetStormLog(message.Command);
if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref haveStormLog, 0);
else Interlocked.Exchange(ref stormLogSnapshot, log);
}
}
var timeoutEx = ExceptionFactory.Timeout(IncludeDetailInExceptions, errMessage, message, server);
timeoutEx.HelpLink = timeoutHelpLink;
if (data != null)
{
foreach (var kv in data)
{
timeoutEx.Data["Redis-" + kv.Item1] = kv.Item2;
}
}
throw timeoutEx;
// very important not to return "source" to the pool here // very important not to return "source" to the pool here
} }
} }
...@@ -2232,8 +2168,8 @@ void add(string lk, string sk, string v) ...@@ -2232,8 +2168,8 @@ void add(string lk, string sk, string v)
/// </summary> /// </summary>
public bool IncludePerformanceCountersInExceptions { get; set; } public bool IncludePerformanceCountersInExceptions { get; set; }
private int haveStormLog = 0; internal int haveStormLog = 0;
private string stormLogSnapshot; internal string stormLogSnapshot;
/// <summary> /// <summary>
/// Limit at which to start recording unusual busy patterns (only one log will be retained at a time; /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time;
/// set to a negative value to disable this feature) /// set to a negative value to disable this feature)
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -9,7 +10,8 @@ internal static class ExceptionFactory ...@@ -9,7 +10,8 @@ internal static class ExceptionFactory
private const string private const string
DataCommandKey = "redis-command", DataCommandKey = "redis-command",
DataSentStatusKey = "request-sent-status", DataSentStatusKey = "request-sent-status",
DataServerKey = "redis-server"; DataServerKey = "redis-server",
timeoutHelpLink = "https://stackexchange.github.io/StackExchange.Redis/Timeouts";
internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server) internal static Exception AdminModeNotEnabled(bool includeDetail, RedisCommand command, Message message, ServerEndPoint server)
{ {
...@@ -165,10 +167,79 @@ internal static Exception NoCursor(RedisCommand command) ...@@ -165,10 +167,79 @@ internal static Exception NoCursor(RedisCommand command)
return new RedisCommandException("Command cannot be used with a cursor: " + s); return new RedisCommandException("Command cannot be used with a cursor: " + s);
} }
internal static Exception Timeout(bool includeDetail, string errorMessage, Message message, ServerEndPoint server) internal static Exception Timeout(ConnectionMultiplexer mutiplexer, string baseErrorMessage, Message message, ServerEndPoint server)
{ {
var ex = new RedisTimeoutException(errorMessage, message?.Status ?? CommandStatus.Unknown); List<Tuple<string, string>> data = new List<Tuple<string, string>> { Tuple.Create("Message", message.CommandAndKey) };
if (includeDetail) AddDetail(ex, message, server, null); var sb = new StringBuilder();
if (!string.IsNullOrEmpty(baseErrorMessage))
{
sb.Append(baseErrorMessage);
}
else
{
sb.Append("Timeout performing ").Append(message.CommandAndKey).Append(" (").Append(Format.ToString(mutiplexer.TimeoutMilliseconds)).Append("ms)");
}
void add(string lk, string sk, string v)
{
if (v != null)
{
data.Add(Tuple.Create(lk, v));
sb.Append(", ").Append(sk).Append(": ").Append(v);
}
}
if (server != null)
{
server.GetOutstandingCount(message.Command, out int inst, out int qs, out int @in);
add("Instantaneous", "inst", inst.ToString());
add("Queue-Awaiting-Response", "qs", qs.ToString());
add("Inbound-Bytes", "in", @in.ToString());
if (mutiplexer.StormLogThreshold >= 0 && qs >= mutiplexer.StormLogThreshold && Interlocked.CompareExchange(ref mutiplexer.haveStormLog, 1, 0) == 0)
{
var log = server.GetStormLog(message.Command);
if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref mutiplexer.haveStormLog, 0);
else Interlocked.Exchange(ref mutiplexer.stormLogSnapshot, log);
}
add("Server-Endpoint", "serverEndpoint", server.EndPoint.ToString());
}
add("Manager", "mgr", mutiplexer.SocketManager?.GetState());
add("Client-Name", "clientName", mutiplexer.ClientName);
var hashSlot = message.GetHashSlot(mutiplexer.ServerSelectionStrategy);
// only add keyslot if its a valid cluster key slot
if (hashSlot != ServerSelectionStrategy.NoSlot)
{
add("Key-HashSlot", "PerfCounterHelperkeyHashSlot", message.GetHashSlot(mutiplexer.ServerSelectionStrategy).ToString());
}
int busyWorkerCount = PerfCounterHelper.GetThreadPoolStats(out string iocp, out string worker);
add("ThreadPool-IO-Completion", "IOCP", iocp);
add("ThreadPool-Workers", "WORKER", worker);
data.Add(Tuple.Create("Busy-Workers", busyWorkerCount.ToString()));
if (mutiplexer.IncludePerformanceCountersInExceptions)
{
add("Local-CPU", "Local-CPU", PerfCounterHelper.GetSystemCpuPercent());
}
sb.Append(" (Please take a look at this article for some common client-side issues that can cause timeouts: ");
sb.Append(timeoutHelpLink);
sb.Append(")");
var ex = new RedisTimeoutException(sb.ToString(), message?.Status ?? CommandStatus.Unknown)
{
HelpLink = timeoutHelpLink
};
if (data != null)
{
foreach (var kv in data)
{
ex.Data["Redis-" + kv.Item1] = kv.Item2;
}
}
if (mutiplexer.IncludeDetailInExceptions) AddDetail(ex, message, server, null);
return ex; return ex;
} }
......
...@@ -575,14 +575,13 @@ internal void OnBridgeHeartbeat() ...@@ -575,14 +575,13 @@ internal void OnBridgeHeartbeat()
var bridge = BridgeCouldBeNull; var bridge = BridgeCouldBeNull;
if (bridge == null) return; if (bridge == null) return;
bool includeDetail = bridge.Multiplexer.IncludeDetailInExceptions;
var server = bridge?.ServerEndPoint; var server = bridge?.ServerEndPoint;
var timeout = bridge.Multiplexer.AsyncTimeoutMilliseconds; var timeout = bridge.Multiplexer.AsyncTimeoutMilliseconds;
foreach (var msg in _writtenAwaitingResponse) foreach (var msg in _writtenAwaitingResponse)
{ {
if (msg.HasAsyncTimedOut(now, timeout, out var elapsed)) if (msg.HasAsyncTimedOut(now, timeout, out var elapsed))
{ {
var timeoutEx = ExceptionFactory.Timeout(includeDetail, $"Timeout awaiting response ({elapsed}ms elapsed, timeout is {timeout}ms)", msg, server); var timeoutEx = ExceptionFactory.Timeout(bridge.Multiplexer, $"Timeout awaiting response ({elapsed}ms elapsed, timeout is {timeout}ms)", msg, server);
bridge.Multiplexer?.OnMessageFaulted(msg, timeoutEx); bridge.Multiplexer?.OnMessageFaulted(msg, timeoutEx);
msg.SetExceptionAndComplete(timeoutEx, bridge); // tell the message that it is doomed msg.SetExceptionAndComplete(timeoutEx, bridge); // tell the message that it is doomed
bridge.Multiplexer.OnAsyncTimeout(); bridge.Multiplexer.OnAsyncTimeout();
......
...@@ -172,10 +172,11 @@ protected ScanResult GetNextPageSync(IScanningCursor obj, long cursor) ...@@ -172,10 +172,11 @@ protected ScanResult GetNextPageSync(IScanningCursor obj, long cursor)
return redis.ExecuteSync(CreateMessage(cursor), Processor, server); return redis.ExecuteSync(CreateMessage(cursor), Processor, server);
} }
protected Task<ScanResult> GetNextPageAsync(IScanningCursor obj, long cursor) protected Task<ScanResult> GetNextPageAsync(IScanningCursor obj, long cursor, out Message message)
{ {
activeCursor = obj; activeCursor = obj;
return redis.ExecuteAsync(CreateMessage(cursor), Processor, server); message = CreateMessage(cursor);
return redis.ExecuteAsync(message, Processor, server);
} }
protected bool TryWait(Task<ScanResult> pending) => redis.TryWait(pending); protected bool TryWait(Task<ScanResult> pending) => redis.TryWait(pending);
...@@ -197,8 +198,11 @@ public CursorEnumerator(CursorEnumerable<T> parent) ...@@ -197,8 +198,11 @@ public CursorEnumerator(CursorEnumerable<T> parent)
private void LoadNextPageAsync() private void LoadNextPageAsync()
{ {
if(pending == null && nextCursor != 0) if (pending == null && nextCursor != 0)
pending = parent.GetNextPageAsync(this, nextCursor); {
pending = parent.GetNextPageAsync(this, nextCursor, out var message);
pendingMessage = message;
}
} }
private bool SimpleNext() private bool SimpleNext()
...@@ -214,6 +218,7 @@ private bool SimpleNext() ...@@ -214,6 +218,7 @@ private bool SimpleNext()
private T[] page; private T[] page;
private Task<ScanResult> pending; private Task<ScanResult> pending;
private Message pendingMessage;
private int pageIndex; private int pageIndex;
private long currentCursor, nextCursor; private long currentCursor, nextCursor;
...@@ -233,6 +238,7 @@ private void ProcessReply(ScanResult result) ...@@ -233,6 +238,7 @@ private void ProcessReply(ScanResult result)
pageIndex = -1; pageIndex = -1;
page = result.Values; page = result.Values;
pending = null; pending = null;
pendingMessage = null;
} }
public bool MoveNext() public bool MoveNext()
...@@ -260,7 +266,7 @@ public bool MoveNext() ...@@ -260,7 +266,7 @@ public bool MoveNext()
} }
else else
{ {
throw ExceptionFactory.Timeout(parent.redis.multiplexer.IncludeDetailInExceptions, "Timeout waiting for scan cursor to complete", null, parent.server); throw ExceptionFactory.Timeout(parent.redis.multiplexer, null, pendingMessage, parent.server);
} }
if (SimpleNext()) return true; if (SimpleNext()) return true;
...@@ -290,6 +296,7 @@ public void Reset() ...@@ -290,6 +296,7 @@ public void Reset()
state = State.Initial; state = State.Initial;
page = null; page = null;
pending = null; pending = null;
pendingMessage = null;
} }
long IScanningCursor.Cursor => currentCursor; long IScanningCursor.Cursor => currentCursor;
......
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