public TransportInitializationHandler(IHttpClient httpClient,
                                              IConnection connection,
                                              string connectionData,
                                              string transport,
                                              CancellationToken disconnectToken)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            _connection     = connection;
            _httpClient     = httpClient;
            _connectionData = connectionData;
            _transport      = transport;

            _initializationTask    = new TaskCompletionSource <object>();
            _initializationInvoker = new ThreadSafeInvoker();

            // Default event
            OnFailure = () => { };

            // We want to fail if the disconnect token is tripped while we're waiting on initialization
            _tokenCleanup = disconnectToken.SafeRegister(_ =>
            {
                Fail();
            },
                                                         state: null);

            TaskAsyncHelper.Delay(connection.TotalTransportConnectTimeout).Then(() =>
            {
                Fail(new TimeoutException(Resources.Error_TransportTimedOutTryingToConnect));
            });
        }
 private void TryDelayedReconnect(IConnection connection, ThreadSafeInvoker reconnectInvoker)
 {
     if (IsReconnecting(connection))
     {
         // Fire the reconnect event after the delay.
         TaskAsyncHelper.Delay(ReconnectDelay)
         .Then(() => TryReconnect(connection, reconnectInvoker));
     }
 }
예제 #3
0
        // TODO: Refactor into a helper class
        private static IDisposable SetTimeout(TimeSpan delay, Action operation)
        {
            var cancellableInvoker = new ThreadSafeInvoker();

            TaskAsyncHelper.Delay(delay).Then(() => cancellableInvoker.Invoke(operation));

            // Disposing this return value will cancel the operation if it has not already been invoked.
            return(new DisposableAction(() => cancellableInvoker.Invoke()));
        }
예제 #4
0
 private void TryDelayedReconnect(IConnection connection, ThreadSafeInvoker reconnectInvoker)
 {
     if (IsReconnecting(connection))
     {
         TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
         {
             TryReconnect(connection, reconnectInvoker);
         });
     }
 }
예제 #5
0
 public void Initialize()
 {
     TaskAsyncHelper.Delay(_assumeSuccessAfter).Then(() =>
     {
         _callbackInvoker.Invoke(() =>
         {
             Initialized();
             _initializeCallback();
         });
     });
 }
예제 #6
0
        private bool ContinueReceiving(IAsyncResult asyncResult, ReceiverContext receiverContext)
        {
            bool     shouldContinue = true;
            TimeSpan backoffAmount  = BackoffAmount;

            try
            {
                IEnumerable <BrokeredMessage> messages = receiverContext.Receiver.EndReceiveBatch(asyncResult);

                receiverContext.OnMessage(messages);

                // Reset the receive timeout if it changed
                receiverContext.ReceiveTimeout = DefaultReadTimeout;
            }
            catch (ServerBusyException ex)
            {
                receiverContext.OnError(ex);

                // Too busy so back off
                shouldContinue = false;
            }
            catch (OperationCanceledException)
            {
                // This means the channel is closed
                _trace.TraceError("Receiving messages from the service bus threw an OperationCanceledException, most likely due to a closed channel.");

                return(false);
            }
            catch (Exception ex)
            {
                receiverContext.OnError(ex);

                shouldContinue = false;

                // TODO: Exponential backoff
                backoffAmount = ErrorBackOffAmount;

                // After an error, we want to adjust the timeout so that we
                // can recover as quickly as possible even if there's no message
                receiverContext.ReceiveTimeout = ErrorReadTimeout;
            }

            if (!shouldContinue)
            {
                TaskAsyncHelper.Delay(backoffAmount)
                .Then(ctx => ProcessMessages(ctx), receiverContext);

                return(false);
            }

            return(true);
        }
예제 #7
0
 private void Reconnect(IConnection connection, string data)
 {
     // Wait for a bit before reconnecting
     TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
     {
         if (connection.State == ConnectionState.Reconnecting ||
             connection.ChangeState(ConnectionState.Connected, ConnectionState.Reconnecting))
         {
             // Now attempt a reconnect
             OpenConnection(connection, data, initializeCallback: null, errorCallback: null);
         }
     });
 }
예제 #8
0
        private void Reconnect(IConnection connection, string data)
        {
            if (CancellationToken.IsCancellationRequested)
            {
                return;
            }

            // Wait for a bit before reconnecting
            TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
            {
                // Now attempt a reconnect
                OpenConnection(connection, data, initializeCallback: null, errorCallback: null);
            });
        }
예제 #9
0
 private void Reconnect(IConnection connection, string data, CancellationToken disconnectToken)
 {
     // Wait for a bit before reconnecting
     TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
     {
         // FIX: Race if Connection is stopped and completely restarted between checking the token and calling
         //      connection.EnsureReconnecting()
         if (!disconnectToken.IsCancellationRequested && connection.EnsureReconnecting())
         {
             // Now attempt a reconnect
             OpenConnection(connection, data, disconnectToken, initializeCallback: null, errorCallback: null);
         }
     });
 }
        public TransportInitializationHandler(IHttpClient httpClient,
                                              IConnection connection,
                                              string connectionData,
                                              string transport,
                                              CancellationToken disconnectToken,
                                              TransportHelper transportHelper)
        {
            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }

            _connection      = connection;
            _httpClient      = httpClient;
            _connectionData  = connectionData;
            _transport       = transport;
            _transportHelper = transportHelper;

            _initializationTask    = new DispatchingTaskCompletionSource <object>();
            _initializationInvoker = new ThreadSafeInvoker();

            // Default event
            OnFailure = () => { };

            // We want to fail if the disconnect token is tripped while we're waiting on initialization
            try
            {
                _tokenCleanup = disconnectToken.SafeRegister(
                    _ => Fail(new OperationCanceledException(Resources.Error_ConnectionCancelled, disconnectToken)),
                    state: null);
            }
            catch (ObjectDisposedException)
            {
                // We only dispose this token after cancelling it, so consider this cancellation.
                // The ODE is only thrown on .NET 4.5.2 and below (.NET 4.6 no longer throws ODE from CTS.Register)
                Fail(new OperationCanceledException(Resources.Error_ConnectionCancelled, disconnectToken));
            }

            TaskAsyncHelper.Delay(connection.TotalTransportConnectTimeout)
            .Then(() =>
            {
                // don't timeout once connect request has finished
                if (Interlocked.CompareExchange(ref _state, InitializationState.Failed, InitializationState.Initial) ==
                    InitializationState.Initial)
                {
                    Fail(new TimeoutException(Resources.Error_TransportTimedOutTryingToConnect));
                }
            });
        }
