//
        // StartRequestHandlerTask()
        //
        // Take any messages on our PendingRequest list and write them to GCP
        private Task StartRequestHandlerTask()
        {
            OnDebugMessage("Started Request Handler");
            return(Task.Run(async() =>
            {
                while (!Done)
                {
                    var request = PendingRequests.Take(CancellationToken);
                    OnDebugMessage("Setup listen for request");

                    //If the listener isn't active, start it
                    if (DuplexStream == null || !ListenerIsActive)
                    {
                        // Initialize streaming call, retrieving the stream object
                        DuplexStream = FirestoreClient.Listen(ListenSettings);
                        ListenerIsActive = true;
                        OnDebugMessage("Response Task Not Active, starting");
                        ResponseHanderTask = StartResponseHandlerTask();
                    }
                    OnDebugMessage("Sending Request");
                    // Stream a request to the server
                    await DuplexStream.WriteAsync(request);
                    ActiveRequests.Enqueue(request);
                }
                OnDebugMessage("Request Handler Completed");
                await DuplexStream.WriteCompleteAsync();
            }));
        }
Exemple #2
0
        /// <summary>Snippet for Listen</summary>
        public async Task Listen()
        {
            // Snippet: Listen(CallSettings, BidirectionalStreamingSettings)
            // Create client
            FirestoreClient firestoreClient = FirestoreClient.Create();

            // Initialize streaming call, retrieving the stream object
            FirestoreClient.ListenStream response = firestoreClient.Listen();

            // Sending requests and retrieving responses can be arbitrarily interleaved
            // Exact sequence will depend on client/server behavior

            // Create task to do something with responses from server
            Task responseHandlerTask = Task.Run(async() =>
            {
                // Note that C# 8 code can use await foreach
                AsyncResponseStream <ListenResponse> responseStream = response.GetResponseStream();
                while (await responseStream.MoveNextAsync())
                {
                    ListenResponse responseItem = responseStream.Current;
                    // Do something with streamed response
                }
                // The response stream has completed
            });

            // Send requests to the server
            bool done = false;

            while (!done)
            {
                // Initialize a request
                ListenRequest request = new ListenRequest
                {
                    Database     = "",
                    AddTarget    = new Target(),
                    RemoveTarget = 0,
                    Labels       = { { "", "" }, },
                };
                // Stream a request to the server
                await response.WriteAsync(request);

                // Set "done" to true when sending requests is complete
            }

            // Complete writing requests to the stream
            await response.WriteCompleteAsync();

            // Await the response handler
            // This will complete once all server responses have been processed
            await responseHandlerTask;
            // End snippet
        }
        /// <summary>Snippet for Listen</summary>
        public async Task Listen()
        {
            // Snippet: Listen(CallSettings,BidirectionalStreamingSettings)
            // Create client
            FirestoreClient firestoreClient = FirestoreClient.Create();

            // Initialize streaming call, retrieving the stream object
            FirestoreClient.ListenStream duplexStream = firestoreClient.Listen();

            // Sending requests and retrieving responses can be arbitrarily interleaved.
            // Exact sequence will depend on client/server behavior.

            // Create task to do something with responses from server
            Task responseHandlerTask = Task.Run(async() =>
            {
                IAsyncEnumerator <ListenResponse> responseStream = duplexStream.ResponseStream;
                while (await responseStream.MoveNext())
                {
                    ListenResponse response = responseStream.Current;
                    // Do something with streamed response
                }
                // The response stream has completed
            });

            // Send requests to the server
            bool done = false;

            while (!done)
            {
                // Initialize a request
                ListenRequest request = new ListenRequest
                {
                    Database = new DatabaseRootName("[PROJECT]", "[DATABASE]").ToString(),
                };
                // Stream a request to the server
                await duplexStream.WriteAsync(request);

                // Set "done" to true when sending requests is complete
            }
            // Complete writing requests to the stream
            await duplexStream.WriteCompleteAsync();

            // Await the response handler.
            // This will complete once all server responses have been processed.
            await responseHandlerTask;
            // End snippet
        }
        internal async Task StartAsync()
        {
            // State used within the method. This is modified by local methods too.
            StreamInitializationCause cause = StreamInitializationCause.WatchStarting;

            FirestoreClient.ListenStream underlyingStream = null;
            TimeSpan nextBackoff = TimeSpan.Zero;

            try
            {
                // This won't actually run forever. Calling Stop will cancel the cancellation token, and we'll end up with
                // an exception which may or may not be caught.
                while (true)
                {
                    var serverResponse = await GetNextResponse().ConfigureAwait(false);

                    _callbackCancellationTokenSource.Token.ThrowIfCancellationRequested();
                    var result = await _state.HandleResponseAsync(serverResponse, _callbackCancellationTokenSource.Token).ConfigureAwait(false);

                    switch (result)
                    {
                    case WatchResponseResult.Continue:
                        break;

                    case WatchResponseResult.ResetStream:
                        await CloseStreamAsync().ConfigureAwait(false);

                        cause = StreamInitializationCause.ResetRequested;
                        break;

                    case WatchResponseResult.StreamHealthy:
                        nextBackoff = TimeSpan.Zero;
                        break;

                    default:
                        throw new InvalidOperationException($"Unknown result type: {result}");
                    }
                    // What about other exception types?
                }
            }
            // Swallow cancellation exceptions unless one of the user-provided cancellation tokens has been
            // cancelled, in which case it's fine to let it through.
            catch (OperationCanceledException) when(!_callbackCancellationTokenSource.Token.IsCancellationRequested)
            {
                // We really do just swallow the exception. No need for logging.
            }
            finally
            {
                lock (_stateLock)
                {
                    _networkCancellationTokenSource.Dispose();
                    _callbackCancellationTokenSource.Dispose();
                    _stopCancellationTokenRegistration.Dispose();
                    _finished = true;
                }
                // Make sure we clean up even if we get an exception we don't handle explicitly.
                await CloseStreamAsync().ConfigureAwait(false);
            }


            // Local method responsible for fetching the next response from the server stream, including
            // stream initialization and error handling.
            async Task <ListenResponse> GetNextResponse()
            {
                while (true)
                {
                    try
                    {
                        // If we're just starting, or we've closed the stream or it broke, restart.
                        if (underlyingStream == null)
                        {
                            await _scheduler.Delay(_backoffJitter.GetDelay(nextBackoff), _networkCancellationTokenSource.Token).ConfigureAwait(false);

                            nextBackoff      = _backoffSettings.NextDelay(nextBackoff);
                            underlyingStream = _db.Client.Listen();
                            await underlyingStream.TryWriteAsync(CreateRequest(_state.ResumeToken)).ConfigureAwait(false);

                            _state.OnStreamInitialization(cause);
                        }
                        // Wait for a response or end-of-stream
                        var next = await underlyingStream.ResponseStream.MoveNext(_networkCancellationTokenSource.Token).ConfigureAwait(false);

                        // If the server provided a response, return it
                        if (next)
                        {
                            return(underlyingStream.ResponseStream.Current);
                        }
                        // Otherwise, close the current stream and restart.
                        await CloseStreamAsync().ConfigureAwait(false);

                        cause = StreamInitializationCause.StreamCompleted;
                    }
                    catch (RpcException e) when(s_transientErrorStatusCodes.Contains(e.Status.StatusCode))
                    {
                        // Close the current stream, ready to create a new one.
                        await CloseStreamAsync().ConfigureAwait(false);

                        // Extend the back-off if necessary.
                        if (e.Status.StatusCode == StatusCode.ResourceExhausted)
                        {
                            nextBackoff = _backoffSettings.NextDelay(nextBackoff);
                        }
                        cause = StreamInitializationCause.RpcError;
                    }
                }
            }

            async Task CloseStreamAsync()
            {
                if (underlyingStream != null)
                {
                    var completeTask = underlyingStream.TryWriteCompleteAsync();
                    // TODO: Handle exceptions from this?
                    if (completeTask != null)
                    {
                        await completeTask.ConfigureAwait(false);
                    }
                    underlyingStream.GrpcCall.Dispose();
                }
                underlyingStream = null;
            }
        }
