/// <summary>
            /// Causes the fake server to provide a response which is passed to the state machine - which then
            /// hangs (expecting its cancellation token to be cancelled).
            /// </summary>
            internal void ProvideResponseHangingInStateMachine()
            {
                var response = new ListenResponse {
                    TargetChange = new TargetChange {
                        TargetIds = { _responseIndex++ }
                    }
                };

                _currentResponseList.Add(() => response);
                _watchState.ExpectResponseAndWaitForUserCancellation(response);
            }
            /// <summary>
            /// Causes the fake server to provide a response, which we expect to be passed to the
            /// state machine.
            /// </summary>
            internal void ProvideResponse(WatchResponseResult result)
            {
                // The response is opaque to WatchStream, but we want to make sure the state machine gets the
                var response = new ListenResponse {
                    TargetChange = new TargetChange {
                        TargetIds = { _responseIndex++ }
                    }
                };

                _currentResponseList.Add(() => response);
                _watchState.ExpectResponse(response, result);
            }
示例#3
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
        }
        public async Task TargetRemoved()
        {
            var db    = FirestoreDb.Create("project", "database", new FakeFirestoreClient());
            var query = db.Collection("col");

            var    state    = new WatchState(query, (snapshot, token) => throw new Exception("Unexpected callback"));
            string message  = "MESSAGE_TO_FIND";
            var    response = new ListenResponse
            {
                TargetChange = new TargetChange
                {
                    TargetChangeType = TargetChange.Types.TargetChangeType.Remove,
                    TargetIds        = { WatchStream.WatchTargetId },
                    Cause            = new Rpc.Status {
                        Code = (int)Rpc.Code.ResourceExhausted, Message = message
                    }
                }
            };
            var exception = await Assert.ThrowsAsync <RpcException>(() => state.HandleResponseAsync(response, default));

            Assert.Equal(StatusCode.ResourceExhausted, exception.StatusCode);
            Assert.Contains(message, exception.Message);
        }