예제 #11
0
            public void AsyncStartShouldBeConnected()
            {
                var connection = new Client.Connection("http://test");
                var transport  = new Mock <IClientTransport>();

                transport.Setup(m => m.Negotiate(connection, It.IsAny <string>()))
                .Returns(TaskAsyncHelper.FromResult(new NegotiationResponse
                {
                    ProtocolVersion = connection.Protocol.ToString(),
                    ConnectionId    = "Something"
                }));

                transport.Setup(m => m.Start(connection, null, It.IsAny <CancellationToken>()))
                .Returns(TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(100)));

                Assert.True(connection.Start(transport.Object).Wait(TimeSpan.FromSeconds(5)), "Start hung.");
                Assert.Equal(ConnectionState.Connected, connection.State);
            }
        private Task OnAfterPoll(Exception exception)
        {
            if (AbortHandler.TryCompleteAbort())
            {
                // Abort() was called, so don't reconnect
                StopPolling();
            }
            else
            {
                _reconnectInvoker = new ThreadSafeInvoker();

                if (exception != null)
                {
                    // Delay polling by the error delay
                    return(TaskAsyncHelper.Delay(ErrorDelay));
                }
            }

            return(TaskAsyncHelper.Empty);
        }
예제 #13
0
        public TransportInitializationHandler(TimeSpan failureTimeout, CancellationToken disconnectToken)
        {
            _initializationTask    = new TaskCompletionSource <object>();
            _initializationInvoker = new ThreadSafeInvoker();

            // Default event
            OnFailure = () => { };

            // We want to fail if the disconnect token is tripped while we're waiting on initialization
            _tokenCleanup = disconnectToken.SafeRegister(_ =>
            {
                Fail();
            },
                                                         state: null);

            TaskAsyncHelper.Delay(failureTimeout).Then(() =>
            {
                Fail(new TimeoutException(Resources.Error_TransportTimedOutTryingToConnect));
            });
        }
예제 #14
0
        private async void OnDisconnected()
        {
            await TaskAsyncHelper.Delay(TimeSpan.FromSeconds(5));

            try
            {
                await _connection.Start(_transportFactory());

                // Join JabbR
                await _chat.Invoke("Join", false);
            }
            catch (Exception ex)
            {
                try
                {
                    _connection.Trace(TraceLevels.Events, ex.Message);
                }
                catch (Exception)
                {
                    // For some reason the Trace method can throw :(
                }
            }
        }
예제 #15
0
            public void AsyncStartShouldFailIfTransportStartFails()
            {
                var connection = new Client.Connection("http://test");
                var transport  = new Mock <IClientTransport>();
                var ex         = new Exception();

                transport.Setup(m => m.Negotiate(connection, It.IsAny <string>()))
                .Returns(TaskAsyncHelper.FromResult(new NegotiationResponse
                {
                    ProtocolVersion = connection.Protocol.ToString(),
                    ConnectionId    = "Something"
                }));

                transport.Setup(m => m.Start(connection, null, It.IsAny <CancellationToken>()))
                .Returns(TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(100)).Then(() =>
                {
                    throw ex;
                }));

                var aggEx = Assert.Throws <AggregateException>(() => connection.Start(transport.Object).Wait());

                Assert.Equal(aggEx.Unwrap(), ex);
                Assert.Equal(ConnectionState.Disconnected, connection.State);
            }
예제 #16
0
        private void ConnectWithRetry()
        {
            Task connectTask = ConnectToRedis();

            connectTask.ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    _trace.TraceError("Error connecting to Redis - " + task.Exception.GetBaseException());

                    if (_state == State.Disposing)
                    {
                        Shutdown();
                        return;
                    }

                    TaskAsyncHelper.Delay(ReconnectDelay)
                    .Then(bus => bus.ConnectWithRetry(), this);
                }
                else
                {
                    var oldState = Interlocked.CompareExchange(ref _state,
                                                               State.Connected,
                                                               State.Closed);
                    if (oldState == State.Closed)
                    {
                        Open(0);
                    }
                    else if (oldState == State.Disposing)
                    {
                        Shutdown();
                    }
                }
            },
                                     TaskContinuationOptions.ExecuteSynchronously);
        }
        private void OpenConnection(IConnection connection,
                                    string data,
                                    CancellationToken disconnectToken,
                                    Action initializeCallback,
                                    Action <Exception> errorCallback)
        {
            // If we're reconnecting add /connect to the url
            bool reconnecting    = initializeCallback == null;
            var  callbackInvoker = new ThreadSafeInvoker();
            var  requestDisposer = new Disposer();

            var      url     = (reconnecting ? connection.Url : connection.Url + "connect") + GetReceiveQueryString(connection, data);
            IRequest request = null;

#if NET35
            Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "SSE: GET {0}", (object)url));
#else
            Debug.WriteLine("SSE: GET {0}", (object)url);
