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)); } }
// 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())); }
private void TryDelayedReconnect(IConnection connection, ThreadSafeInvoker reconnectInvoker) { if (IsReconnecting(connection)) { TaskAsyncHelper.Delay(ReconnectDelay).Then(() => { TryReconnect(connection, reconnectInvoker); }); } }
public void Initialize() { TaskAsyncHelper.Delay(_assumeSuccessAfter).Then(() => { _callbackInvoker.Invoke(() => { Initialized(); _initializeCallback(); }); }); }
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); }
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); } }); }
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); }); }
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)); } }); }
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); }
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)); }); }
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 :( } } }
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); }
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); }); } }
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); }); } }
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()); } }); } }
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); }); } }
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); }); } }
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); }); } }
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); }); } }
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()); } }); } }
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; } }
public async Task ErrorTask() { await TaskAsyncHelper.Delay(TimeSpan.FromMilliseconds(1)); throw new Exception("Custom Error from task."); }
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); }; }