Skip to content

Commit

Permalink
Added AutoTransport which is will pick the best transport supported b…
Browse files Browse the repository at this point in the history
…y the client and server.

- Added ConnectTimeout to ServerSentEventsTransport.
- Handle stopping the SSE transport better.
- Added Wp7 sample.
- Added Delay event based on threading timers to the TaskAsyncHelper.
  • Loading branch information
davidfowl committed Jan 15, 2012
1 parent a4eb02d commit 4b4db04
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 12 deletions.
2 changes: 1 addition & 1 deletion SignalR.Client.WP7.Sample/MainPage.xaml.cs
Expand Up @@ -28,7 +28,7 @@ public MainPage()
});
};

connection.Start(Transport.LongPolling).ContinueWith(task =>
connection.Start().ContinueWith(task =>
{
Debug.WriteLine("ERROR: {0}", task.Exception.GetBaseException().Message);
},
Expand Down
3 changes: 3 additions & 0 deletions SignalR.Client.WP7/SignalR.Client.WP7.csproj
Expand Up @@ -103,6 +103,9 @@
<Compile Include="..\SignalR.Client\NegotiationResponse.cs">
<Link>NegotiationResponse.cs</Link>
</Compile>
<Compile Include="..\SignalR.Client\Transports\AutoTransport.cs">
<Link>Transports\AutoTransport.cs</Link>
</Compile>
<Compile Include="..\SignalR.Client\Transports\HttpBasedTransport.cs">
<Link>Transports\HttpBasedTransport.cs</Link>
</Compile>
Expand Down
3 changes: 3 additions & 0 deletions SignalR.Client.WP71/SignalR.Client.WP71.csproj
Expand Up @@ -105,6 +105,9 @@
<Compile Include="..\SignalR.Client\NegotiationResponse.cs">
<Link>NegotiationResponse.cs</Link>
</Compile>
<Compile Include="..\SignalR.Client\Transports\AutoTransport.cs">
<Link>Transports\AutoTransport.cs</Link>
</Compile>
<Compile Include="..\SignalR.Client\Transports\HttpBasedTransport.cs">
<Link>Transports\HttpBasedTransport.cs</Link>
</Compile>
Expand Down
6 changes: 5 additions & 1 deletion SignalR.Client/Connection.cs
Expand Up @@ -18,6 +18,9 @@ public class Connection : IConnection
private IClientTransport _transport;
private bool _initialized;

// Used by transports to sync
internal int _initializedCalled;

public event Action<string> Received;
public event Action<Exception> Error;
public event Action Closed;
Expand Down Expand Up @@ -52,7 +55,8 @@ public Connection(string url)

public Task Start()
{
return Start(Transport.ServerSentEvents);
// Pick the best transport supported by the client
return Start(new AutoTransport());
}

public virtual Task Start(IClientTransport transport)
Expand Down
1 change: 1 addition & 0 deletions SignalR.Client/SignalR.Client.csproj
Expand Up @@ -73,6 +73,7 @@
<Compile Include="ObservableConnection.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Hubs\HubProxyExtensions.cs" />
<Compile Include="Transports\AutoTransport.cs" />
<Compile Include="Transports\HttpBasedTransport.cs" />
<Compile Include="Transports\IClientTransport.cs" />
<Compile Include="Transports\LongPollingTransport.cs" />
Expand Down
66 changes: 66 additions & 0 deletions SignalR.Client/Transports/AutoTransport.cs
@@ -0,0 +1,66 @@
using System.Threading.Tasks;

namespace SignalR.Client.Transports
{
public class AutoTransport : IClientTransport
{
// Transport that's in use
private IClientTransport _transport;

// List of transports in fallback order
private static readonly IClientTransport[] _transports = new[] { Transport.ServerSentEvents, Transport.LongPolling };

public Task Start(Connection connection, string data)
{
var tcs = new TaskCompletionSource<object>();

// Resolve the transport
ResolveTransport(connection, data, tcs, 0);

return tcs.Task;
}

private void ResolveTransport(Connection connection, string data, TaskCompletionSource<object> tcs, int index)
{
// Pick the current transport
IClientTransport transport = _transports[index];

transport.Start(connection, data).ContinueWith(task =>
{
if (task.IsFaulted)
{
// If that transport fails to initialize then fallback
var next = index + 1;
if (next < _transports.Length)
{
// Try the next transport
ResolveTransport(connection, data, tcs, next);
}
else
{
// If there's nothing else to try then just fail
tcs.SetException(task.Exception);
}
}
else
{
// Set the active transport
_transport = transport;
// Complete the process
tcs.SetResult(null);
}
});
}

public Task<T> Send<T>(Connection connection, string data)
{
return _transport.Send<T>(connection, data);
}

public void Stop(Connection connection)
{
_transport.Stop(connection);
}
}
}
49 changes: 41 additions & 8 deletions SignalR.Client/Transports/ServerSentEventsTransport.cs
Expand Up @@ -10,13 +10,20 @@ namespace SignalR.Client.Transports
public class ServerSentEventsTransport : HttpBasedTransport
{
private const string ReaderKey = "sse.reader";

private static readonly TimeSpan ReconnectDelay = TimeSpan.FromSeconds(2);

public ServerSentEventsTransport()
: base("serverSentEvents")
{
ConnectionTimeout = TimeSpan.FromSeconds(2);
}

/// <summary>
/// Time allowed before failing the connect request
/// </summary>
public TimeSpan ConnectionTimeout { get; set; }

protected override void OnStart(Connection connection, string data, Action initializeCallback, Action<Exception> errorCallback)
{
OpenConnection(connection, data, initializeCallback, errorCallback);
Expand All @@ -41,14 +48,19 @@ private void OpenConnection(Connection connection, string data, Action initializ
{
if (task.IsFaulted)
{
if (errorCallback != null)
{
errorCallback(task.Exception);
}
else
var exception = task.Exception.GetBaseException();
if (!IsRequestAborted(exception) &&
Interlocked.CompareExchange(ref connection._initializedCalled, 0, 0) == 0)
{
// Raise the error event if we failed to reconnect
connection.OnError(task.Exception.GetBaseException());
if (errorCallback != null)
{
errorCallback(exception);
}
else
{
// Raise the error event if we failed to reconnect
connection.OnError(exception);
}
}
}
else
Expand All @@ -57,7 +69,13 @@ private void OpenConnection(Connection connection, string data, Action initializ
var stream = task.Result.GetResponseStream();
var reader = new AsyncStreamReader(stream,
connection,
initializeCallback,
() =>
{
if (Interlocked.CompareExchange(ref connection._initializedCalled, 1, 0) == 0)
{
initializeCallback();
}
},
() =>
{
// Wait for a bit before reconnecting
Expand All @@ -72,6 +90,21 @@ private void OpenConnection(Connection connection, string data, Action initializ
connection.Items[ReaderKey] = reader;
}
});

if (initializeCallback != null)
{
TaskAsyncHelper.Delay(ConnectionTimeout).Then(() =>
{
if (Interlocked.CompareExchange(ref connection._initializedCalled, 1, 0) == 0)
{
// Stop the connection
Stop(connection);
// Connection timeout occured
errorCallback(new TimeoutException());
}
});
}
}

protected override void OnBeforeAbort(Connection connection)
Expand Down
4 changes: 2 additions & 2 deletions SignalR.Samples/SignalR.Samples.csproj
Expand Up @@ -12,7 +12,7 @@
<RootNamespace>SignalR.Samples</RootNamespace>
<AssemblyName>SignalR.Samples</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<UseIISExpress>false</UseIISExpress>
<UseIISExpress>true</UseIISExpress>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
Expand Down Expand Up @@ -244,7 +244,7 @@
<AutoAssignPort>False</AutoAssignPort>
<DevelopmentServerPort>40476</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost/SignalR.Samples</IISUrl>
<IISUrl>http://localhost:40476/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
Expand Down
66 changes: 66 additions & 0 deletions SignalR.WP7.sln
Expand Up @@ -26,46 +26,112 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalR.ScaleOut", "SignalR
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalR.AspNet", "SignalR.AspNet\SignalR.AspNet.csproj", "{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalR.Client.Samples", "SignalR.Client.Samples\SignalR.Client.Samples.csproj", "{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Debug|x86.ActiveCfg = Debug|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Release|Any CPU.Build.0 = Release|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1B9A82C4-BCA1-4834-A33E-226F17BE070B}.Release|x86.ActiveCfg = Release|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Debug|x86.ActiveCfg = Debug|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Release|Any CPU.Build.0 = Release|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EB46B9C6-EE37-48F9-835E-E49580E40E0A}.Release|x86.ActiveCfg = Release|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Release|Any CPU.Build.0 = Release|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2D23C742-9886-4079-A70F-05C7E4401969}.Release|x86.ActiveCfg = Release|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Debug|x86.ActiveCfg = Debug|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Release|Any CPU.Build.0 = Release|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1B2BD09D-ECFF-427F-BA58-C59A01EE6A2C}.Release|x86.ActiveCfg = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Debug|x86.ActiveCfg = Debug|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Any CPU.Build.0 = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Any CPU.Deploy.0 = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{E059F6F3-E9D5-4113-AF2B-C2D19CE7FAFF}.Release|x86.ActiveCfg = Release|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Debug|x86.ActiveCfg = Debug|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Release|Any CPU.Build.0 = Release|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1EA34A62-E03E-45CF-A9C9-82D2DA0FCD82}.Release|x86.ActiveCfg = Release|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Debug|x86.ActiveCfg = Debug|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Release|Any CPU.Build.0 = Release|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{32D16B36-970E-4CF2-B954-78CB1833CBC1}.Release|x86.ActiveCfg = Release|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Debug|x86.ActiveCfg = Debug|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Release|Any CPU.Build.0 = Release|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{0E513AE2-BEA8-40CF-B9F2-102B351F2FB2}.Release|x86.ActiveCfg = Release|Any CPU
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Debug|Any CPU.ActiveCfg = Debug|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Debug|Mixed Platforms.Build.0 = Debug|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Debug|x86.ActiveCfg = Debug|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Debug|x86.Build.0 = Debug|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Release|Any CPU.ActiveCfg = Release|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Release|Mixed Platforms.ActiveCfg = Release|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Release|Mixed Platforms.Build.0 = Release|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Release|x86.ActiveCfg = Release|x86
{E0223FDC-0982-4D80-B6C2-BFAA6C6748C5}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 17 additions & 0 deletions SignalR/TaskAsyncHelper.cs
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading;

namespace SignalR
{
Expand Down Expand Up @@ -385,6 +386,22 @@ public static Task<T> FastUnwrap<T>(this Task<Task<T>> task)
return innerTask ?? task.Unwrap();
}

public static Task Delay(TimeSpan timeOut)
{
var tcs = new TaskCompletionSource<object>();

var timer = new Timer(tcs.SetResult,
null,
timeOut,
TimeSpan.FromMilliseconds(-1));

return tcs.Task.ContinueWith(_ =>
{
timer.Dispose();
},
TaskContinuationOptions.ExecuteSynchronously);
}

public static Task AllSucceeded(this Task[] tasks, Action continuation)
{
return AllSucceeded(tasks, _ => continuation());
Expand Down

0 comments on commit 4b4db04

Please sign in to comment.