#endif

            HttpClient.GetAsync(url, req =>
            {
                request = req;
                connection.PrepareRequest(request);

                request.Accept = "text/event-stream";
            }).ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    Exception exception = task.Exception.Unwrap();
                    if (!ExceptionHelper.IsRequestAborted(exception))
                    {
                        if (errorCallback != null)
                        {
                            callbackInvoker.Invoke((cb, ex) => cb(ex), errorCallback, exception);
                        }
                        else if (reconnecting)
                        {
                            // Only raise the error event if we failed to reconnect
                            connection.OnError(exception);

                            Reconnect(connection, data, disconnectToken);
                        }
                    }
                    requestDisposer.Dispose();
                }
                else
                {
                    IResponse response = task.Result;
                    Stream stream      = response.GetResponseStream();

                    var eventSource = new EventSourceStreamReader(stream);
                    bool retry      = true;

                    var esCancellationRegistration = disconnectToken.SafeRegister(es =>
                    {
                        retry = false;
                        es.Close();
                    }, eventSource);

                    eventSource.Opened = () =>
                    {
                        if (!reconnecting)
                        {
                            callbackInvoker.Invoke(initializeCallback);
                        }
                        else if (connection.ChangeState(ConnectionState.Reconnecting, ConnectionState.Connected))
                        {
                            // Raise the reconnect event if the connection comes back up
                            connection.OnReconnected();
                        }
                    };

                    eventSource.Message = sseEvent =>
                    {
                        if (sseEvent.EventType == EventType.Data)
                        {
                            if (sseEvent.Data.Equals("initialized", StringComparison.OrdinalIgnoreCase))
                            {
                                return;
                            }

                            bool timedOut;
                            bool disconnected;
                            ProcessResponse(connection, sseEvent.Data, out timedOut, out disconnected);

                            if (disconnected)
                            {
                                retry = false;
                                connection.Disconnect();
                            }
                        }
                    };

                    eventSource.Closed = exception =>
                    {
                        bool isRequestAborted = false;

                        if (exception != null)
                        {
                            // Check if the request is aborted
                            isRequestAborted = ExceptionHelper.IsRequestAborted(exception);

                            if (!isRequestAborted)
                            {
                                // Don't raise exceptions if the request was aborted (connection was stopped).
                                connection.OnError(exception);
                            }
                        }

                        // Skip reconnect attempt for aborted requests
                        if (!isRequestAborted && retry)
                        {
                            Reconnect(connection, data, disconnectToken);
                        }
                    };

                    // See http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.close.aspx
                    eventSource.Disabled = () =>
                    {
                        requestDisposer.Dispose();
                        esCancellationRegistration.Dispose();
                        response.Close();
                    };

                    eventSource.Start();
                }
            });

            var requestCancellationRegistration = disconnectToken.SafeRegister(req =>
            {
                if (req != null)
                {
                    // This will no-op if the request is already finished.
                    req.Abort();
                }

                if (errorCallback != null)
                {
                    callbackInvoker.Invoke((cb, token) =>
                    {
#if NET35 || WINDOWS_PHONE
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled));
#else
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled, token));
#endif
                    }, errorCallback, disconnectToken);
                }
            }, request);

            requestDisposer.Set(requestCancellationRegistration);

            if (errorCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectionTimeout).Then(() =>
                {
                    callbackInvoker.Invoke((conn, cb) =>
                    {
                        connection.Disconnect();

                        // Connection timeout occurred
                        cb(new TimeoutException());
                    },
                                           connection,
                                           errorCallback);
                });
            }
        }
예제 #18
0
        private void OpenConnection(IConnection connection, string data, Action initializeCallback, Action <Exception> errorCallback)
        {
            // If we're reconnecting add /connect to the url
            bool reconnecting    = initializeCallback == null;
            var  callbackInvoker = new ThreadSafeInvoker();

            var url = (reconnecting ? connection.Url : connection.Url + "connect") + GetReceiveQueryString(connection, data);

            Action <IRequest> prepareRequest = PrepareRequest(connection);

#if NET35
            Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "SSE: GET {0}", (object)url));
#else
            Debug.WriteLine("SSE: GET {0}", (object)url);
#endif

            _httpClient.GetAsync(url, request =>
            {
                prepareRequest(request);

                request.Accept = "text/event-stream";
            }).ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    Exception exception = task.Exception.Unwrap();
                    if (!ExceptionHelper.IsRequestAborted(exception))
                    {
                        if (errorCallback != null)
                        {
                            callbackInvoker.Invoke((cb, ex) => cb(ex), errorCallback, exception);
                        }
                        else if (reconnecting)
                        {
                            // Only raise the error event if we failed to reconnect
                            connection.OnError(exception);

                            Reconnect(connection, data);
                        }
                    }
                }
                else
                {
                    IResponse response = task.Result;
                    Stream stream      = response.GetResponseStream();

                    var eventSource = new EventSourceStreamReader(stream);
                    bool retry      = true;

                    connection.Items[EventSourceKey] = eventSource;

                    eventSource.Opened = () =>
                    {
                        if (initializeCallback != null)
                        {
                            callbackInvoker.Invoke(initializeCallback);
                        }

                        if (reconnecting && connection.ChangeState(ConnectionState.Reconnecting, ConnectionState.Connected))
                        {
                            // Raise the reconnect event if the connection comes back up
                            connection.OnReconnected();
                        }
                    };

                    eventSource.Message = sseEvent =>
                    {
                        if (sseEvent.Type == EventType.Data)
                        {
                            if (sseEvent.Data.Equals("initialized", StringComparison.OrdinalIgnoreCase))
                            {
                                return;
                            }

                            bool timedOut;
                            bool disconnected;
                            ProcessResponse(connection, sseEvent.Data, out timedOut, out disconnected);

                            if (disconnected)
                            {
                                retry = false;
                            }
                        }
                    };

                    eventSource.Closed = exception =>
                    {
                        if (exception != null && !ExceptionHelper.IsRequestAborted(exception))
                        {
                            // Don't raise exceptions if the request was aborted (connection was stopped).
                            connection.OnError(exception);
                        }

                        // See http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.close.aspx
                        response.Close();

                        if (retry)
                        {
                            Reconnect(connection, data);
                        }
                        else
                        {
                            connection.Stop();
                        }
                    };

                    eventSource.Start();
                }
            });

            if (errorCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectionTimeout).Then(() =>
                {
                    callbackInvoker.Invoke((conn, cb) =>
                    {
                        // Stop the connection
                        Stop(conn);

                        // Connection timeout occured
                        cb(new TimeoutException());
                    },
                                           connection,
                                           errorCallback);
                });
            }
        }