示例#6
0
        public async Task <WatchResponseResult> HandleResponseAsync(ListenResponse response, CancellationToken cancellationToken)
        {
            switch (response.ResponseTypeCase)
            {
            case ListenResponse.ResponseTypeOneofCase.TargetChange:
                TargetChange change      = response.TargetChange;
                bool         noTargetIds = change.TargetIds.Count == 0;

                switch (change.TargetChangeType)
                {
                case NoChange:
                    if (noTargetIds && change.ReadTime != null && _current)
                    {
                        // This means everything is up-to-date, so emit the current set of docs as a snapshot,
                        // if there were changes.
                        await PushSnapshotAsync(Timestamp.FromProto(change.ReadTime), change.ResumeToken, cancellationToken).ConfigureAwait(false);
                    }
                    break;

                case Add:
                    GaxPreconditions.CheckState(WatchStream.WatchTargetId == change.TargetIds[0], "Target ID must be 0x{0:x}", WatchStream.WatchTargetId);
                    break;

                case Remove:
                    // TODO: Do we really want an RpcException here?
                    StatusCode status = (StatusCode?)change.Cause?.Code ?? StatusCode.Cancelled;
                    throw new RpcException(new Status(status, $"Backend ended listen stream: {change.Cause?.Message}"));

                case Current:
                    _current = true;
                    break;

                case Reset:
                    ResetDocs();
                    return(WatchResponseResult.Continue);

                default:
                    // TODO: Do we really want an RpcException here?
                    throw new RpcException(new Status(StatusCode.InvalidArgument, $"Encountered invalid target change type: {change.Cause.Message}"));
                }

                bool healthy = change.ResumeToken != null && (change.TargetIds.Count == 0 || change.TargetIds.Contains(WatchStream.WatchTargetId));
                // Possibly tell the watch stream that it's now healthy (so reset backoff), or just continue.
                return(healthy ? WatchResponseResult.StreamHealthy : WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentChange:
                // No other targetIds can show up here, but we still need to see if the targetId was in the
                // added list or removed list.
                var               changed  = response.DocumentChange.TargetIds.Contains(WatchStream.WatchTargetId);
                var               removed  = response.DocumentChange.RemovedTargetIds.Contains(WatchStream.WatchTargetId);
                Document          document = response.DocumentChange.Document;
                DocumentReference docRef   = CreateDocumentReference(document.Name);
                if (changed && removed)
                {
                    throw new InvalidOperationException("Server error: document was both changed and removed");
                }
                if (!changed && !removed)
                {
                    throw new InvalidOperationException("Server error: document was neither changed nor removed");
                }
                _changeMap[docRef] = changed ? document : null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentDelete:
                _changeMap[CreateDocumentReference(response.DocumentDelete.Document)] = null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.DocumentRemove:
                _changeMap[CreateDocumentReference(response.DocumentRemove.Document)] = null;
                return(WatchResponseResult.Continue);

            case ListenResponse.ResponseTypeOneofCase.Filter:
                // TODO: Do we really want to create the change set itself, rather than just count? It seems a bit wasteful.
                ChangeSet changeSet   = ExtractChanges(default);
                int       currentSize = _documentSet.Count + changeSet.Adds.Count - changeSet.Deletes.Count;
                // Reset the stream if we don't have the right number of documents.
                if (response.Filter.Count != currentSize)
                {
                    ResetDocs();
                    return(WatchResponseResult.ResetStream);
                }
                return(WatchResponseResult.Continue);

            default:
                throw new RpcException(new Status(StatusCode.InvalidArgument, $"Encountered invalid listen response type: {response.ResponseTypeCase}"));
            }
        }
示例#7
0
        private async void ReadMessages()
        {
            WebSocketReceiveResult webSocketReceiveResult = null;
            string lastMessage = null;

            try
            {
                readers.AddCount();

                while (true)
                {
                    await CheckConnectionOrWait();

                    bool readCompleted = false;

                    try
                    {
                        webSocketReceiveResult = await clientWebSocket.ReceiveAsync(incomingData, readerTokenSource.Token);

                        readCompleted = true;
                    }
                    catch (TaskCanceledException) { /* swallow */ }
                    catch (ThreadAbortException) { /* swallow */ }
                    catch (ObjectDisposedException) { /* swallow */ }
                    catch (OperationCanceledException) { /* swallow */ }
                    catch (WebSocketException)
                    {
                        communication.SendWarningMessage($"PubSub Websocket closed unexpectedly.");
                    }
                    catch (Exception ex)
                    {
                        communication.SendErrorMessage($"PubSub Exception: {ex.GetType().Name}");
                        errorHandler.LogMessageException(ex, "");
                    }

                    if (generalTokenSource.IsCancellationRequested)
                    {
                        //We are quitting
                        break;
                    }

                    if ((readerTokenSource?.IsCancellationRequested ?? true) || !readCompleted)
                    {
                        //We are just restarting reader, since it was intercepted with an exception or the reader token source was cancelled
                        continue;
                    }

                    if (webSocketReceiveResult.Count < 1)
                    {
                        await Task.Delay(100, generalTokenSource.Token);

                        continue;
                    }

                    lastMessage = Encoding.UTF8.GetString(incomingData, 0, webSocketReceiveResult.Count);

                    while (!webSocketReceiveResult.EndOfMessage)
                    {
                        readCompleted = false;
                        try
                        {
                            webSocketReceiveResult = await clientWebSocket.ReceiveAsync(incomingData, readerTokenSource.Token);

                            readCompleted = true;
                        }
                        catch (TaskCanceledException) { /* swallow */ }
                        catch (ThreadAbortException) { /* swallow */ }
                        catch (ObjectDisposedException) { /* swallow */ }
                        catch (OperationCanceledException) { /* swallow */ }
                        catch (WebSocketException)
                        {
                            communication.SendWarningMessage($"PubSub Websocket closed unexpectedly.");
                        }
                        catch (Exception ex)
                        {
                            communication.SendErrorMessage($"PubSub Exception: {ex.GetType().Name}");
                            errorHandler.LogMessageException(ex, "");
                        }

                        if (generalTokenSource.IsCancellationRequested)
                        {
                            //We are quitting
                            break;
                        }

                        if ((readerTokenSource?.IsCancellationRequested ?? true) || !readCompleted)
                        {
                            //We are just restarting reader, since it was intercepted with an exception or the reader token source was cancelled
                            break;
                        }

                        if (webSocketReceiveResult.Count < 1)
                        {
                            communication.SendWarningMessage($"WebSocketMessage returned no characters despite not being at end.  {lastMessage}");
                            break;
                        }

                        lastMessage += Encoding.UTF8.GetString(incomingData, 0, webSocketReceiveResult.Count);
                    }


                    if (generalTokenSource.IsCancellationRequested)
                    {
                        //We are quitting
                        break;
                    }

                    if ((readerTokenSource?.IsCancellationRequested ?? true) || !readCompleted)
                    {
                        //We are just restarting reader, since it was intercepted with an exception or the reader token source was cancelled
                        continue;
                    }

                    BasicPubSubMessage message = JsonSerializer.Deserialize <BasicPubSubMessage>(lastMessage);

                    switch (message.TypeString)
                    {
                    case "PONG":
                        pongReceived = true;
                        break;

                    case "PING":
                        await SendMessage(pongMessage);

                        break;

                    case "RECONNECT":
                        communication.SendDebugMessage($"PubSub Reconnect Message Received");
                        breakConnection = true;
                        break;

                    case "RESPONSE":
                    {
                        PubSubResponseMessage response    = JsonSerializer.Deserialize <PubSubResponseMessage>(lastMessage);
                        PubSubMessage         sentMessage = sentMessages.Where(x => x.Nonce == response.Nonce).FirstOrDefault();

                        if (!string.IsNullOrEmpty(response.ErrorString))
                        {
                            if (sentMessage is not null)
                            {
                                communication.SendErrorMessage($"Error with message {JsonSerializer.Serialize(sentMessage)}: {response.ErrorString}");
                            }
                            else
                            {
                                communication.SendErrorMessage($"Error with message <Unable To Locate>: {response.ErrorString}");
                            }
                        }

                        if (sentMessage is not null)
                        {
                            sentMessages.Remove(sentMessage);
                        }
                    }
                    break;

                    case "MESSAGE":
                    {
                        ListenResponse listenResponse = JsonSerializer.Deserialize <ListenResponse>(lastMessage);

                        BaseMessageData messageData = JsonSerializer.Deserialize <BaseMessageData>(listenResponse.Data.Message);

                        switch (messageData.TypeString)
                        {
                        case "reward-redeemed":
                            ChannelPointMessageData channelPointMessageData = JsonSerializer.Deserialize <ChannelPointMessageData>(listenResponse.Data.Message);
                            redemptionHandler.HandleRedemption(channelPointMessageData.Data);
                            break;

                        default:
                            communication.SendErrorMessage($"Unsupported PubSub Message: {messageData.TypeString} - {listenResponse.Data.Message}");
                            break;
                        }
                    }
                    break;

                    default:
                        communication.SendErrorMessage($"Unsupported PubSub Message Type: {message.TypeString} - {lastMessage}");
                        break;
                    }
                }
            }
            catch (TaskCanceledException) { /* swallow */ }
            catch (ThreadAbortException) { /* swallow */ }
            catch (ObjectDisposedException) { /* swallow */ }
            catch (OperationCanceledException) { /* swallow */ }
            catch (Exception ex)
            {
                communication.SendErrorMessage($"PubSub Exception: {ex.GetType().Name}");
                if (lastMessage is not null)
                {
                    communication.SendErrorMessage($"Last PubSub Message: {lastMessage}");
                }

                errorHandler.LogMessageException(ex, "");
            }
            finally
            {
                readers.Signal();
            }
        }
        //
        // StartResponseHandlerTask()
        //
        // Read responses from the response stream and dispatch events as necessary
        private Task StartResponseHandlerTask()
        {
            OnDebugMessage("Starting Response Handler");
            return(Task.Run(async() =>
            {
                IAsyncEnumerator <ListenResponse> responseStream = DuplexStream.ResponseStream;
                try
                {
                    //ListenerIsActive = true;
                    while (await responseStream.MoveNext(CancellationToken))
                    {
                        ListenResponse response = responseStream.Current;
                        if (response.TargetChange != null)
                        {
                            if (response.TargetChange.TargetChangeType == TargetChangeType.NoChange)
                            {
                                if (response.TargetChange.ResumeToken != null && response.TargetChange.ResumeToken.Length > 0)
                                {
                                    foreach (var request in ActiveRequests)
                                    {
                                        request.AddTarget.ResumeToken = response.TargetChange.ResumeToken;
                                    }
                                }
                            }
                            else if (response.TargetChange.TargetChangeType == TargetChangeType.Add)
                            {
                                //Not much to be done here, if we follow the Node.js example we could set a TargetId when
                                //we create the request, then ensure it is returned
                            }
                            else if (response.TargetChange.TargetChangeType == TargetChangeType.Remove)
                            {
                                //Remove called, shutdown the listener
                                OnError("Document Removed - " + response.TargetChange.Cause.Message);
                                this.Cancel();
                            }
                            else if (response.TargetChange.TargetChangeType == TargetChangeType.Reset)
                            {
                                if (Reset != null)
                                {
                                    Reset(this, new EventArgs());
                                }
                            }
                            else if (response.TargetChange.TargetChangeType == TargetChangeType.Current)
                            {
                                if (Current != null)
                                {
                                    Current(this, new EventArgs());
                                }
                            }
                            else
                            {
                                OnError("Unknown TargetChangeType");
                            }
                        }
                        else if (response.DocumentChange != null)
                        {
                            var snapshot = CreateDocumentSnapshot(response.DocumentChange.Document);
                            if (DocumentChanged != null)
                            {
                                DocumentChanged(this, new DocumentEventArgs {
                                    Document = response.DocumentChange.Document, DocumentSnapshot = snapshot
                                });
                            }
                        }
                        else if (response.DocumentRemove != null)
                        {
                            if (DocumentRemoved != null)
                            {
                                DocumentRemoved(this, new DocumentIdEventArgs {
                                    Id = response.DocumentRemove.Document
                                });
                            }
                        }
                        else if (response.DocumentDelete != null)
                        {
                            if (DocumentDeleted != null)
                            {
                                DocumentDeleted(this, new DocumentIdEventArgs {
                                    Id = response.DocumentDelete.Document
                                });
                            }
                        }
                        else if (response.Filter != null)
                        {
                            if (DocumentFiltered != null)
                            {
                                DocumentFiltered(this, new DocumentCountEventArgs {
                                    Count = response.Filter.Count
                                });
                            }
                        }
                        else
                        {
                            OnError("Unknown listen response type");
                            Cancel();
                        }
                    }
                }
                catch (RpcException ex)
                {
                    ListenerIsActive = false;

                    var status = DuplexStream.GrpcCall.GetStatus();
                    OnDebugMessage(string.Format("Handling Exception: stream status {0} - {1}", status.StatusCode.ToString(), status.Detail));

                    if (CancellationToken.IsCancellationRequested)
                    {
                        OnDebugMessage("Cancel Requested - will not attempt recovery.");
                    }
                    else if (!IsPermanentError(ex))
                    {
                        OnDebugMessage("Attempting to recover");
                        RestartAllRequests();
                    }
                }
                OnDebugMessage("Response Handler Completed");
            }));
        }