Beispiel #1
0
 /// <summary>
 /// Executes the given RPC command locally on the client immediately and returns the result.
 /// No exception is thrown, but a <see cref="RpcFailure"/> result is set in case of a failure.
 /// </summary>
 public async Task <RpcCommandResult> ExecuteLocallyNow(RpcCommand command)
 {
     // Do not run the same command twice. If the command with this ID was already
     // executed, return the cached result. If the cache is not available any more, return a
     // obsolete function call failure.
     if (serverCache.GetCachedResult(command.ID) is RpcCommandResult result)
     {
         return(result);
     }
     // Execute the command
     try {
         var runner = new RpcCommandRunner(clientMethods(), null);
         result = await runner.Execute(clientConfig.ClientID, command);
     }
     catch (Exception ex) {
         result = RpcCommandResult.FromFailure(command.ID,
                                               new RpcFailure(RpcFailureType.RemoteException, ex.Message));
     }
     // Cache result, if there was no network problem
     if (false == (result.Failure?.IsNetworkProblem == true))
     {
         serverCache.CacheResult(result);
     }
     return(result);
 }
Beispiel #2
0
        /// <summary>
        /// Call this method when the client called the "/rpc/push"-endpoint.
        /// It executes the given RPC command immediately and returns the result.
        /// No exception is thrown, but a <see cref="RpcFailure"/> result is set in case of a failure.
        /// </summary>
        public async Task <RpcCommandResult> OnClientPush(string clientID, RpcCommand command, RpcCommandRunner runner)
        {
            // Do not run the same command twice. If the command with this ID was already
            // executed, return the cached result. If the cache is not available any more, return a
            // obsolete function call failure.
            var client = clients.GetClient(clientID, commandBacklog);

            if (client.GetCachedResult(command.ID) is RpcCommandResult result)
            {
                return(result);
            }
            // Execute the command
            try {
                result = await runner.Execute(clientID, command);
            }
            catch (Exception ex) {
                result = RpcCommandResult.FromFailure(command.ID,
                                                      new RpcFailure(RpcFailureType.RemoteException, ex.Message));
            }
            // Cache result, if there was no network problem
            if (false == (result.Failure?.IsNetworkProblem == true))
            {
                client.CacheResult(result);
            }
            return(result);
        }
Beispiel #3
0
 /// <summary>
 /// Caches the given command result, so that repeated calls of the same
 /// command ID can be answered without executing the command again.
 /// </summary>
 public void CacheResult(RpcCommandResult result)
 {
     lastCachedResultCommandID = Math.Max(lastCachedResultCommandID, result.CommandID);
     cachedResults.Enqueue(result);
     while (cachedResults.Count > maxQueueSize)
     {
         cachedResults.TryDequeue(out _);
     }
 }
Beispiel #4
0
 /// <summary>
 /// Runs the given command on the client ID.
 /// </summary>
 public async Task <RpcCommandResult> Execute(string clientID, RpcCommand command)
 {
     foreach (var rpc in rpcFunctions)
     {
         rpc.Context = new RpcContext(clientID, serviceScopeFactory);
         if (rpc.Execute(command) is Task <string?> task)
         {
             // Method found in this class
             return(RpcCommandResult.FromSuccess(command.ID, await task));
         }
     }
     // Called method is not implemented in any registered class
     throw new Exception("Unknown method name: " + command.MethodName);
 }
Beispiel #5
0
        /// <summary>
        /// Gets the cached result of the command with the given ID, if it was executed already,
        /// or null, if it is a new command which has to be executed now.
        /// If the command was already executed, but is too old so that there is no cached result
        /// any more, a failure result with <see cref="RpcFailureType.ObsoleteCommandID"/> is returned.
        /// </summary>
        public RpcCommandResult?GetCachedResult(ulong commandID)
        {
            // New command?
            if (commandID > lastCachedResultCommandID)
            {
                return(null);
            }
            // It is an old command. Find the cached result.
            var cachedResults = this.cachedResults.ToList(); // Defensive copy
            var result        = cachedResults.Find(it => it.CommandID == commandID);

            return(result ?? RpcCommandResult.FromFailure(commandID, new RpcFailure(
                                                              RpcFailureType.ObsoleteCommandID, $"Command ID {commandID} already executed too long ago " +
                                                              (ClientID != null ? $"on the client {ClientID}" : "for the server"))));
        }
Beispiel #6
0
        /// <summary>
        /// Sends the given command to the server now, reads the result or catches the
        /// exception, and sets the command's state accordingly.
        /// </summary>
        private async Task ExecuteOnServerNow(RpcCommand command)
        {
            RpcCommandResult result;
            var bodyJson = JsonLib.ToJson(command);

            try {
                var httpResponse = await httpPush.PostAsync(clientConfig.ServerUrl + "/push",
                                                            new StringContent(bodyJson, Encoding.UTF8, "application/json"));

                if (httpResponse.IsSuccessStatusCode)
                {
                    // Response (either success or remote failure) received.
                    result = JsonLib.FromJson <RpcCommandResult>(await httpResponse.Content.ReadAsStringAsync());
                }
                else
                {
                    // The server did not respond with 200 (which it should do even in case of
                    // a remote exception). So there is a communication error.
                    result = RpcCommandResult.FromFailure(command.ID,
                                                          new RpcFailure(RpcFailureType.RpcError, "Remote side problem with RPC call. HTTP status code " +
                                                                         (int)httpResponse.StatusCode));
                }
            }
            catch {
                // Could not reach server.
                result = RpcCommandResult.FromFailure(command.ID,
                                                      new RpcFailure(RpcFailureType.Timeout, "Could not reach the server"));
            }
            // When a result was received (i.e. when there was no network problem), the command is finished
            if (false == (result.Failure?.IsNetworkProblem == true) && command.ID == serverCache.CurrentCommand?.ID)
            {
                serverCache.FinishCurrentCommand();
            }
            // Finish command
            command.Finish(result);
        }