public async Task ExecuteServerAsync(IQbservableProvider provider) { Contract.Requires(!IsClient); Task receivingAsync = null; ExceptionDispatchInfo fatalException = null; try { await InitializeSinksAsync().ConfigureAwait(false); var input = await ServerReceiveQueryAsync().ConfigureAwait(false); receivingAsync = ServerReceiveAsync(); try { await ExecuteServerQueryAsync(input, provider).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception ex) { if (ShutdownReason == QbservableProtocolShutdownReason.None) { ShutdownReason = QbservableProtocolShutdownReason.BadClientRequest; } CancelAllCommunication(ex); fatalException = ExceptionDispatchInfo.Capture(ex); } } catch (OperationCanceledException ex) { if (errors.Count == 1) { if (ShutdownReason == QbservableProtocolShutdownReason.None) { ShutdownReason = QbservableProtocolShutdownReason.ServerError; } fatalException = errors[0]; } else if (errors.Count > 1) { if (ShutdownReason == QbservableProtocolShutdownReason.None) { ShutdownReason = QbservableProtocolShutdownReason.ServerError; } fatalException = ExceptionDispatchInfo.Capture(new AggregateException(errors.Select(e => e.SourceException))); } if (ShutdownReason == QbservableProtocolShutdownReason.None) { ShutdownReason = QbservableProtocolShutdownReason.ClientTerminated; } fatalException = ExceptionDispatchInfo.Capture(ex); } catch (Exception ex) { CancelAllCommunication(ex); fatalException = ExceptionDispatchInfo.Capture(ex); } if (receivingAsync != null) { try { await receivingAsync.ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception ex) { errors.Add(ExceptionDispatchInfo.Capture(ex)); } } if (fatalException != null) { fatalException.Throw(); } }
private async Task SendObservableAsync(object untypedObservable, Type dataType, bool sendServerErrorsToClients, CancellationToken cancel) { var networkErrors = new ConcurrentBag <ExceptionDispatchInfo>(); ExceptionDispatchInfo expressionSecurityError = null; ExceptionDispatchInfo qbservableSubscriptionError = null; ExceptionDispatchInfo qbservableError = null; var terminationKind = NotificationKind.OnCompleted; try { var cancelSubscription = new CancellationTokenSource(); cancel.Register(cancelSubscription.Cancel); IObservable <object> observable; new PermissionSet(PermissionState.Unrestricted).Assert(); try { observable = dataType.UpCast(untypedObservable); } finally { PermissionSet.RevertAssert(); } await observable.ForEachAsync( async data => { try { await ServerSendAsync(NotificationKind.OnNext, data).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception ex) { /* Collecting exceptions handles a possible race condition. Since this code is using a fire-and-forget model to * subscribe to the observable, due to the async OnNext handler, it's possible that more than one SendAsync task * can be executing concurrently. As a result, cancelling the cancelSubscription below does not guarantee that * this catch block won't run again. */ networkErrors.Add(ExceptionDispatchInfo.Capture(ex)); cancelSubscription.Cancel(); } }, cancelSubscription.Token) .ConfigureAwait(false); } catch (OperationCanceledException) { if (cancel.IsCancellationRequested && networkErrors.Count == 0) { throw; } } catch (ExpressionSecurityException ex) { ShutdownReason = QbservableProtocolShutdownReason.ExpressionSecurityViolation; expressionSecurityError = ExceptionDispatchInfo.Capture(ex); } catch (QbservableSubscriptionException ex) { ShutdownReason = QbservableProtocolShutdownReason.ExpressionSubscriptionException; qbservableSubscriptionError = ExceptionDispatchInfo.Capture(ex.InnerException ?? ex); } catch (TargetInvocationException ex) { if (ex.InnerException is QbservableSubscriptionException) { ShutdownReason = QbservableProtocolShutdownReason.ExpressionSubscriptionException; qbservableSubscriptionError = ExceptionDispatchInfo.Capture(ex.InnerException.InnerException ?? ex.InnerException); } else { qbservableSubscriptionError = ExceptionDispatchInfo.Capture(ex); } } catch (Exception ex) { terminationKind = NotificationKind.OnError; qbservableError = ExceptionDispatchInfo.Capture(ex); } var error = expressionSecurityError ?? qbservableSubscriptionError; if (error != null) { if (networkErrors.Count > 0) { // It's not technically a network error, but since the client can't receive it anyway add it so that it's thrown later networkErrors.Add(error); } else { if (sendServerErrorsToClients || expressionSecurityError != null) { var exception = expressionSecurityError == null ? error.SourceException : new SecurityException(error.SourceException.Message); // Remove stack trace await ServerSendAsync(NotificationKind.OnError, exception).ConfigureAwait(false); } error.Throw(); } } /* There's an acceptable race condition here whereby ForEachAsync is canceled by the external cancellation token though * it's still executing a fire-and-forget task. It's possible for the fire-and-forget task to throw before seeing the * cancellation, yet after the following code has already executed. In that case, since the cancellation was requested * externally, it's acceptable for the cancellation to simply beat the send error, thus the error can safely be ignored. */ if (networkErrors.Count > 0) { throw new AggregateException(networkErrors.Select(e => e.SourceException)); } else { await ServerSendAsync(terminationKind, (qbservableError == null ? null : qbservableError.SourceException)).ConfigureAwait(false); if (qbservableError != null) { qbservableError.Throw(); } } }
private static IObservable <TcpClientTermination> CreateService <TSource, TResult>( IPEndPoint endPoint, Func <IRemotingFormatter> formatterFactory, QbservableServiceOptions options, Func <IObservable <TSource>, IQbservable <TResult> > service) { var listener = new TcpListener(endPoint); listener.Start(); return(Observable .FromAsync(listener.AcceptTcpClientAsync) .Repeat() .Finally(listener.Stop) .SelectMany(client => Observable .StartAsync(async cancel => { var watch = Stopwatch.StartNew(); var localEndPoint = client.Client.LocalEndPoint; var remoteEndPoint = client.Client.RemoteEndPoint; var exceptions = new List <ExceptionDispatchInfo>(); var shutdownReason = QbservableProtocolShutdownReason.None; try { using (var stream = client.GetStream()) using (var protocol = await QbservableProtocol.NegotiateServerAsync(stream, formatterFactory(), options, cancel).ConfigureAwait(false)) { var provider = new TcpServerQbservableProvider <TResult>( protocol, options, argument => { if (argument == null && typeof(TSource).IsValueType) { return service(Observable.Return(default(TSource))); } else { return service(Observable.Return((TSource)argument)); } }); try { await protocol.ExecuteServerAsync(provider).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception ex) { exceptions.Add(ExceptionDispatchInfo.Capture(ex)); } var protocolExceptions = protocol.Exceptions; if (protocolExceptions != null) { foreach (var exception in protocolExceptions) { exceptions.Add(exception); } } shutdownReason = protocol.ShutdownReason; } } catch (OperationCanceledException) { shutdownReason = QbservableProtocolShutdownReason.ProtocolNegotiationCanceled; } catch (Exception ex) { shutdownReason = QbservableProtocolShutdownReason.ProtocolNegotiationError; exceptions.Add(ExceptionDispatchInfo.Capture(ex)); } return new TcpClientTermination(localEndPoint, remoteEndPoint, watch.Elapsed, shutdownReason, exceptions); }) .Finally(client.Close))); }