예제 #19
0
        private void OpenConnection(Connection connection, string data, Action initializeCallback, Action <Exception> errorCallback)
        {
            // If we're reconnecting add /connect to the url
            bool reconnect = initializeCallback == null;

            var url = (reconnect ? connection.Url : connection.Url + "connect") + GetReceiveQueryString(connection, data);

            Action <HttpWebRequest> prepareRequest = PrepareRequest(connection);

            HttpHelper.GetAsync(url, request =>
            {
                prepareRequest(request);

                request.Accept = "text/event-stream";
            }).ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    var exception = task.Exception.GetBaseException();
                    if (!IsRequestAborted(exception) &&
                        Interlocked.CompareExchange(ref connection._initializedCalled, 0, 0) == 0)
                    {
                        if (errorCallback != null)
                        {
                            errorCallback(exception);
                        }
                        else
                        {
                            // Raise the error event if we failed to reconnect
                            connection.OnError(exception);
                        }
                    }
                }
                else
                {
                    // Get the reseponse stream and read it for messages
                    var stream = task.Result.GetResponseStream();
                    var reader = new AsyncStreamReader(stream,
                                                       connection,
                                                       () =>
                    {
                        if (Interlocked.CompareExchange(ref connection._initializedCalled, 1, 0) == 0)
                        {
                            initializeCallback();
                        }
                    },
                                                       () =>
                    {
                        // Wait for a bit before reconnecting
                        Thread.Sleep(ReconnectDelay);

                        // Now attempt a reconnect
                        OpenConnection(connection, data, initializeCallback: null, errorCallback: null);
                    });
                    reader.StartReading();

                    // Set the reader for this connection
                    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());
                    }
                });
            }
        }
예제 #20
0
        private void PollingLoop(IConnection connection, string data, Action initializeCallback, Action <Exception> errorCallback, bool raiseReconnect = false)
        {
            string url = connection.Url;

            var reconnectInvoker = new ThreadSafeInvoker();
            var callbackInvoker  = new ThreadSafeInvoker();

            if (connection.MessageId == null)
            {
                url += "connect";
            }
            else if (raiseReconnect)
            {
                url += "reconnect";

                if (connection.State != ConnectionState.Reconnecting &&
                    !connection.ChangeState(ConnectionState.Connected, ConnectionState.Reconnecting))
                {
                    return;
                }
            }

            url += GetReceiveQueryString(connection, data);

#if NET35
            Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "LP: {0}", (object)url));
#else
            Debug.WriteLine("LP: {0}", (object)url);
#endif

            _httpClient.PostAsync(url, PrepareRequest(connection)).ContinueWith(task =>
            {
                // Clear the pending request
#if MONOTOUCH
                lock (connection.Items)
                {
#endif
                connection.Items.Remove(HttpRequestKey);
#if MONOTOUCH
            }
#endif

                bool shouldRaiseReconnect = false;
                bool disconnectedReceived = false;

                try
                {
                    if (!task.IsFaulted)
                    {
                        if (raiseReconnect)
                        {
                            // If the timeout for the reconnect hasn't fired as yet just fire the
                            // event here before any incoming messages are processed
                            reconnectInvoker.Invoke((conn) => FireReconnected(conn), connection);
                        }

                        if (initializeCallback != null)
                        {
                            // If the timeout for connect hasn't fired as yet then just fire
                            // the event before any incoming messages are processed
                            callbackInvoker.Invoke(initializeCallback);
                        }

                        // Get the response
                        var raw = task.Result.ReadAsString();

#if NET35
                        Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "LP Receive: {0}", (object)raw));
#else
                        Debug.WriteLine("LP Receive: {0}", (object)raw);
#endif

                        ProcessResponse(connection, raw, out shouldRaiseReconnect, out disconnectedReceived);
                    }
                }
                finally
                {
                    if (disconnectedReceived)
                    {
                        connection.Stop();
                    }
                    else
                    {
                        bool requestAborted = false;

                        if (task.IsFaulted)
                        {
                            reconnectInvoker.Invoke();

                            // Raise the reconnect event if we successfully reconnect after failing
                            shouldRaiseReconnect = true;

                            // Get the underlying exception
                            Exception exception = task.Exception.Unwrap();

                            // If the error callback isn't null then raise it and don't continue polling
                            if (errorCallback != null)
                            {
                                callbackInvoker.Invoke((cb, ex) => cb(ex), errorCallback, exception);
                            }
                            else
                            {
                                // Figure out if the request was aborted
                                requestAborted = ExceptionHelper.IsRequestAborted(exception);

                                // Sometimes a connection might have been closed by the server before we get to write anything
                                // so just try again and don't raise OnError.
                                if (!requestAborted && !(exception is IOException))
                                {
                                    // Raise on error
                                    connection.OnError(exception);

                                    // If the connection is still active after raising the error event wait for 2 seconds
                                    // before polling again so we aren't hammering the server
                                    TaskAsyncHelper.Delay(ErrorDelay).Then(() =>
                                    {
                                        if (connection.State != ConnectionState.Disconnected)
                                        {
                                            PollingLoop(connection,
                                                        data,
                                                        initializeCallback: null,
                                                        errorCallback: null,
                                                        raiseReconnect: shouldRaiseReconnect);
                                        }
                                    });
                                }
                            }
                        }
                        else
                        {
                            if (connection.State != ConnectionState.Disconnected)
                            {
                                // Continue polling if there was no error
                                PollingLoop(connection,
                                            data,
                                            initializeCallback: null,
                                            errorCallback: null,
                                            raiseReconnect: shouldRaiseReconnect);
                            }
                        }
                    }
                }
            });

            if (initializeCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectDelay).Then(() =>
                {
                    callbackInvoker.Invoke(initializeCallback);
                });
            }

            if (raiseReconnect)
            {
                TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
                {
                    // Fire the reconnect event after the delay. This gives the
                    reconnectInvoker.Invoke((conn) => FireReconnected(conn), connection);
                });
            }
        }
예제 #21
0
        private void PollingLoop(IConnection connection,
                                 string data,
                                 CancellationToken disconnectToken,
                                 Action initializeCallback,
                                 Action <Exception> errorCallback,
                                 bool raiseReconnect = false)
        {
            string url = connection.Url;

            IRequest request          = null;
            var      reconnectInvoker = new ThreadSafeInvoker();
            var      callbackInvoker  = new ThreadSafeInvoker();
            var      requestDisposer  = new Disposer();

            if (connection.MessageId == null)
            {
                url += "connect";
            }
            else if (raiseReconnect)
            {
                url += "reconnect";

                // FIX: Race if the connection is stopped and completely restarted between checking the token and calling
                //      connection.EnsureReconnecting()
                if (disconnectToken.IsCancellationRequested || !connection.EnsureReconnecting())
                {
                    return;
                }
            }

            url += GetReceiveQueryString(connection, data);

#if NET35
            Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "LP: {0}", (object)url));
#else
            Debug.WriteLine("LP: {0}", (object)url);