Exemple #5
0
        internal async Task StartAsync()
        {
            // State used within the method. This is modified by local methods too.
            StreamInitializationCause cause = StreamInitializationCause.WatchStarting;

            FirestoreClient.ListenStream underlyingStream = null;
            IEnumerator <RetryAttempt>   retryAttempts    = CreateRetryAttemptSequence();

            try
            {
                // This won't actually run forever. Calling Stop will cancel the cancellation token, and we'll end up with
                // an exception which may or may not be caught.
                while (true)
                {
                    var serverResponse = await GetNextResponse().ConfigureAwait(false);

                    _callbackCancellationTokenSource.Token.ThrowIfCancellationRequested();
                    var result = await _state.HandleResponseAsync(serverResponse, _callbackCancellationTokenSource.Token).ConfigureAwait(false);

                    switch (result)
                    {
                    case WatchResponseResult.Continue:
                        break;

                    case WatchResponseResult.ResetStream:
                        await CloseStreamAsync().ConfigureAwait(false);

                        cause = StreamInitializationCause.ResetRequested;
                        break;

                    case WatchResponseResult.StreamHealthy:
                        // Reset the retry backoff to zero.
                        retryAttempts = CreateRetryAttemptSequence();
                        break;

                    default:
                        throw new InvalidOperationException($"Unknown result type: {result}");
                    }
                    // What about other exception types?
                }
            }
            // Swallow cancellation exceptions unless one of the user-provided cancellation tokens has been
            // cancelled, in which case it's fine to let it through.
            catch (OperationCanceledException) when(!_callbackCancellationTokenSource.Token.IsCancellationRequested)
            {
                // We really do just swallow the exception. No need for logging.
            }
            finally
            {
                lock (_stateLock)
                {
                    _networkCancellationTokenSource.Dispose();
                    _callbackCancellationTokenSource.Dispose();
                    _stopCancellationTokenRegistration.Dispose();
                    _finished = true;
                }
                // Make sure we clean up even if we get an exception we don't handle explicitly.
                await CloseStreamAsync().ConfigureAwait(false);
            }


            // Local method responsible for fetching the next response from the server stream, including
            // stream initialization and error handling.
            async Task <ListenResponse> GetNextResponse()
            {
                while (true)
                {
                    try
                    {
                        // If we're just starting, or we've closed the stream or it broke, restart.
                        if (underlyingStream == null)
                        {
                            await retryAttempts.Current.BackoffAsync(_networkCancellationTokenSource.Token).ConfigureAwait(false);

                            retryAttempts.MoveNext();
                            underlyingStream = _db.Client.Listen(_listenCallSettings);
                            await underlyingStream.TryWriteAsync(CreateRequest(_state.ResumeToken)).ConfigureAwait(false);

                            _state.OnStreamInitialization(cause);
                        }
                        // Wait for a response or end-of-stream
                        var next = await underlyingStream.GrpcCall.ResponseStream.MoveNext(_networkCancellationTokenSource.Token).ConfigureAwait(false);

                        // If the server provided a response, return it
                        if (next)
                        {
                            return(underlyingStream.GrpcCall.ResponseStream.Current);
                        }
                        // Otherwise, close the current stream and restart.
                        await CloseStreamAsync().ConfigureAwait(false);

                        cause = StreamInitializationCause.StreamCompleted;
                    }
                    catch (RpcException e) when(s_transientErrorStatusCodes.Contains(e.Status.StatusCode))
                    {
                        // Close the current stream, ready to create a new one.
                        await CloseStreamAsync().ConfigureAwait(false);

                        // Extend the back-off if necessary.
                        if (e.Status.StatusCode == StatusCode.ResourceExhausted)
                        {
                            retryAttempts.MoveNext();
                        }
                        cause = StreamInitializationCause.RpcError;
                    }
                }
            }

            async Task CloseStreamAsync()
            {
                if (underlyingStream != null)
                {
                    try
                    {
                        var completeTask = underlyingStream.TryWriteCompleteAsync();
                        if (completeTask != null)
                        {
                            await completeTask.ConfigureAwait(false);
                        }
                    }
                    catch (RpcException)
                    {
                        // Swallow gRPC errors when trying to "complete" the stream. This may be in response to the network connection
                        // being dropped, at which point completing the stream will fail; we don't want the listener to stop at that
                        // point. Instead, it will reconnect.
                    }
                    underlyingStream.GrpcCall.Dispose();
                }
                underlyingStream = null;
            }

            // Create a new enumerator for the retry attempt sequence, starting with a backoff of zero.
            IEnumerator <RetryAttempt> CreateRetryAttemptSequence()
            {
                var iterator = RetryAttempt
                               .CreateRetrySequence(_backoffSettings, _scheduler, initialBackoffOverride: TimeSpan.Zero)
                               .GetEnumerator();

                // Make sure Current is already valid
                iterator.MoveNext();
                return(iterator);
            }
        }