/// <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); }
/// <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); }
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}")); } }
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"); })); }