From c71585b100e58de82935fd4bceb53b099cff55e4 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sat, 22 Feb 2025 09:28:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=91=D1=8D=D0=BA=D0=B5=D0=BD=D0=B4=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BC=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example.Backend/Example.Backend.csproj | 3 +- Example.Backend/LoadTestImp.cs | 7 ++ Example.Backend/Program.cs | 3 +- Example.Frontend/Program.cs | 1 + Example.Shared/ILoadTest.cs | 6 +- mROA.Codegen/mROASourceGenerator.cs | 22 +++-- mROA/Abstract/ICancellationRepository.cs | 12 +++ mROA/Abstract/IExecuteModule.cs | 2 +- .../Backend/BasicExecutionModule.cs | 81 +++++++++++++------ mROA/Implementation/CancellationRepository.cs | 31 +++++++ .../ExceptionCommandExecution.cs | 7 ++ .../Frontend/RequestExtractor.cs | 2 +- 12 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 mROA/Abstract/ICancellationRepository.cs create mode 100644 mROA/Implementation/CancellationRepository.cs diff --git a/Example.Backend/Example.Backend.csproj b/Example.Backend/Example.Backend.csproj index fc4e81b..3eac518 100644 --- a/Example.Backend/Example.Backend.csproj +++ b/Example.Backend/Example.Backend.csproj @@ -2,7 +2,8 @@ Exe - netstandard2.1 + + net9.0 enable diff --git a/Example.Backend/LoadTestImp.cs b/Example.Backend/LoadTestImp.cs index bc22304..e980aae 100644 --- a/Example.Backend/LoadTestImp.cs +++ b/Example.Backend/LoadTestImp.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Example.Shared; using mROA.Implementation.Attributes; @@ -26,5 +28,10 @@ namespace Example.Backend { throw new NotImplementedException(); } + + public async Task AsyncTest(CancellationToken token) + { + await Task.Delay(TimeSpan.FromSeconds(5), token); + } } } \ No newline at end of file diff --git a/Example.Backend/Program.cs b/Example.Backend/Program.cs index 0a8315a..06bec62 100644 --- a/Example.Backend/Program.cs +++ b/Example.Backend/Program.cs @@ -34,7 +34,8 @@ class Program builder.Modules.Add(new CreativeRepresentationModuleProducer( new IInjectableModule[] { builder.GetModule()! }, typeof(RepresentationModule))); - + builder.Modules.Add(new CancellationRepository()); + builder.Build(); new RemoteTypeBinder(); diff --git a/Example.Frontend/Program.cs b/Example.Frontend/Program.cs index b9bf3a7..63a25b2 100644 --- a/Example.Frontend/Program.cs +++ b/Example.Frontend/Program.cs @@ -47,6 +47,7 @@ class Program var name = printer.Value.GetName(); Console.WriteLine("Printer name : {0}", name); + Thread.Sleep(100); factory.Register(new SharedObject(new ClientBasedPrinter())); diff --git a/Example.Shared/ILoadTest.cs b/Example.Shared/ILoadTest.cs index 326822f..e9f958f 100644 --- a/Example.Shared/ILoadTest.cs +++ b/Example.Shared/ILoadTest.cs @@ -1,4 +1,6 @@ -using mROA.Implementation.Attributes; +using System.Threading; +using System.Threading.Tasks; +using mROA.Implementation.Attributes; namespace Example.Shared { @@ -9,6 +11,8 @@ namespace Example.Shared int Last(int next); void C(); void A(); + + Task AsyncTest(CancellationToken token = default); } } diff --git a/mROA.Codegen/mROASourceGenerator.cs b/mROA.Codegen/mROASourceGenerator.cs index db252ae..84fef04 100644 --- a/mROA.Codegen/mROASourceGenerator.cs +++ b/mROA.Codegen/mROASourceGenerator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -120,7 +121,8 @@ namespace {Namespace} bool isAsync = method.ReturnType.Name == "Task"; - bool isVoid = method.ReturnType.Name == "Void" || method.ReturnType.ToString() == "Task"; + bool isVoid = method.ReturnType.Name == "Void" || + method.ReturnType.ToString() == "System.Threading.Tasks.Task"; bool isParametrized = method.Parameters.Length == 1 && !isAsync || method.Parameters.Length == 2 && isAsync; @@ -135,9 +137,16 @@ namespace {Namespace} var prefix = isAsync ? "await " : ""; var postfix = !isAsync ? (isVoid ? ".Wait()" : ".GetAwaiter().GetResult()") : ""; var parameterLink = isParametrized ? ", " + method.Parameters.First().Name : string.Empty; - var caller = isVoid ? $"CallAsync({index}{parameterLink})" : - isAsync ? $"GetResultAsync<{ExtractTaskType(method.ReturnType)}>({index}{parameterLink})" : - $"GetResultAsync<{ToFullString(method.ReturnType)}>({index}{parameterLink})"; + var tokenInsert = isAsync + ? isParametrized + ? ", cancellationToken : " + method.Parameters[1].Name + : ", cancellationToken : " + method.Parameters[0].Name + : String.Empty; + var caller = isVoid + ? $"CallAsync({index}{parameterLink}{tokenInsert})" + : isAsync + ? $"GetResultAsync<{ExtractTaskType(method.ReturnType)}>({index}{parameterLink}{tokenInsert})" + : $"GetResultAsync<{ToFullString(method.ReturnType)}>({index}{parameterLink}{tokenInsert})"; if (!isVoid) prefix = "return " + prefix; @@ -195,7 +204,6 @@ namespace {namespaceName} "; - // Add the source code to the compilation. context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8)); @@ -250,7 +258,7 @@ namespace mROA.Codegen "; context.AddSource($"CoCodegenMethodRepository.g.cs", SourceText.From(coCodegenRepoCode, Encoding.UTF8)); } - + if (frontendContextRepo.Count != 0) { var fronendRepoCode = @$"// @@ -324,7 +332,7 @@ namespace mROA.Codegen GenerateCode(context, context.Compilation, interfaces.ToImmutableArray()); } - + private bool ContainsSOIAttribute(SyntaxList attributes, GeneratorExecutionContext context, InterfaceDeclarationSyntax interfaceDeclarationSyntax) { diff --git a/mROA/Abstract/ICancellationRepository.cs b/mROA/Abstract/ICancellationRepository.cs new file mode 100644 index 0000000..489a648 --- /dev/null +++ b/mROA/Abstract/ICancellationRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading; + +namespace mROA.Abstract +{ + public interface ICancellationRepository : IInjectableModule + { + void RegisterCancellation(Guid id, CancellationTokenSource cts); + CancellationTokenSource? GetCancellation(Guid id); + void FreeCancelation(Guid id); + } +} \ No newline at end of file diff --git a/mROA/Abstract/IExecuteModule.cs b/mROA/Abstract/IExecuteModule.cs index 2ebdd24..29f5211 100644 --- a/mROA/Abstract/IExecuteModule.cs +++ b/mROA/Abstract/IExecuteModule.cs @@ -4,6 +4,6 @@ namespace mROA.Abstract { public interface IExecuteModule : IInjectableModule { - ICommandExecution Execute(ICallRequest command, IContextRepository contextRepository); + ICommandExecution Execute(ICallRequest command, IContextRepository contextRepository, IRepresentationModule representationModule); } } \ No newline at end of file diff --git a/mROA/Implementation/Backend/BasicExecutionModule.cs b/mROA/Implementation/Backend/BasicExecutionModule.cs index 8f5ac39..ec57413 100644 --- a/mROA/Implementation/Backend/BasicExecutionModule.cs +++ b/mROA/Implementation/Backend/BasicExecutionModule.cs @@ -10,20 +10,26 @@ namespace mROA.Implementation.Backend public class BasicExecutionModule : IExecuteModule { private IMethodRepository? _methodRepo; + private ICancellationRepository? _cancellationRepo; public void Inject(T dependency) { if (dependency is IMethodRepository methodRepo) _methodRepo = methodRepo; + if (dependency is ICancellationRepository cancellationRepo) _cancellationRepo = cancellationRepo; } - public ICommandExecution Execute(ICallRequest command, IContextRepository contextRepository) + public ICommandExecution Execute(ICallRequest command, IContextRepository contextRepository, + IRepresentationModule representationModule) { + if (_cancellationRepo is null) + throw new NullReferenceException("Method repository was not defined"); + if (_methodRepo is null) throw new NullReferenceException("Method repository was not defined"); - + if (contextRepository is null) throw new NullReferenceException("Context repository was not defined"); - + var currentCommand = _methodRepo.GetMethod(command.CommandId); if (currentCommand == null) throw new Exception($"Command {command.CommandId} not found"); @@ -35,10 +41,10 @@ namespace mROA.Implementation.Backend if (currentCommand.ReturnType.BaseType == typeof(Task) && currentCommand.ReturnType.GenericTypeArguments.Length == 1) - return TypedExecuteAsync(currentCommand, context, parameter, command); + return TypedExecuteAsync(currentCommand, context, parameter, command, _cancellationRepo, representationModule); if (currentCommand.ReturnType == typeof(Task)) - return ExecuteAsync(currentCommand, context, parameter, command); + return ExecuteAsync(currentCommand, context, parameter, command, _cancellationRepo, representationModule); return Execute(currentCommand, context, parameter, command); } @@ -48,8 +54,10 @@ namespace mROA.Implementation.Backend { try { - var finalResult = currentCommand.Invoke(context, parameter is null ? new object[0] : new[] - { parameter }); + var finalResult = currentCommand.Invoke(context, parameter is null + ? new object[0] + : new[] + { parameter }); return new TypedFinalCommandExecution { CommandId = command.CommandId, Result = finalResult, @@ -68,20 +76,34 @@ namespace mROA.Implementation.Backend } private static ICommandExecution ExecuteAsync(MethodInfo currentCommand, object context, object? parameter, - ICallRequest command) + ICallRequest command, ICancellationRepository cancellationRepository, IRepresentationModule representationModule) { var tokenSource = new CancellationTokenSource(); + cancellationRepository.RegisterCancellation(command.Id, tokenSource); var token = tokenSource.Token; try { - var result = (Task)currentCommand.Invoke(context, parameter is null ? new object[] { token } : new[] - { parameter, token })!; + var result = (Task)currentCommand.Invoke(context, parameter is null + ? new object[] { token } + : new[] + { parameter, token })!; - result.Wait(token); + result.ContinueWith(_ => + { + var payload = new FinalCommandExecution + { + Id = command.Id, + CommandId = command.CommandId + }; + representationModule.PostCallMessage(command.Id, MessageType.FinishedCommandExecution, payload); + }, token); - - return new FinalCommandExecution { CommandId = command.CommandId, Id = command.Id }; + return new AsyncCommandExecution + { + Id = command.Id, CommandId = command.CommandId + }; + } catch (Exception e) { @@ -94,25 +116,36 @@ namespace mROA.Implementation.Backend } private static ICommandExecution TypedExecuteAsync(MethodInfo currentCommand, object context, object? parameter, - ICallRequest command) + ICallRequest command, ICancellationRepository cancellationRepository, IRepresentationModule representationModule) { var tokenSource = new CancellationTokenSource(); + cancellationRepository.RegisterCancellation(command.Id, tokenSource); + var token = tokenSource.Token; try { var result = - (Task)currentCommand.Invoke(context, parameter is null ? new object[] { token } : new[] - { parameter, token })!; + (Task)currentCommand.Invoke(context, parameter is null + ? new object[] { token } + : new[] + { parameter, token })!; - result.Wait(token); - - var finalResult = result.GetType().GetProperty("Result")?.GetValue(result); - return new TypedFinalCommandExecution + result.ContinueWith(t => { - Id = command.Id, - Result = finalResult, - CommandId = command.CommandId, - Type = finalResult?.GetType() + var finalResult = t.GetType().GetProperty("Result")?.GetValue(t); + var payload = new TypedFinalCommandExecution + { + Id = command.Id, + Result = finalResult, + CommandId = command.CommandId, + Type = finalResult?.GetType() + }; + representationModule.PostCallMessage(command.Id, MessageType.FinishedCommandExecution, payload); + }, token); + + return new AsyncCommandExecution + { + Id = command.Id, CommandId = command.CommandId }; } catch (Exception e) diff --git a/mROA/Implementation/CancellationRepository.cs b/mROA/Implementation/CancellationRepository.cs new file mode 100644 index 0000000..3c5659e --- /dev/null +++ b/mROA/Implementation/CancellationRepository.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using mROA.Abstract; + +namespace mROA.Implementation +{ + public class CancellationRepository : ICancellationRepository + { + private Dictionary _cancellations = new(); + public void RegisterCancellation(Guid id, CancellationTokenSource cts) + { + _cancellations.TryAdd(id, cts); + } + + public CancellationTokenSource? GetCancellation(Guid id) + { + return _cancellations.GetValueOrDefault(id, null); + } + + public void FreeCancelation(Guid id) + { + _cancellations.Remove(id); + } + + public void Inject(T dependency) + { + + } + } +} \ No newline at end of file diff --git a/mROA/Implementation/CommandExecution/ExceptionCommandExecution.cs b/mROA/Implementation/CommandExecution/ExceptionCommandExecution.cs index c9d32ab..6d18129 100644 --- a/mROA/Implementation/CommandExecution/ExceptionCommandExecution.cs +++ b/mROA/Implementation/CommandExecution/ExceptionCommandExecution.cs @@ -16,4 +16,11 @@ namespace mROA.Implementation.CommandExecution return new RemoteException(Exception) { CallRequestId = Id }; } } + + public class AsyncCommandExecution : ICommandExecution + { + public Guid Id { get; set; } + public int ClientId { get; set; } + public int CommandId { get; set; } + } } \ No newline at end of file diff --git a/mROA/Implementation/Frontend/RequestExtractor.cs b/mROA/Implementation/Frontend/RequestExtractor.cs index 7716314..25232bb 100644 --- a/mROA/Implementation/Frontend/RequestExtractor.cs +++ b/mROA/Implementation/Frontend/RequestExtractor.cs @@ -74,7 +74,7 @@ namespace mROA.Implementation.Frontend request.Parameter = _serializationToolkit.Cast(request.Parameter, parameterType); } - var result = _executeModule.Execute(request, _contextRepository); + var result = _executeModule.Execute(request, _contextRepository, _representationModule); var resultType = result is FinalCommandExecution ? MessageType.FinishedCommandExecution