/// <summary> /// Creates a waiting operation for this session. Could be a remote cancellation request or a pending result request. /// </summary> /// <returns>Wait operation to wait on.</returns> public RpcWaitHandle CreateWaitHandle() { var return_wait = new RpcWaitHandle { ReturnResetEvent = new ManualResetEventSlim() }; // Lock the id incrementation to prevent duplicates. lock (rpc_call_id_lock) { if (++rpc_call_id > ushort.MaxValue) { rpc_call_id = 0; } return_wait.Id = (ushort)rpc_call_id; } // Add the wait to the outstanding wait dictionary for retrieval later. if (LocalWaitHandles.TryAdd(return_wait.Id, return_wait) == false) { throw new InvalidOperationException($"Id {return_wait.Id} already exists in the return_wait_handles dictionary."); } return(return_wait); }
/// <summary> /// Processes the incoming Rpc call from the recipient connection. /// </summary> /// <param name="message">Message containing the Rpc call.</param> /// <param name="message_type">Type of call this message is.</param> private void ProcessRpcCall(MqMessage message, RpcCallMessageType message_type) { // Execute the processing on the worker thread. Task.Run(() => { // Retrieve a serialization cache to work with. var serialization = Session.SerializationCache.Get(message); ushort rec_message_return_id = 0; try { // Skip Handler.Id & RpcMessageType serialization.MessageReader.Skip(2); // Determine if this call has a return value. if (message_type == RpcCallMessageType.MethodCall) { rec_message_return_id = serialization.MessageReader.ReadUInt16(); } // Read the string service name, method and number of arguments. var rec_service_name = serialization.MessageReader.ReadString(); var rec_method_name = serialization.MessageReader.ReadString(); var rec_argument_count = serialization.MessageReader.ReadByte(); // Verify that the requested service exists. if (Services.ContainsKey(rec_service_name) == false) { throw new Exception($"Service '{rec_service_name}' does not exist."); } // Get the service from the instance list. var service = Services[rec_service_name]; // Get the actual method. TODO: Might want to cache this for performance purposes. var method_info = service.GetType().GetMethod(rec_method_name); var method_parameters = method_info.GetParameters(); // Determine if the last parameter is a cancellation token. var last_param = method_info.GetParameters().LastOrDefault(); var cancellation_source = new CancellationTokenSource(); // Number used to increase the number of parameters if there is a cancellation token. int cancellation_token_param = 0; RpcWaitHandle cancellation_wait; // If the past parameter is a cancellation token, setup a return wait for this call to allow for remote cancellation. if (rec_message_return_id != 0 && last_param?.ParameterType == typeof(CancellationToken)) { cancellation_wait = new RpcWaitHandle { Token = cancellation_source.Token, TokenSource = cancellation_source, Id = rec_message_return_id }; // Set the number to 1 to increase the parameter number by one. cancellation_token_param = 1; // Add it to the main list of ongoing operations. RemoteWaitHandles.TryAdd(rec_message_return_id, cancellation_wait); } // Setup the parameters to pass to the invoked method. object[] parameters = new object[rec_argument_count + cancellation_token_param]; // Determine if we have any parameters to pass to the invoked method. if (rec_argument_count > 0) { serialization.PrepareDeserializeReader(); // Parse each parameter to the parameter list. for (int i = 0; i < rec_argument_count; i++) { parameters[i] = serialization.DeserializeFromReader(method_parameters[i].ParameterType, i); } } // Add the cancellation token to the parameters. if (cancellation_token_param > 0) { parameters[parameters.Length - 1] = cancellation_source.Token; } object return_value; try { // Invoke the requested method. return_value = method_info.Invoke(service, parameters); } catch (Exception ex) { // Determine if this method was waited on. If it was and an exception was thrown, // Let the recipient session know an exception was thrown. if (rec_message_return_id != 0 && ex.InnerException?.GetType() != typeof(OperationCanceledException)) { SendRpcException(serialization, ex, rec_message_return_id); } return; } finally { // Remove the cancellation wait if it exists. LocalWaitHandles.TryRemove(rec_message_return_id, out cancellation_wait); } // Determine what to do with the return value. if (message_type == RpcCallMessageType.MethodCall) { // Reset the stream. serialization.Stream.SetLength(0); // Write the Rpc call type and the id. serialization.MessageWriter.Clear(); serialization.MessageWriter.Write(Id); serialization.MessageWriter.Write((byte)RpcCallMessageType.MethodReturn); serialization.MessageWriter.Write(rec_message_return_id); // Serialize the return value and add it to the stream. serialization.SerializeToWriter(return_value, 0); // Send the return value message to the recipient. Session.Send(serialization.MessageWriter.ToMessage(true)); } // Return the serialization to the cache to be reused. Session.SerializationCache.Put(serialization); } catch (Exception ex) { // If an exception occurred, notify the recipient connection. SendRpcException(serialization, ex, rec_message_return_id); Session.SerializationCache.Put(serialization); } }); }