forked from StackExchange/StackExchange.Redis
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A suitably evil hack to avoid exec-sync
- Loading branch information
Showing
23 changed files
with
220 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,5 @@ | ||
The Dangers of Synchronous Continuations | ||
=== | ||
|
||
This is more of a "don't do this" guide. | ||
|
||
When you are using the `*Async` API of StackExchange.Redis, it will hand you either a `Task` or a `Task<T>` that represents your reply when it is available. From here you can do a few things: | ||
|
||
- you can ignore it (if you are going to do that, you should specify `CommandFlags.FireAndForget`, to reduce overhead) | ||
- you can asynchronously `await` it | ||
- you can synchronously `.Wait()` it (or `Task.WaitAll` or `.Result`, which do the same) | ||
- you can add a continuation with `.ContinueWith(...)` | ||
|
||
The last one of these has overloads that allow you to control the behavior, including one or more overloads that accept a [`TaskContinuationOptions`][1]. And one of these options is `ExecuteSynchronously`. | ||
|
||
To put it simply: **do not use `TaskContinuationOptions.ExecuteSynchronously` here**. On other tasks, sure. But please please do not use this option on the task that StackExchange.Redis hands you. The reason | ||
for this is that *if you do*, your continuation could end up interrupting the reader thread that is processing incoming redis data, and in a busy system blocking the reader will cause problems **very** quickly. | ||
|
||
If you *can't* control this (and I strongly suggest you try to), then you can change `ConfigurationOptions.AllowSynchronousContinuations` to `false` when creating your `ConnectionMultiplexer` (or add `;syncCont=false` to the configuration string); | ||
this will cause *all* tasks with continuations to be expressly moved *off* the reader thread and completed separately by the thread-pool. This *sounds* tempting, but in a busy system where the thread-pool is under heavy load, this can itself be problematic | ||
(especially if the active workers are currently blocking waiting on responses that can't be actioned because the completions are stuck waiting for a worker - a deadlock). Unfortunately, at the current time | ||
[there isn't much I can do about this](http://stackoverflow.com/q/22579206/23354), other than to advise you not to do it. | ||
|
||
To be clear: | ||
|
||
- `ContinueWith` by itself is fine | ||
- and I'm sure there are times when `TaskContinuationOptions.ExecuteSynchronously` makes perfect sense on other tasks | ||
- but please do not use `ContinueWith` with `TaskContinuationOptions.ExecuteSynchronously` on the tasks that StackExchange.Redis hands you | ||
|
||
[1]: http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions(v=vs.110).aspx | ||
Once, there was more content here; then [a suitably evil workaround was found](http://stackoverflow.com/a/22588431/23354). This page is not | ||
listed in the index, but remains for your curiosity. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,118 @@ | ||
using System.Threading.Tasks; | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NUnit.Framework; | ||
|
||
namespace StackExchange.Redis.Tests | ||
{ | ||
[TestFixture] | ||
public class TaskTests | ||
{ | ||
#if DEBUG | ||
[Test] | ||
public void CheckContinuationCheck() | ||
[TestCase(SourceOrign.NewTCS, false)] | ||
[TestCase(SourceOrign.Create, false)] | ||
[TestCase(SourceOrign.CreateDenyExec, true)] | ||
public void CheckContinuationCheck(SourceOrign origin, bool expected) | ||
{ | ||
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>(); | ||
|
||
Assert.IsTrue(TaskContinationCheck.NoContinuations(tcs.Task), "vanilla"); | ||
var source = Create<int>(origin); | ||
Assert.AreEqual(expected, TaskSource.IsSyncSafe(source.Task)); | ||
} | ||
|
||
tcs.Task.ContinueWith(x => { }); | ||
static TaskCompletionSource<T> Create<T>(SourceOrign origin) | ||
{ | ||
switch (origin) | ||
{ | ||
case SourceOrign.NewTCS: return new TaskCompletionSource<T>(); | ||
case SourceOrign.Create: return TaskSource.Create<T>(null); | ||
case SourceOrign.CreateDenyExec: return TaskSource.CreateDenyExecSync<T>(null); | ||
default: throw new ArgumentOutOfRangeException("origin"); | ||
} | ||
} | ||
[Test] | ||
// regular framework behaviour: 2 out of 3 cause hijack | ||
[TestCase(SourceOrign.NewTCS, AttachMode.ContinueWith, false)] | ||
[TestCase(SourceOrign.NewTCS, AttachMode.ContinueWithExecSync, true)] | ||
[TestCase(SourceOrign.NewTCS, AttachMode.Await, true)] | ||
// Create is just a wrapper of ^^^; expect the same | ||
[TestCase(SourceOrign.Create, AttachMode.ContinueWith, false)] | ||
[TestCase(SourceOrign.Create, AttachMode.ContinueWithExecSync, true)] | ||
[TestCase(SourceOrign.Create, AttachMode.Await, true)] | ||
// deny exec-sync: none should cause hijack | ||
[TestCase(SourceOrign.CreateDenyExec, AttachMode.ContinueWith, false)] | ||
[TestCase(SourceOrign.CreateDenyExec, AttachMode.ContinueWithExecSync, false)] | ||
[TestCase(SourceOrign.CreateDenyExec, AttachMode.Await, false)] | ||
public void TestContinuationHijacking(SourceOrign origin, AttachMode attachMode, bool expectHijack) | ||
{ | ||
TaskCompletionSource<int> source = Create<int>(origin); | ||
|
||
Assert.IsFalse(TaskContinationCheck.NoContinuations(tcs.Task), "dirty"); | ||
int settingThread = Environment.CurrentManagedThreadId; | ||
var state = new AwaitState(); | ||
state.Attach(source.Task, attachMode); | ||
source.TrySetResult(123); | ||
state.Wait(); // waits for the continuation to run | ||
int from = state.Thread; | ||
Assert.AreNotEqual(-1, from, "not set"); | ||
if (expectHijack) | ||
{ | ||
Assert.AreEqual(settingThread, from, "expected hijack; didn't happen"); | ||
} | ||
else | ||
{ | ||
Assert.AreNotEqual(settingThread, from, "setter was hijacked"); | ||
} | ||
} | ||
public enum SourceOrign | ||
{ | ||
NewTCS, | ||
Create, | ||
CreateDenyExec | ||
} | ||
public enum AttachMode | ||
{ | ||
ContinueWith, | ||
ContinueWithExecSync, | ||
Await | ||
} | ||
class AwaitState | ||
{ | ||
public int Thread { get { return continuationThread; } } | ||
volatile int continuationThread = -1; | ||
private ManualResetEventSlim evt = new ManualResetEventSlim(); | ||
public void Wait() | ||
{ | ||
if (!evt.Wait(5000)) throw new TimeoutException(); | ||
} | ||
public void Attach(Task task, AttachMode attachMode) | ||
{ | ||
switch(attachMode) | ||
{ | ||
case AttachMode.ContinueWith: | ||
task.ContinueWith(Continue); | ||
break; | ||
case AttachMode.ContinueWithExecSync: | ||
task.ContinueWith(Continue, TaskContinuationOptions.ExecuteSynchronously); | ||
break; | ||
case AttachMode.Await: | ||
DoAwait(task); | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException("attachMode"); | ||
} | ||
} | ||
private void Continue(Task task) | ||
{ | ||
continuationThread = Environment.CurrentManagedThreadId; | ||
evt.Set(); | ||
} | ||
private async void DoAwait(Task task) | ||
{ | ||
await task; | ||
continuationThread = Environment.CurrentManagedThreadId; | ||
evt.Set(); | ||
} | ||
} | ||
#endif | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.