From 4b4db04e92b39f799c38cf7600f8decd73512086 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 14 Jan 2012 18:55:00 -0800 Subject: [PATCH] Added AutoTransport which is will pick the best transport supported by 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. --- SignalR.Client.WP7.Sample/MainPage.xaml.cs | 2 +- SignalR.Client.WP7/SignalR.Client.WP7.csproj | 3 + .../SignalR.Client.WP71.csproj | 3 + SignalR.Client/Connection.cs | 6 +- SignalR.Client/SignalR.Client.csproj | 1 + SignalR.Client/Transports/AutoTransport.cs | 66 +++++++++++++++++++ .../Transports/ServerSentEventsTransport.cs | 49 +++++++++++--- SignalR.Samples/SignalR.Samples.csproj | 4 +- SignalR.WP7.sln | 66 +++++++++++++++++++ SignalR/TaskAsyncHelper.cs | 17 +++++ 10 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 SignalR.Client/Transports/AutoTransport.cs diff --git a/SignalR.Client.WP7.Sample/MainPage.xaml.cs b/SignalR.Client.WP7.Sample/MainPage.xaml.cs index 3a67ed67e7..3cf9e84a2f 100644 --- a/SignalR.Client.WP7.Sample/MainPage.xaml.cs +++ b/SignalR.Client.WP7.Sample/MainPage.xaml.cs @@ -28,7 +28,7 @@ public MainPage() }); }; - connection.Start(Transport.LongPolling).ContinueWith(task => + connection.Start().ContinueWith(task => { Debug.WriteLine("ERROR: {0}", task.Exception.GetBaseException().Message); }, diff --git a/SignalR.Client.WP7/SignalR.Client.WP7.csproj b/SignalR.Client.WP7/SignalR.Client.WP7.csproj index 3bd0a63093..d289c006ff 100644 --- a/SignalR.Client.WP7/SignalR.Client.WP7.csproj +++ b/SignalR.Client.WP7/SignalR.Client.WP7.csproj @@ -103,6 +103,9 @@ NegotiationResponse.cs + + Transports\AutoTransport.cs + Transports\HttpBasedTransport.cs diff --git a/SignalR.Client.WP71/SignalR.Client.WP71.csproj b/SignalR.Client.WP71/SignalR.Client.WP71.csproj index b8efa82ea7..b69c1ed803 100644 --- a/SignalR.Client.WP71/SignalR.Client.WP71.csproj +++ b/SignalR.Client.WP71/SignalR.Client.WP71.csproj @@ -105,6 +105,9 @@ NegotiationResponse.cs + + Transports\AutoTransport.cs + Transports\HttpBasedTransport.cs diff --git a/SignalR.Client/Connection.cs b/SignalR.Client/Connection.cs index 86e3828bed..379f8511e4 100644 --- a/SignalR.Client/Connection.cs +++ b/SignalR.Client/Connection.cs @@ -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 Received; public event Action Error; public event Action Closed; @@ -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) diff --git a/SignalR.Client/SignalR.Client.csproj b/SignalR.Client/SignalR.Client.csproj index 0d4dbc7204..286bc0c75a 100644 --- a/SignalR.Client/SignalR.Client.csproj +++ b/SignalR.Client/SignalR.Client.csproj @@ -73,6 +73,7 @@ + diff --git a/SignalR.Client/Transports/AutoTransport.cs b/SignalR.Client/Transports/AutoTransport.cs new file mode 100644 index 0000000000..60867460da --- /dev/null +++ b/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(); + + // Resolve the transport + ResolveTransport(connection, data, tcs, 0); + + return tcs.Task; + } + + private void ResolveTransport(Connection connection, string data, TaskCompletionSource 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 Send(Connection connection, string data) + { + return _transport.Send(connection, data); + } + + public void Stop(Connection connection) + { + _transport.Stop(connection); + } + } +} diff --git a/SignalR.Client/Transports/ServerSentEventsTransport.cs b/SignalR.Client/Transports/ServerSentEventsTransport.cs index 9cf2c2e85a..b967c7aaf0 100644 --- a/SignalR.Client/Transports/ServerSentEventsTransport.cs +++ b/SignalR.Client/Transports/ServerSentEventsTransport.cs @@ -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); } + /// + /// Time allowed before failing the connect request + /// + public TimeSpan ConnectionTimeout { get; set; } + protected override void OnStart(Connection connection, string data, Action initializeCallback, Action errorCallback) { OpenConnection(connection, data, initializeCallback, errorCallback); @@ -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 @@ -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 @@ -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) diff --git a/SignalR.Samples/SignalR.Samples.csproj b/SignalR.Samples/SignalR.Samples.csproj index 9999b888ab..b795279296 100644 --- a/SignalR.Samples/SignalR.Samples.csproj +++ b/SignalR.Samples/SignalR.Samples.csproj @@ -12,7 +12,7 @@ SignalR.Samples SignalR.Samples v4.0 - false + true ..\ true @@ -244,7 +244,7 @@ False 40476 / - http://localhost/SignalR.Samples + http://localhost:40476/ False False diff --git a/SignalR.WP7.sln b/SignalR.WP7.sln index db6806fae5..80df54f700 100644 --- a/SignalR.WP7.sln +++ b/SignalR.WP7.sln @@ -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 diff --git a/SignalR/TaskAsyncHelper.cs b/SignalR/TaskAsyncHelper.cs index 69ad216ff7..f35173a3db 100644 --- a/SignalR/TaskAsyncHelper.cs +++ b/SignalR/TaskAsyncHelper.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using System.Threading; namespace SignalR { @@ -385,6 +386,22 @@ public static Task FastUnwrap(this Task> task) return innerTask ?? task.Unwrap(); } + public static Task Delay(TimeSpan timeOut) + { + var tcs = new TaskCompletionSource(); + + 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());