#endif

            HttpClient.PostAsync(url, req =>
            {
                request = req;
                connection.PrepareRequest(request);
            }).ContinueWith(task =>
            {
                bool shouldRaiseReconnect = false;
                bool disconnectedReceived = false;

                try
                {
                    if (!task.IsFaulted)
                    {
                        if (raiseReconnect)
                        {
                            // If the timeout for the reconnect hasn't fired as yet just fire the
                            // event here before any incoming messages are processed
                            reconnectInvoker.Invoke((conn) => FireReconnected(conn), connection);
                        }

                        if (initializeCallback != null)
                        {
                            // If the timeout for connect hasn't fired as yet then just fire
                            // the event before any incoming messages are processed
                            callbackInvoker.Invoke(initializeCallback);
                        }

                        // Get the response
                        var raw = task.Result.ReadAsString();

#if NET35
                        Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "LP Receive: {0}", (object)raw));
#else
                        Debug.WriteLine("LP Receive: {0}", (object)raw);
#endif

                        ProcessResponse(connection, raw, out shouldRaiseReconnect, out disconnectedReceived);
                    }
                }
                finally
                {
                    if (disconnectedReceived)
                    {
                        connection.Disconnect();
                    }
                    else
                    {
                        bool requestAborted = false;

                        if (task.IsFaulted)
                        {
                            reconnectInvoker.Invoke();

                            // Raise the reconnect event if we successfully reconnect after failing
                            shouldRaiseReconnect = true;

                            // Get the underlying exception
                            Exception exception = task.Exception.Unwrap();

                            // If the error callback isn't null then raise it and don't continue polling
                            if (errorCallback != null)
                            {
                                callbackInvoker.Invoke((cb, ex) => cb(ex), errorCallback, exception);
                            }
                            else
                            {
                                // Figure out if the request was aborted
                                requestAborted = ExceptionHelper.IsRequestAborted(exception);

                                // Sometimes a connection might have been closed by the server before we get to write anything
                                // so just try again and don't raise OnError.
                                if (!requestAborted && !(exception is IOException))
                                {
                                    // Raise on error
                                    connection.OnError(exception);

                                    // If the connection is still active after raising the error event wait for 2 seconds
                                    // before polling again so we aren't hammering the server
                                    TaskAsyncHelper.Delay(ErrorDelay).Then(() =>
                                    {
                                        if (!disconnectToken.IsCancellationRequested)
                                        {
                                            PollingLoop(connection,
                                                        data,
                                                        disconnectToken,
                                                        initializeCallback: null,
                                                        errorCallback: null,
                                                        raiseReconnect: shouldRaiseReconnect);
                                        }
                                    });
                                }
                            }
                        }
                        else
                        {
                            if (!disconnectToken.IsCancellationRequested)
                            {
                                // Continue polling if there was no error
                                PollingLoop(connection,
                                            data,
                                            disconnectToken,
                                            initializeCallback: null,
                                            errorCallback: null,
                                            raiseReconnect: shouldRaiseReconnect);
                            }
                        }
                    }
                    requestDisposer.Dispose();
                }
            });

            var requestCancellationRegistration = disconnectToken.SafeRegister(req =>
            {
                if (req != null)
                {
                    // This will no-op if the request is already finished.
                    req.Abort();
                }

                // Prevent the connection state from switching to the reconnected state.
                reconnectInvoker.Invoke();

                if (errorCallback != null)
                {
                    callbackInvoker.Invoke((cb, token) =>
                    {
#if NET35 || WINDOWS_PHONE
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled));
#else
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled, token));
#endif
                    }, errorCallback, disconnectToken);
                }
            }, request);

            requestDisposer.Set(requestCancellationRegistration);

            if (initializeCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectDelay).Then(() =>
                {
                    callbackInvoker.Invoke(initializeCallback);
                });
            }

            if (raiseReconnect)
            {
                TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
                {
                    // Fire the reconnect event after the delay. This gives the
                    reconnectInvoker.Invoke((conn) => FireReconnected(conn), connection);
                });
            }
        }
예제 #22
0
        private void OpenConnection(IConnection connection,
                                    string data,
                                    CancellationToken disconnectToken,
                                    Action initializeCallback,
                                    Action <Exception> errorCallback)
        {
            // If we're reconnecting add /connect to the url
            bool reconnecting    = initializeCallback == null;
            var  callbackInvoker = new ThreadSafeInvoker();
            var  requestDisposer = new Disposer();

            var url = (reconnecting ? connection.Url : connection.Url + "connect") + GetReceiveQueryString(connection, data);

            connection.Trace(TraceLevels.Events, "SSE: GET {0}", url);

            HttpClient.Get(url, req =>
            {
                _request = req;
                connection.PrepareRequest(_request);

                _request.Accept = "text/event-stream";
            }).ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    Exception exception = task.Exception.Unwrap();
                    if (!ExceptionHelper.IsRequestAborted(exception))
                    {
                        if (errorCallback != null)
                        {
                            callbackInvoker.Invoke((cb, ex) => cb(ex), errorCallback, exception);
                        }
                        else if (reconnecting)
                        {
                            // Only raise the error event if we failed to reconnect
                            connection.OnError(exception);

                            Reconnect(connection, data, disconnectToken);
                        }
                    }
                    requestDisposer.Dispose();
                }
                else
                {
                    var response  = task.Result;
                    Stream stream = response.GetStream();

                    var eventSource = new EventSourceStreamReader(connection, stream);

                    bool retry = true;

                    var esCancellationRegistration = disconnectToken.SafeRegister(state =>
                    {
                        retry = false;

                        ((IRequest)state).Abort();
                    },
                                                                                  _request);

                    eventSource.Opened = () =>
                    {
                        // If we're not reconnecting, then we're starting the transport for the first time. Trigger callback only on first start.
                        if (!reconnecting)
                        {
                            callbackInvoker.Invoke(initializeCallback);
                        }
                        else if (connection.ChangeState(ConnectionState.Reconnecting, ConnectionState.Connected))
                        {
                            // Raise the reconnect event if the connection comes back up
                            connection.OnReconnected();
                        }
                    };

                    eventSource.Message = sseEvent =>
                    {
                        if (sseEvent.EventType == EventType.Data)
                        {
                            if (sseEvent.Data.Equals("initialized", StringComparison.OrdinalIgnoreCase))
                            {
                                return;
                            }

                            bool timedOut;
                            bool disconnected;
                            TransportHelper.ProcessResponse(connection, sseEvent.Data, out timedOut, out disconnected);

                            if (disconnected)
                            {
                                retry = false;
                                connection.Disconnect();
                            }
                        }
                    };

                    eventSource.Closed = exception =>
                    {
                        if (exception != null)
                        {
                            // Check if the request is aborted
                            bool isRequestAborted = ExceptionHelper.IsRequestAborted(exception);

                            if (!isRequestAborted)
                            {
                                // Don't raise exceptions if the request was aborted (connection was stopped).
                                connection.OnError(exception);
                            }
                        }
                        requestDisposer.Dispose();
                        esCancellationRegistration.Dispose();
                        response.Dispose();

                        if (AbortResetEvent != null)
                        {
                            AbortResetEvent.Set();
                        }
                        else if (retry)
                        {
                            Reconnect(connection, data, disconnectToken);
                        }
                    };

                    eventSource.Start();
                }
            });

            var requestCancellationRegistration = disconnectToken.SafeRegister(state =>
            {
                if (state != null)
                {
                    // This will no-op if the request is already finished.
                    ((IRequest)state).Abort();
                }

                if (errorCallback != null)
                {
                    callbackInvoker.Invoke((cb, token) =>
                    {
#if NET35 || WINDOWS_PHONE
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled));
#else
                        cb(new OperationCanceledException(Resources.Error_ConnectionCancelled, token));
#endif
                    }, errorCallback, disconnectToken);
                }
            }, _request);

            requestDisposer.Set(requestCancellationRegistration);

            if (errorCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectionTimeout).Then(() =>
                {
                    callbackInvoker.Invoke((conn, cb) =>
                    {
                        // Abort the request before cancelling
                        _request.Abort();

                        // Connection timeout occurred
                        cb(new TimeoutException());
                    },
                                           connection,
                                           errorCallback);
                });
            }
        }
