Skip to content

Commit 7293213

Browse files
authored
Timeouts: hop out of queue processing as soon as we know no more are eligible for timeout (#2217)
Since we're in a head-of-queue style situation here, the first non-timeout we hit means we're done - hop out and release the lock. Small docs fix tucked in here because reasons.
1 parent 4895a0b commit 7293213

File tree

4 files changed

+20
-10
lines changed

4 files changed

+20
-10
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1111
<CodeAnalysisRuleset>$(MSBuildThisFileDirectory)Shared.ruleset</CodeAnalysisRuleset>
1212
<MSBuildWarningsAsMessages>NETSDK1069</MSBuildWarningsAsMessages>
13-
<NoWarn>NU5105</NoWarn>
13+
<NoWarn>NU5105;NU1507</NoWarn>
1414
<PackageReleaseNotes>https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes</PackageReleaseNotes>
1515
<PackageProjectUrl>https://github.com/StackExchange/StackExchange.Redis/</PackageProjectUrl>
1616
<PackageLicenseExpression>MIT</PackageLicenseExpression>

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Adds: `IConnectionMultiplexer` now implements `IAsyncDisposable` ([#2161 by kimsey0](https://github.com/StackExchange/StackExchange.Redis/pull/2161))
77
- Adds: `IConnectionMultiplexer.GetServers()` to get all `IServer` instances for a multiplexer ([#2203 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2203))
88
- Fix [#2016](https://github.com/StackExchange/StackExchange.Redis/issues/2016): Align server selection with supported commands (e.g. with writable servers) to reduce `Command cannot be issued to a replica` errors ([#2191 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2191))
9+
- Performance: Optimization around timeout processing to reduce lock contention in the case of many items that haven't yet timed out during a heartbeat ([#2217 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2217))
910

1011
## 2.6.48
1112

docs/Timeouts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ By default Redis Timeout exception(s) includes useful information, which can hel
8787
|qu | Queue-Awaiting-Write : {int}|There are x operations currently waiting in queue to write to the redis server.|
8888
|qs | Queue-Awaiting-Response : {int}|There are x operations currently awaiting replies from redis server.|
8989
|aw | Active-Writer: {bool}||
90-
|bw | Backlog-Writer: {enum} | Possible values are Inactive, Started, CheckingForWork, CheckingForTimeout, RecordingTimeout, WritingMessage, Flushing, MarkingInactive, RecordingWriteFailure, RecordingFault,SettingIdle,Faulted|
90+
|bw | Backlog-Writer: {enum} | Possible values are Inactive, Started, CheckingForWork, CheckingForTimeout, RecordingTimeout, WritingMessage, Flushing, MarkingInactive, RecordingWriteFailure, RecordingFault, SettingIdle, SpinningDown, Faulted|
9191
|rs | Read-State: {enum}|Possible values are NotStarted, Init, RanToCompletion, Faulted, ReadSync, ReadAsync, UpdateWriteTime, ProcessBuffer, MarkProcessed, TryParseResult, MatchResult, PubSubMessage, PubSubPMessage, Reconfigure, InvokePubSub, DequeueResult, ComputeResult, CompletePendingMessage, NA|
9292
|ws | Write-State: {enum}| Possible values are Initializing, Idle, Writing, Flushing, Flushed, NA|
9393
|in | Inbound-Bytes : {long}|there are x bytes waiting to be read from the input stream from redis|

src/StackExchange.Redis/PhysicalConnection.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -665,15 +665,24 @@ internal void OnBridgeHeartbeat()
665665
{
666666
// We only handle async timeouts here, synchronous timeouts are handled upstream.
667667
// Those sync timeouts happen in ConnectionMultiplexer.ExecuteSyncImpl() via Monitor.Wait.
668-
if (msg.ResultBoxIsAsync && msg.HasTimedOut(now, timeout, out var elapsed))
668+
if (msg.HasTimedOut(now, timeout, out var elapsed))
669669
{
670-
bool haveDeltas = msg.TryGetPhysicalState(out _, out _, out long sentDelta, out var receivedDelta) && sentDelta >= 0 && receivedDelta >= 0;
671-
var timeoutEx = ExceptionFactory.Timeout(multiplexer, haveDeltas
672-
? $"Timeout awaiting response (outbound={sentDelta >> 10}KiB, inbound={receivedDelta >> 10}KiB, {elapsed}ms elapsed, timeout is {timeout}ms)"
673-
: $"Timeout awaiting response ({elapsed}ms elapsed, timeout is {timeout}ms)", msg, server);
674-
multiplexer.OnMessageFaulted(msg, timeoutEx);
675-
msg.SetExceptionAndComplete(timeoutEx, bridge); // tell the message that it is doomed
676-
multiplexer.OnAsyncTimeout();
670+
if (msg.ResultBoxIsAsync)
671+
{
672+
bool haveDeltas = msg.TryGetPhysicalState(out _, out _, out long sentDelta, out var receivedDelta) && sentDelta >= 0 && receivedDelta >= 0;
673+
var timeoutEx = ExceptionFactory.Timeout(multiplexer, haveDeltas
674+
? $"Timeout awaiting response (outbound={sentDelta >> 10}KiB, inbound={receivedDelta >> 10}KiB, {elapsed}ms elapsed, timeout is {timeout}ms)"
675+
: $"Timeout awaiting response ({elapsed}ms elapsed, timeout is {timeout}ms)", msg, server);
676+
multiplexer.OnMessageFaulted(msg, timeoutEx);
677+
msg.SetExceptionAndComplete(timeoutEx, bridge); // tell the message that it is doomed
678+
multiplexer.OnAsyncTimeout();
679+
}
680+
}
681+
else
682+
{
683+
// This is a head-of-line queue, which means the first thing we hit that *hasn't* timed out means no more will timeout
684+
// and we can stop looping and release the lock early.
685+
break;
677686
}
678687
// Note: it is important that we **do not** remove the message unless we're tearing down the socket; that
679688
// would disrupt the chain for MatchResult; we just preemptively abort the message from the caller's

0 commit comments

Comments
 (0)