public async Task ReceiveMethod_And_SendResult_Test() { // Method call to receive ulong id = 25; var receiving = new Queue <(RpcMessage, int)>(); receiving.Enqueue((RpcMessage.Encode(new RpcMethod { ID = id, Name = "MyMethod" }), 0)); var connection = new ReceivingMockRpcConnection(receiving); // Start channel var channel = await RpcChannel.Create(new RpcPeerInfo(null, "localhost"), connection, new MockRpcMethodExecutor(), backlog : null); _ = channel.Start(); await Task.Delay(200); // Give a moment to execute // Check sent message Assert.AreEqual(1, connection.SentMessages.Count); Assert.AreEqual(new RpcResult { MethodID = id, ReturnValue = new byte[] { 42 } }, connection.SentMessages[0].DecodeRpcResult()); channel.Stop(); }
public void Encode_RpcMethod_Failure() { // With message byte[] actual = RpcMessage.Encode(testResultFailure).Data; CollectionAssert.AreEqual(testResultFailureBytes, actual); // Without message actual = RpcMessage.Encode(testResultFailure_NoMessage).Data; CollectionAssert.AreEqual(testResultFailureBytes_NoMessage, actual); }
public void Encode_RpcMethod() { // With parameters byte[] actual = RpcMessage.Encode(testMethod).Data; CollectionAssert.AreEqual(testMethodBytes, actual); // Without parameters actual = RpcMessage.Encode(testMethod_NoParams).Data; CollectionAssert.AreEqual(testMethodBytes_NoParams, actual); }
public DivMockRpcConnection(bool enableReceivingCalls) { if (enableReceivingCalls) { // While running, create calculation tasks which the peer can receive Task.Run(async() => { ulong nextID = 0; while (isRunning && isReceivingMoreDivs) { var div = Div.CreateNew(nextID++); receiving.Enqueue(RpcMessage.Encode(div.ToMethod())); await Task.Delay(50); } }); } }
public async Task Run_And_ReceiveResult_Test() { // Result to receive ulong id = 25; var responding = new Queue <RpcMessage>(); responding.Enqueue(RpcMessage.Encode(new RpcResult { MethodID = id, ReturnValue = new byte[] { 42 } })); int responseTimeMs = 500; var connection = new RespondingMockRpcConnection(responding, responseTimeMs); // Start channel var channel = await RpcChannel.Create(new RpcPeerInfo(null, "localhost"), connection, new MockRpcMethodExecutor(), backlog : null); _ = channel.Start(); var callTask = channel.Run(new RpcCall { Method = new RpcMethod { ID = id, Name = "MyMethod" } }); // Wait shorter than the response time, nothing should be received yet await Task.Delay(responseTimeMs / 2); Assert.IsFalse(callTask.IsCompleted); // Give remaining time to execute await Task.Delay(responseTimeMs / 2 + 200); // Check received message Assert.IsTrue(callTask.IsCompleted); Assert.AreEqual(new RpcResult { MethodID = id, ReturnValue = new byte[] { 42 } }, callTask.Result); channel.Stop(); }
public async Task Send(RpcMessage message, CancellationToken cancellationToken) { if (message.IsRpcMethod()) { // Peer sent us a div task. "Execute" the method (by waiting executionTimeMs) and // add the result to the receiving queue and log the div. var div = Div.FromMethod(message.DecodeRpcMethod()); Log.Trace($"Div {div.methodID} sent to DivMock"); SentDivs.Enqueue(div); Action setResult = () => { div.result = div.ComputeExpectedResult(); receiving.Enqueue(RpcMessage.Encode(div.result)); Log.Trace($"Div {div.methodID} executed on DivMock"); }; if (executionTimeMs > 0) { _ = Task.Delay(executionTimeMs).ContinueWith(_ => setResult()); } else { setResult(); // Immediate execution on the same thread } } else if (message.IsRpcResult()) { // Peer sent the result from a div call. Log it. var result = message.DecodeRpcResult(); var div = ReceivedDivs.AsEnumerable().FirstOrDefault(it => it.methodID == result.MethodID); if (div == null) { Debug.WriteLine("Unexpected method ID"); } else { div.result = result; } } }
public void Encode_RpcResult_Success() { byte[] actual = RpcMessage.Encode(testResultSuccess).Data; CollectionAssert.AreEqual(testResultSuccessBytes, actual); }
/// <summary> /// Runs the sending operations in a loop, as long as the websocket is open. /// It is sending both the results from remote calls and its own calls. /// It also handles closing requests from this side (triggered by calling <see cref="Stop"/>). /// </summary> private async Task SendLoop() { try { var messagePart = new StringBuilder(); while (connection.IsOpen()) { bool didSomething = false; // Result in the queue? Then send it. if (resultsQueue.TryDequeue(out var queuedResult)) { Log.Trace($"Sending result {queuedResult.MethodID} to {RemotePeer}"); await connection.Send(RpcMessage.Encode(queuedResult), cancellationToken.Token); didSomething = true; } // Current call ran into a timeout? if (currentCall != null && currentCall.Result == null && currentCall.StartTime + GetTimeoutMs(currentCall) < TimeNowMs()) { currentCall.Result = RpcResult.Timeout(currentCall.Method.ID); } // Current call finished? if (currentCall != null && currentCall.Result is RpcResult result) { string logMsg = $"Method {currentCall.Method.ID} {currentCall.Method.Name} finished " + (result.Failure != null ? "with failure " + result.Failure?.Type : "successfully"); if (result.IsRetryNeeded() && currentCall.IsRetryable()) { Log.Trace(logMsg + ", trying it again"); currentCall.ResetStartTimeAndResult(); } else { var discarded = await callsQueue.Dequeue(); Log.Trace(logMsg + $", dequeuing [{discarded?.Method.ID}]"); } if (result.Failure?.Type == RpcFailureType.Timeout) { // When we receive a timeout, we immediately close the connection. // Maybe the connection does not exist any more, although the network lib thinks that it exists. // We experienced this problem when the router switched routes, e.g. from LAN to mobile net Log.Info("Closing connection because of timeout"); cancellationToken.Cancel(); await connection.Close(); break; } if (openCalls.TryGetValue(result.MethodID, out var callExecution)) { callExecution.Finish(result); } currentCall = null; // Take next call from queue } // Next call already queued? Then see if we can send it already. if (currentCall == null && await callsQueue.Peek() is RpcCall call) { // Send it. Do not dequeue it yet, only after is has been finished. var method = RpcMessage.Encode(call.Method); Log.Trace($"Sending method {call.Method.ID} {call.Method.Name} to {RemotePeer}, {method.Data.Length} bytes"); currentCall = call; await connection.Send(method, cancellationToken.Token); didSomething = true; } // Close nicely, when locally requested if (cancellationToken.IsCancellationRequested) { await connection.Close(); didSomething = true; } // When we had something to do, immediately continue. Otherwise, wait a short moment // or until we get notified that the next item is here if (false == didSomething) { sendingWaiter = CreateAsyncTaskCompletionSource <bool>(); // async continuation is crucial await Task.WhenAny(Task.Delay(100), sendingWaiter.Task); } } Log.Debug($"SendLoop: Connection to {RemotePeer} closed."); } catch (Exception ex) { Log.Debug($"Unexpectedly closed connection to {RemotePeer}: {ex.Message}"); } }