예제 #23
0
        private void PollingLoop(IConnection connection, string data, Action initializeCallback, Action <Exception> errorCallback, bool raiseReconnect = false)
        {
            string url = connection.Url;
            var    reconnectTokenSource = new CancellationTokenSource();
            int    reconnectFired       = 0;

            // This is only necessary for the initial request where initializeCallback and errorCallback are non-null
            int callbackFired = 0;

            if (connection.MessageId == null)
            {
                url += "connect";
            }
            else if (raiseReconnect)
            {
                url += "reconnect";

                connection.State = ConnectionState.Reconnecting;
            }

            url += GetReceiveQueryString(connection, data);

            Debug.WriteLine("LP: {0}", (object)url);

            _httpClient.PostAsync(url, PrepareRequest(connection)).ContinueWith(task =>
            {
                // Clear the pending request
                connection.Items.Remove(HttpRequestKey);

                bool shouldRaiseReconnect = false;
                bool disconnectedReceived = false;

                try
                {
                    if (!task.IsFaulted)
                    {
                        if (raiseReconnect)
                        {
                            // If the timeout for the reconnect hasn't fired as yet just fire the
                            // event here before any incoming messages are processed
                            FireReconnected(connection, reconnectTokenSource, ref reconnectFired);
                        }

                        // Get the response
                        var raw = task.Result.ReadAsString();

                        Debug.WriteLine("LP Receive: {0}", (object)raw);

                        ProcessResponse(connection, raw, out shouldRaiseReconnect, out disconnectedReceived);
                    }
                }
                finally
                {
                    if (disconnectedReceived)
                    {
                        connection.Stop();
                    }
                    else
                    {
                        bool requestAborted = false;

                        if (task.IsFaulted)
                        {
                            // Cancel the previous reconnect event
                            reconnectTokenSource.Cancel();

                            // Raise the reconnect event if we successfully reconnect after failing
                            shouldRaiseReconnect = true;

                            // Get the underlying exception
                            Exception exception = task.Exception.Unwrap();

                            // If the error callback isn't null then raise it and don't continue polling
                            if (errorCallback != null &&
                                Interlocked.Exchange(ref callbackFired, 1) == 0)
                            {
                                // Call the callback
                                errorCallback(exception);
                            }
                            else
                            {
                                // Figure out if the request was aborted
                                requestAborted = ExceptionHelper.IsRequestAborted(exception);

                                // Sometimes a connection might have been closed by the server before we get to write anything
                                // so just try again and don't raise OnError.
                                if (!requestAborted && !(exception is IOException))
                                {
                                    // Raise on error
                                    connection.OnError(exception);

                                    // If the connection is still active after raising the error event wait for 2 seconds
                                    // before polling again so we aren't hammering the server
                                    TaskAsyncHelper.Delay(_errorDelay).Then(() =>
                                    {
                                        if (!CancellationToken.IsCancellationRequested)
                                        {
                                            PollingLoop(connection,
                                                        data,
                                                        initializeCallback: null,
                                                        errorCallback: null,
                                                        raiseReconnect: shouldRaiseReconnect);
                                        }
                                    });
                                }
                            }
                        }
                        else
                        {
                            if (!CancellationToken.IsCancellationRequested)
                            {
                                // Continue polling if there was no error
                                PollingLoop(connection,
                                            data,
                                            initializeCallback: null,
                                            errorCallback: null,
                                            raiseReconnect: shouldRaiseReconnect);
                            }
                        }
                    }
                }
            });

            if (initializeCallback != null)
            {
                if (Interlocked.Exchange(ref callbackFired, 1) == 0)
                {
                    initializeCallback();
                }
            }

            if (raiseReconnect)
            {
                TaskAsyncHelper.Delay(ReconnectDelay).Then(() =>
                {
                    // Fire the reconnect event after the delay. This gives the
                    FireReconnected(connection, reconnectTokenSource, ref reconnectFired);
                });
            }
        }
