Commit de41b0e6 authored by Marc Gravell's avatar Marc Gravell

Use RunContinuationsAsynchronously when PLAT_SAFE_CONTINUATIONS is defined (NET46, DNXCORE50, etc)

parent 2bca2a52
......@@ -20,9 +20,12 @@
</group>
<group targetFramework="net45">
</group>
<group targetFramework="net46">
</group>
</dependencies>
</metadata>
<files>
<file src="StackExchange.Redis_Net46\bin.snk\Release\StackExchange.Redis*.*" target="lib\net46" />
<file src="StackExchange.Redis\bin.snk\Release\StackExchange.Redis*.*" target="lib\net45" />
<file src="StackExchange.Redis_Net40\bin.snk\Release\StackExchange.Redis*.*" target="lib\net40" />
</files>
......
......@@ -9,24 +9,26 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>StackExchange.Redis.Tests</RootNamespace>
<AssemblyName>StackExchange.Redis.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG;PLAT_SAFE_CONTINUATIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE;PLAT_SAFE_CONTINUATIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
......@@ -139,13 +141,13 @@
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj">
<Project>{7cec07f2-8c03-4c42-b048-738b215824c1}</Project>
<Name>StackExchange.Redis</Name>
</ProjectReference>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
<ProjectReference Include="..\StackExchange.Redis_Net46\StackExchange.Redis_Net46.csproj">
<Project>{8c473a6f-b0de-4add-88f8-c41b441e407c}</Project>
<Name>StackExchange.Redis_Net46</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
......
......@@ -9,6 +9,8 @@ namespace StackExchange.Redis.Tests
public class TaskTests
{
#if DEBUG
#if !PLAT_SAFE_CONTINUATIONS // IsSyncSafe doesn't exist if PLAT_SAFE_CONTINUATIONS is defined
[Test]
[TestCase(SourceOrign.NewTCS, false)]
[TestCase(SourceOrign.Create, false)]
......@@ -18,7 +20,7 @@ public void VerifyIsSyncSafe(SourceOrign origin, bool expected)
var source = Create<int>(origin);
Assert.AreEqual(expected, TaskSource.IsSyncSafe(source.Task));
}
#endif
static TaskCompletionSource<T> Create<T>(SourceOrign origin)
{
switch (origin)
......
......@@ -20,9 +20,12 @@
</group>
<group targetFramework="net45">
</group>
<group targetFramework="net46">
</group>
</dependencies>
</metadata>
<files>
<file src="StackExchange.Redis_Net46\bin\Release\StackExchange.Redis*.*" target="lib\net46" />
<file src="StackExchange.Redis\bin\Release\StackExchange.Redis*.*" target="lib\net45" />
<file src="StackExchange.Redis_Net40\bin\Release\StackExchange.Redis*.*" target="lib\net40" />
</files>
......

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
VisualStudioVersion = 14.0.24606.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis", "StackExchange.Redis\StackExchange.Redis.csproj", "{7CEC07F2-8C03-4C42-B048-738B215824C1}"
EndProject
......@@ -56,6 +56,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.StrongN
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net40.StrongName", "StackExchange.Redis_Net40\StackExchange.Redis_Net40.StrongName.csproj", "{75CED009-AAC6-4AC1-9C38-A0530619062D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.csproj", "{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis_Net46.StrongName", "StackExchange.Redis_Net46\StackExchange.Redis_Net46.StrongName.csproj", "{8CE5D027-E332-42DD-BA54-16310DCD529C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -155,6 +159,26 @@ Global
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Release|Any CPU.Build.0 = Release|Any CPU
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Verbose|Any CPU.ActiveCfg = Release|Any CPU
{75CED009-AAC6-4AC1-9C38-A0530619062D}.Verbose|Any CPU.Build.0 = Release|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Log Output|Any CPU.ActiveCfg = Mono|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Log Output|Any CPU.Build.0 = Mono|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Mono|Any CPU.Build.0 = Mono|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Release|Any CPU.Build.0 = Release|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Verbose|Any CPU.ActiveCfg = Mono|Any CPU
{8C473A6F-B0DE-4ADD-88F8-C41B441E407C}.Verbose|Any CPU.Build.0 = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Log Output|Any CPU.ActiveCfg = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Log Output|Any CPU.Build.0 = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Mono|Any CPU.Build.0 = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Release|Any CPU.Build.0 = Release|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.ActiveCfg = Mono|Any CPU
{8CE5D027-E332-42DD-BA54-16310DCD529C}.Verbose|Any CPU.Build.0 = Mono|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
......@@ -99,8 +99,10 @@ public override bool TryComplete(bool isAsync)
{
if (stateOrCompletionSource is TaskCompletionSource<T>)
{
var tcs = (TaskCompletionSource<T>)stateOrCompletionSource;
if (isAsync || TaskSource.IsSyncSafe(tcs.Task))
var tcs = (TaskCompletionSource<T>)stateOrCompletionSource;
#if !PLAT_SAFE_CONTINUATIONS // we don't need to check in this scenario
if (isAsync || TaskSource.IsSyncSafe(tcs.Task))
#endif
{
T val;
Exception ex;
......@@ -116,11 +118,13 @@ public override bool TryComplete(bool isAsync)
GC.SuppressFinalize(tcs.Task);
}
return true;
}
}
#if !PLAT_SAFE_CONTINUATIONS
else
{ // looks like continuations; push to async to preserve the reader thread
return false;
}
}
#endif
}
else
{
......
......@@ -16,75 +16,85 @@ namespace StackExchange.Redis
#endif
static class TaskSource
{
#if !PLAT_SAFE_CONTINUATIONS
// on .NET < 4.6, it was possible to have threads hijacked; this is no longer a problem in 4.6 and core-clr 5,
// thanks to the new TaskCreationOptions.RunContinuationsAsynchronously, however we still need to be a little
// "test and react", as we could be targeting 4.5 but running on a 4.6 machine, in which case *it can still
// work the magic* (thanks to over-the-top install)
/// <summary>
/// Indicates whether the specified task will not hijack threads when results are set
/// </summary>
public static readonly Func<Task, bool> IsSyncSafe;
static TaskSource()
{
static TaskSource()
{
try
{
Type taskType = typeof(Task);
FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic);
Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic);
if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null)
{
var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true);
var il = method.GetILGenerator();
//var hasContinuation = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel();
// check if null
il.Emit(OpCodes.Brtrue_S, nonNull);
il.MarkLabel(goodReturn);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
{
Type taskType = typeof(Task);
FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic);
Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic);
if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null)
{
var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true);
var il = method.GetILGenerator();
//var hasContinuation = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel();
// check if null
il.Emit(OpCodes.Brtrue_S, nonNull);
il.MarkLabel(goodReturn);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Ret);
// check if is a SetOnInvokeMres - if so, we're OK
il.MarkLabel(nonNull);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
il.Emit(OpCodes.Isinst, safeScenario);
il.Emit(OpCodes.Brtrue_S, goodReturn);
// check if is a SetOnInvokeMres - if so, we're OK
il.MarkLabel(nonNull);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, continuationField);
il.Emit(OpCodes.Isinst, safeScenario);
il.Emit(OpCodes.Brtrue_S, goodReturn);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ret);
IsSyncSafe = (Func<Task, bool>)method.CreateDelegate(typeof(Func<Task, bool>));
IsSyncSafe = (Func<Task, bool>)method.CreateDelegate(typeof(Func<Task, bool>));
// and test them (check for an exception etc)
var tcs = new TaskCompletionSource<int>();
bool expectTrue = IsSyncSafe(tcs.Task);
tcs.Task.ContinueWith(delegate { });
bool expectFalse = IsSyncSafe(tcs.Task);
tcs.SetResult(0);
if(!expectTrue || expectFalse)
{
Debug.WriteLine("IsSyncSafe reported incorrectly!");
Trace.WriteLine("IsSyncSafe reported incorrectly!");
// revert to not trusting /them
IsSyncSafe = null;
}
}
}
catch(Exception ex)
{
Debug.WriteLine(ex.Message);
Trace.WriteLine(ex.Message);
IsSyncSafe = null;
}
// and test them (check for an exception etc)
var tcs = new TaskCompletionSource<int>();
bool expectTrue = IsSyncSafe(tcs.Task);
tcs.Task.ContinueWith(delegate { });
bool expectFalse = IsSyncSafe(tcs.Task);
tcs.SetResult(0);
if (!expectTrue || expectFalse)
{
Debug.WriteLine("IsSyncSafe reported incorrectly!");
Trace.WriteLine("IsSyncSafe reported incorrectly!");
// revert to not trusting /them
IsSyncSafe = null;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
Trace.WriteLine(ex.Message);
IsSyncSafe = null;
}
if (IsSyncSafe == null)
IsSyncSafe = t => false; // assume: not
}
#endif
/// <summary>
/// Create a new TaskCompletion source
/// </summary>
public static TaskCompletionSource<T> Create<T>(object asyncState)
{
return new TaskCompletionSource<T>(asyncState);
}
#if PLAT_SAFE_CONTINUATIONS
return new TaskCompletionSource<T>(asyncState, TaskCreationOptions.RunContinuationsAsynchronously);
#else
return new TaskCompletionSource<T>(asyncState, TaskCreationOptions.None);
#endif
}
/// <summary>
/// Create a new TaskCompletionSource that will not allow result-setting threads to be hijacked
......
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
<?xml version="1.0" encoding="utf-8"?>
<packages>
</packages>
\ 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