예제 #24
0
        private void OpenConnection(IConnection connection, string data, Action initializeCallback, Action <Exception> errorCallback)
        {
            // If we're reconnecting add /connect to the url
            bool reconnecting = initializeCallback == null;

            var url = (reconnecting ? connection.Url : connection.Url + "connect") + GetReceiveQueryString(connection, data);

            Action <IRequest> prepareRequest = PrepareRequest(connection);

            Debug.WriteLine("SSE: GET {0}", (object)url);

            _httpClient.GetAsync(url, request =>
            {
                prepareRequest(request);

                request.Accept = "text/event-stream";
            }).ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    var exception = task.Exception.Unwrap();
                    if (!ExceptionHelper.IsRequestAborted(exception))
                    {
                        if (errorCallback != null &&
                            Interlocked.Exchange(ref _initializedCalled, 1) == 0)
                        {
                            errorCallback(exception);
                        }
                        else if (reconnecting)
                        {
                            // Only raise the error event if we failed to reconnect
                            connection.OnError(exception);
                        }
                    }

                    if (reconnecting && !CancellationToken.IsCancellationRequested)
                    {
                        connection.State = ConnectionState.Reconnecting;

                        // Retry
                        Reconnect(connection, data);
                        return;
                    }
                }
                else
                {
                    IResponse response = task.Result;
                    Stream stream      = response.GetResponseStream();

                    var eventSource = new EventSourceStreamReader(stream);
                    bool retry      = true;

                    // When this fires close the event source
                    CancellationToken.Register(() => eventSource.Close());

                    eventSource.Opened = () =>
                    {
                        if (Interlocked.CompareExchange(ref _initializedCalled, 1, 0) == 0)
                        {
                            initializeCallback();
                        }

                        if (reconnecting)
                        {
                            // Change the status to connected
                            connection.State = ConnectionState.Connected;

                            // Raise the reconnect event if the connection comes back up
                            connection.OnReconnected();
                        }
                    };

                    eventSource.Error = connection.OnError;

                    eventSource.Message = sseEvent =>
                    {
                        if (sseEvent.Type == EventType.Data)
                        {
                            if (sseEvent.Data.Equals("initialized", StringComparison.OrdinalIgnoreCase))
                            {
                                return;
                            }

                            bool timedOut;
                            bool disconnected;
                            ProcessResponse(connection, sseEvent.Data, out timedOut, out disconnected);

                            if (disconnected)
                            {
                                retry = false;
                            }
                        }
                    };

                    eventSource.Closed = () =>
                    {
                        response.Close();

                        if (retry && !CancellationToken.IsCancellationRequested)
                        {
                            // If we're retrying then just go again
                            connection.State = ConnectionState.Reconnecting;

                            Reconnect(connection, data);
                        }
                        else
                        {
                            connection.Stop();
                        }
                    };

                    if (!CancellationToken.IsCancellationRequested)
                    {
                        eventSource.Start();
                    }
                }
            });

            if (initializeCallback != null)
            {
                TaskAsyncHelper.Delay(ConnectionTimeout).Then(() =>
                {
                    if (Interlocked.CompareExchange(ref _initializedCalled, 1, 0) == 0)
                    {
                        // Stop the connection
                        Stop(connection);

                        // Connection timeout occured
                        errorCallback(new TimeoutException());
                    }
                });
            }
        }
예제 #25
0
        private void PollingSetup(IConnection connection,
                                  string data,
                                  CancellationToken disconnectToken,
                                  PollingRequestHandler requestHandler,
                                  Action onInitialized)
        {
            // reconnectInvoker is created new on each poll
            var reconnectInvoker = new ThreadSafeInvoker();

            var disconnectRegistration = disconnectToken.SafeRegister(state =>
            {
                reconnectInvoker.Invoke();
                requestHandler.Stop();
            }, null);

            requestHandler.ResolveUrl = () =>
            {
                var url = connection.Url;

                if (connection.MessageId == null)
                {
                    url += "connect";
                    connection.Trace(TraceLevels.Events, "LP Connect: {0}", url);
                }
                else if (IsReconnecting(connection))
                {
                    url += "reconnect";
                    connection.Trace(TraceLevels.Events, "LP Reconnect: {0}", url);
                }
                else
                {
                    url += "poll";
                    connection.Trace(TraceLevels.Events, "LP Poll: {0}", url);
                }

                url += GetReceiveQueryString(connection, data);

                return(url);
            };

            requestHandler.PrepareRequest += req =>
            {
                connection.PrepareRequest(req);
            };

            requestHandler.OnMessage += message =>
            {
                var shouldReconnect      = false;
                var disconnectedReceived = false;

                connection.Trace(TraceLevels.Messages, "LP: OnMessage({0})", message);

                TransportHelper.ProcessResponse(connection,
                                                message,
                                                out shouldReconnect,
                                                out disconnectedReceived,
                                                onInitialized);

                if (IsReconnecting(connection))
                {
                    // If the timeout for the reconnect hasn't fired as yet just fire the
                    // event here before any incoming messages are processed
                    TryReconnect(connection, reconnectInvoker);
                }

                if (shouldReconnect)
                {
                    // Transition into reconnecting state
                    connection.EnsureReconnecting();
                }

                if (disconnectedReceived)
                {
                    connection.Trace(TraceLevels.Messages, "Disconnect command received from server.");
                    connection.Disconnect();
                }
            };

            requestHandler.OnError += exception =>
            {
                reconnectInvoker.Invoke();

                if (!TransportHelper.VerifyLastActive(connection))
                {
                    return;
                }

                // Transition into reconnecting state
                connection.EnsureReconnecting();

                // Sometimes a connection might have been closed by the server before we get to write anything
                // so just try again and raise OnError.
                if (!ExceptionHelper.IsRequestAborted(exception) && !(exception is IOException))
                {
                    connection.OnError(exception);
                }
                else
                {
                    requestHandler.Stop();
                }
            };

            requestHandler.OnPolling += () =>
            {
                // Capture the cleanup within a closure so it can persist through multiple requests
                TryDelayedReconnect(connection, reconnectInvoker);
            };

            requestHandler.OnAfterPoll = exception =>
            {
                if (AbortHandler.TryCompleteAbort())
                {
                    // Abort() was called, so don't reconnect
                    requestHandler.Stop();
                }
                else
                {
                    reconnectInvoker = new ThreadSafeInvoker();

                    if (exception != null)
                    {
                        // Delay polling by the error delay
                        return(TaskAsyncHelper.Delay(ErrorDelay));
                    }
                }

                return(TaskAsyncHelper.Empty);
            };

            requestHandler.OnAbort += _ =>
            {
                disconnectRegistration.Dispose();

                // Complete any ongoing calls to Abort()
                // If someone calls Abort() later, have it no-op
                AbortHandler.CompleteAbort();
            };
        }
        public void TaskAsyncHelpersPreserveCulture()
        {
            TaskCompletionSource <CultureInfo> tcs   = null;
            TaskCompletionSource <CultureInfo> uiTcs = null;
            var defaultCulture   = Thread.CurrentThread.CurrentCulture;
            var defaultUiCulture = Thread.CurrentThread.CurrentUICulture;
            var testCulture      = new CultureInfo("zh-Hans");
            var testUICulture    = new CultureInfo("zh-CN");

            Func <Task> saveThreadCulture = () =>
            {
                tcs.SetResult(Thread.CurrentThread.CurrentCulture);
                uiTcs.SetResult(Thread.CurrentThread.CurrentUICulture);
                return(TaskAsyncHelper.Empty);
            };

            Action <IEnumerable <Func <Task <object> > >, Action <Task <object> > > ensureCulturePreserved = (taskGenerators, testAction) =>
            {
                foreach (var taskGenerator in taskGenerators)
                {
                    tcs   = new TaskCompletionSource <CultureInfo>();
                    uiTcs = new TaskCompletionSource <CultureInfo>();
                    testAction(taskGenerator());
                    Assert.Equal(testCulture, tcs.Task.Result);
                    Assert.Equal(testUICulture, uiTcs.Task.Result);
                }
            };

            try
            {
                Thread.CurrentThread.CurrentCulture   = testCulture;
                Thread.CurrentThread.CurrentUICulture = testUICulture;

                var successfulTaskGenerators = new Func <Task <object> >[]
                {
                    () => TaskAsyncHelper.FromResult <object>(null),                                         // Completed
                    () => TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(50)).Then(() => (object)null),     // Async Completed
                };

                // Non-generic Then with sync/async completed tasks
                ensureCulturePreserved(successfulTaskGenerators, task => task.Then(saveThreadCulture));

                var faultedTcs  = new TaskCompletionSource <object>();
                var canceledTcs = new TaskCompletionSource <object>();
                faultedTcs.SetException(new Exception());
                canceledTcs.SetCanceled();
                var allTaskGenerators = successfulTaskGenerators.Concat(new Func <Task <object> >[]
                {
                    () => faultedTcs.Task,                                                                   // Faulted
                    () => canceledTcs.Task,                                                                  // Canceled
                    () => TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(50)).Then(() => faultedTcs.Task),  // Async Faulted
                    () => TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(50)).Then(() => canceledTcs.Task), // Async Canceled
                });

                // Generic ContinueWithPreservedCulture with sync/async faulted, canceled and completed tasks
                ensureCulturePreserved(allTaskGenerators, task => task.ContinueWithPreservedCulture(_ => saveThreadCulture()));

                // Verify that threads in the ThreadPool keep the default culture
                tcs   = new TaskCompletionSource <CultureInfo>();
                uiTcs = new TaskCompletionSource <CultureInfo>();
                TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(100)).ContinueWith(_ => saveThreadCulture());
                Assert.Equal(defaultCulture, tcs.Task.Result);
                Assert.Equal(defaultUiCulture, uiTcs.Task.Result);
            }
            finally
            {
                Thread.CurrentThread.CurrentCulture   = defaultCulture;
                Thread.CurrentThread.CurrentUICulture = defaultUiCulture;
            }
        }
예제 #27
0
            public async Task ErrorTask()
            {
                await TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(1));

                throw new Exception("Custom Error from task.");
            }
예제 #28
0
        private void PollingSetup(IConnection connection,
                                  string data,
                                  CancellationToken disconnectToken,
                                  PollingRequestHandler requestHandler)
        {
            // These are created new on each poll
            var reconnectInvoker = new ThreadSafeInvoker();
            var requestDisposer  = new Disposer();

            requestHandler.ResolveUrl = () =>
            {
                var url = connection.Url;

                if (connection.MessageId == null)
                {
                    url += "connect";
                }
                else if (IsReconnecting(connection))
                {
                    url += "reconnect";
                }
                else
                {
                    url += "poll";
                }

                url += GetReceiveQueryString(connection, data);

                return(url);
            };

            requestHandler.PrepareRequest += req =>
            {
                connection.PrepareRequest(req);
            };

            requestHandler.OnMessage += message =>
            {
                var shouldReconnect      = false;
                var disconnectedReceived = false;

                connection.Trace(TraceLevels.Messages, "LP: OnMessage({0})", message);

                TransportHelper.ProcessResponse(connection,
                                                message,
                                                out shouldReconnect,
                                                out disconnectedReceived);

                if (IsReconnecting(connection))
                {
                    // If the timeout for the reconnect hasn't fired as yet just fire the
                    // event here before any incoming messages are processed
                    TryReconnect(connection, reconnectInvoker);
                }

                if (shouldReconnect)
                {
                    // Transition into reconnecting state
                    connection.EnsureReconnecting();
                }

                if (AbortResetEvent != null)
                {
                    AbortResetEvent.Set();
                }
                else if (disconnectedReceived)
                {
                    connection.Disconnect();
                }
            };

            requestHandler.OnError += exception =>
            {
                reconnectInvoker.Invoke();

                // Transition into reconnecting state
                connection.EnsureReconnecting();

                // Sometimes a connection might have been closed by the server before we get to write anything
                // so just try again and raise OnError.
                if (!ExceptionHelper.IsRequestAborted(exception) && !(exception is IOException))
                {
                    connection.OnError(exception);
                }
                else
                {
                    // If we aborted purposely then we need to stop the request handler
                    requestHandler.Stop();
                }
            };

            requestHandler.OnPolling += () =>
            {
                // Capture the cleanup within a closure so it can persist through multiple requests
                TryDelayedReconnect(connection, reconnectInvoker);

                requestDisposer.Set(disconnectToken.SafeRegister(state =>
                {
                    reconnectInvoker.Invoke();
                    requestHandler.Stop();
                }, null));
            };

            requestHandler.OnAfterPoll = exception =>
            {
                requestDisposer.Dispose();
                requestDisposer  = new Disposer();
                reconnectInvoker = new ThreadSafeInvoker();

                if (exception != null)
                {
                    // Delay polling by the error delay
                    return(TaskAsyncHelper.Delay(ErrorDelay));
                }

                return(TaskAsyncHelper.Empty);
            };
        }