diff --git a/Directory.Build.props b/Directory.Build.props index b6ef25a0e..13099aef2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ 1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618 true enable - 4.10.1 + 4.11.0 $(PackageProjectUrl) true true diff --git a/WolverineWebApiFSharp/Program.fs b/WolverineWebApiFSharp/Program.fs new file mode 100644 index 000000000..84e53fa76 --- /dev/null +++ b/WolverineWebApiFSharp/Program.fs @@ -0,0 +1,20 @@ + +open System +open JasperFx +open Marten +open Microsoft.AspNetCore.Builder +open Wolverine +open Wolverine.Http + +// We'll come back to this later. Used it for now to manually test some changes +// let args = Environment.GetCommandLineArgs()[1..] +// +// let builder = WebApplication.CreateBuilder(args) +// +// builder.Host.UseWolverine() |> ignore +// builder.Services.AddWolverineHttp() |> ignore +// builder.Services.AddMarten("Host=localhost;Port=12345;Username=postgres;Password=postgres;Database=postgres") |> ignore +// +// let app = builder.Build() +// app.MapWolverineEndpoints(); +// app.RunJasperFxCommands(args).GetAwaiter().GetResult() |> ignore \ No newline at end of file diff --git a/WolverineWebApiFSharp/SideEffects.fs b/WolverineWebApiFSharp/SideEffects.fs new file mode 100644 index 000000000..27f4927bb --- /dev/null +++ b/WolverineWebApiFSharp/SideEffects.fs @@ -0,0 +1,43 @@ +module WolverineWebApiFSharp.SideEffects + +open System +open Wolverine +open Wolverine.Http +open Wolverine.Marten + +type Command = { Name: string } + +(* I cannot define a custom side effect here because code generation does not recognize the Execute method +Unhandled exception. Wolverine.InvalidSideEffectException: Invalid Wolverine side effect exception for Wolverine.ISideEffect, no public Execute/ExecuteAsync method found + at Wolverine.SideEffectPolicy.applySideEffectExecution(Variable effect, IChain chain) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 93 + at Wolverine.SideEffectPolicy.lookForSingularSideEffects(GenerationRules rules, IServiceContainer container, IChain chain) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 62 + at Wolverine.SideEffectPolicy.Apply(IReadOnlyList`1 chains, GenerationRules rules, IServiceContainer container) in /Users/marcpiechura/RiderProjects/wolverine/src/Wolverine/ISideEffect.cs:line 42 + at Wolverine.Http.HttpGraph.DiscoverEndpoints(WolverineHttpOptions wolverineHttpOptions) in /Users/marcpiechura/RiderProjects/wolverine/src/Http/Wolverine.Http/HttpGraph.cs:line 107 + at Wolverine.Http.WolverineHttpEndpointRouteBuilderExtensions.MapWolverineEndpoints(IEndpointRouteBuilder endpoints, Action`1 configure) in /Users/marcpiechura/RiderProjects/wolverine/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs:line 202 + +*) +type SomeSideEffect() = + static member val WasExecuted = false with get, set + + interface ISideEffect + + member this.Execute() = + SomeSideEffect.WasExecuted <- true + + +type Event = { Id: Guid; Name: string } +type SomeType = { Id: Guid } + +// this one is a bit more tricky, generally using ISideEffect works, +// but for MartenOps.StartStream one has to provide the generic type parameter, otherwise +// codegen will not generate a handler at all. + +[] +[] +let post (command: Command) = + let event: Event = { Id = Guid.NewGuid(); Name = command.Name } + //this doesn't work + MartenOps.StartStream(event.Id, box event) + + // but this does + //MartenOps.StartStream(event.Id, box event) \ No newline at end of file diff --git a/WolverineWebApiFSharp/TupleSupport.fs b/WolverineWebApiFSharp/TupleSupport.fs new file mode 100644 index 000000000..33ceeb08e --- /dev/null +++ b/WolverineWebApiFSharp/TupleSupport.fs @@ -0,0 +1,64 @@ +namespace WolverineWebApiFSharp.TupleSupport + +open System +open Microsoft.AspNetCore.Mvc +open Wolverine.Http + +type Command = { SomeProperty: string } + +// The root problem is, that F# uses System.Tuple by default, but C# uses System.ValueTuple when defining a tuple with (int, string) syntax. +// A workaround for F# is to use struct(int, string) which compiles to ValueTuple. +// That affects at least the ability to pass values from Before/Validate.. methods to the handler method, +// as well as the ability to use cascading messages in the handler method. + +module ValidationSupport = + (* Codegen looks like this: + public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) + { + // Reading the request body via JSON deserialization + var (command, jsonContinue) = await ReadJsonAsync(httpContext); + if (jsonContinue == Wolverine.HandlerContinuation.Stop) return; + var validationResult = httpContext.Request.Query["validationResult"]; + var tuple = WolverineWebApiFSharp.TupleSupport.ValidationSupport.Validate(command); + + // The actual HTTP request handler execution + var result_of_post = WolverineWebApiFSharp.TupleSupport.ValidationSupport.post(command, validationResult); + + await WriteString(httpContext, result_of_post); + } + *) + + let Validate (command: Command) = + if String.IsNullOrWhiteSpace(command.SomeProperty) then + ProblemDetails(Status = 400), "" + else + WolverineContinue.NoProblems, command.SomeProperty + + + [] + let post (command: Command, validationResult: string) = + validationResult + + +module CascadingMessages = + (* Codegen looks like this + public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) + { + // Reading the request body via JSON deserialization + var (command, jsonContinue) = await ReadJsonAsync(httpContext); + if (jsonContinue == Wolverine.HandlerContinuation.Stop) return; + + // The actual HTTP request handler execution + var tuple_response = WolverineWebApiFSharp.TupleSupport.AdditionalReturnValues.post(command); + + // Writing the response body to JSON because this was the first 'return variable' in the method signature + await WriteJsonAsync(httpContext, tuple_response); + } + *) + + type Event = { Id: Guid } + + [] + let post (command: Command) = + let event = { Id = Guid.NewGuid() } + event.Id, event \ No newline at end of file diff --git a/WolverineWebApiFSharp/UnitSupport.fs b/WolverineWebApiFSharp/UnitSupport.fs new file mode 100644 index 000000000..4476fe2ef --- /dev/null +++ b/WolverineWebApiFSharp/UnitSupport.fs @@ -0,0 +1,43 @@ +namespace WolverineWebApiFSharp.UnitSupport + +open System.Threading.Tasks +open Wolverine.Http + +type Command = { SomeProperty: string } + +// In F# the type Unit defines the absence of a value, similar to void in C#. +// In C# it is represented as a Task with no result, which is equivalent to Task. +// But the codegen treats the Unit type as a regular type and generates a cascaded message call for it. +// I've only encountered this in the context of Task handlers, but will add more examples if I find them. + +module TaskUnitSupport = + (* Codegen looks like this: + public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) + { + var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime); + // Reading the request body via JSON deserialization + var (command, jsonContinue) = await ReadJsonAsync(httpContext); + if (jsonContinue == Wolverine.HandlerContinuation.Stop) return; + + // The actual HTTP request handler execution + var unit = await WolverineWebApiFSharp.UnitSupport.TaskUnitSupport.post(command).ConfigureAwait(false); + + + // Outgoing, cascaded message + await messageContext.EnqueueCascadingAsync(unit).ConfigureAwait(false); + + // Wolverine automatically sets the status code to 204 for empty responses + if (httpContext.Response is { HasStarted: false, StatusCode: 200 }) httpContext.Response.StatusCode = 204; + + // Have to flush outgoing messages just in case Marten did nothing because of https://github.com/JasperFx/wolverine/issues/536 + await messageContext.FlushOutgoingMessagesAsync().ConfigureAwait(false); + + } + *) + + [] + [] + let post (command: Command) = task { + do! Task.Delay 10 + } + \ No newline at end of file diff --git a/WolverineWebApiFSharp/WolverineWebApiFSharp.fsproj b/WolverineWebApiFSharp/WolverineWebApiFSharp.fsproj index 48b4ccda3..58ba7eafc 100644 --- a/WolverineWebApiFSharp/WolverineWebApiFSharp.fsproj +++ b/WolverineWebApiFSharp/WolverineWebApiFSharp.fsproj @@ -4,14 +4,18 @@ net8.0 true 8.0 + Exe + + + diff --git a/docs/guide/durability/efcore/operations.md b/docs/guide/durability/efcore/operations.md index 92ff00253..c016a1f90 100644 --- a/docs/guide/durability/efcore/operations.md +++ b/docs/guide/durability/efcore/operations.md @@ -141,4 +141,7 @@ public static class TodoHandler Wolverine also supports the usage of the `[Entity]` attribute to load entity data by its identity with EF Core. As you'd expect, Wolverine can "find" the right EF Core `DbContext` type for the entity type through IoC service registrations. +The loaded EF core entity does not included related entities. +For more information on the usage of this attribute see +[Automatically loading entities to method parameters](/guide/handlers/persistence#automatically-loading-entities-to-method-parameters). diff --git a/src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs b/src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs index 50de08368..f33d60f65 100644 --- a/src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs +++ b/src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs @@ -53,7 +53,7 @@ public RouteEndpoint BuildEndpoint(RouteWarmup warmup) if (Endpoint != null) return Endpoint; RequestDelegate? requestDelegate = null; - if (_parent.Rules.TypeLoadMode == TypeLoadMode.Static) + if (_parent.Rules.TypeLoadMode == TypeLoadMode.Static && !DynamicCodeBuilder.WithinCodegenCommand) { this.InitializeSynchronously(_parent.Rules, _parent, _parent.Container.Services); var handler = (HttpHandler)_parent.Container.QuickBuild(_handlerType); diff --git a/src/Http/WolverineWebApi/WolverineWebApi.csproj b/src/Http/WolverineWebApi/WolverineWebApi.csproj index a7ad3a7a1..87c4cc6cc 100644 --- a/src/Http/WolverineWebApi/WolverineWebApi.csproj +++ b/src/Http/WolverineWebApi/WolverineWebApi.csproj @@ -36,6 +36,164 @@ Servers.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs b/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs index 26cc28ce4..99e066cd4 100644 --- a/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs +++ b/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs @@ -22,6 +22,7 @@ using Wolverine.SqlServer; using Wolverine.Tracking; using Wolverine.Transports; +using IServiceContainer = Wolverine.Runtime.IServiceContainer; namespace EfCoreTests; diff --git a/src/Persistence/MartenTests/basic_marten_integration.cs b/src/Persistence/MartenTests/basic_marten_integration.cs index 47e30ba8b..cd24a828e 100644 --- a/src/Persistence/MartenTests/basic_marten_integration.cs +++ b/src/Persistence/MartenTests/basic_marten_integration.cs @@ -15,6 +15,7 @@ using Wolverine.Persistence.Durability; using Wolverine.Postgresql; using Wolverine.Runtime; +using IServiceContainer = Wolverine.Runtime.IServiceContainer; namespace MartenTests; diff --git a/src/Persistence/MartenTests/transactional_frame_end_to_end.cs b/src/Persistence/MartenTests/transactional_frame_end_to_end.cs index dbc9bfdf0..6582dcd27 100644 --- a/src/Persistence/MartenTests/transactional_frame_end_to_end.cs +++ b/src/Persistence/MartenTests/transactional_frame_end_to_end.cs @@ -15,6 +15,7 @@ using Wolverine.Marten.Codegen; using Wolverine.Runtime; using Wolverine.Runtime.Handlers; +using IServiceContainer = Wolverine.Runtime.IServiceContainer; namespace MartenTests; diff --git a/src/Persistence/Wolverine.Marten/Wolverine.Marten.csproj b/src/Persistence/Wolverine.Marten/Wolverine.Marten.csproj index 9906f35ee..f2c23f81d 100644 --- a/src/Persistence/Wolverine.Marten/Wolverine.Marten.csproj +++ b/src/Persistence/Wolverine.Marten/Wolverine.Marten.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Persistence/Wolverine.RDBMS/MessageDatabase.DeadLetters.cs b/src/Persistence/Wolverine.RDBMS/MessageDatabase.DeadLetters.cs index 7652baea1..89d3f4841 100644 --- a/src/Persistence/Wolverine.RDBMS/MessageDatabase.DeadLetters.cs +++ b/src/Persistence/Wolverine.RDBMS/MessageDatabase.DeadLetters.cs @@ -40,6 +40,11 @@ public async Task QueryDeadLetterEnvelopesAsync(DeadLe query += $" and {DatabaseConstants.Id} >= @startId"; } + if (queryParameters.Limit > 0) + { + query += " limit @limit"; + } + var command = CreateCommand(query); if (!string.IsNullOrEmpty(queryParameters.ExceptionType)) @@ -72,15 +77,18 @@ public async Task QueryDeadLetterEnvelopesAsync(DeadLe command = command.With("startId", queryParameters.StartId.Value); } - command.With("limit", queryParameters.Limit); + if (queryParameters.Limit > 0) + { + command = command.With("limit", queryParameters.Limit + 1); + } var deadLetterEnvelopes = (List)await command.FetchListAsync(reader => DatabasePersistence.ReadDeadLetterAsync(reader, _cancellation), cancellation: _cancellation); - if (deadLetterEnvelopes.Count > queryParameters.Limit) + if (queryParameters.Limit > 0 && deadLetterEnvelopes.Count > queryParameters.Limit) { - deadLetterEnvelopes.RemoveAt(deadLetterEnvelopes.Count - 1); var nextId = deadLetterEnvelopes.LastOrDefault()?.Envelope.Id; + deadLetterEnvelopes.RemoveAt(deadLetterEnvelopes.Count - 1); return new(deadLetterEnvelopes, nextId, tenantId); } diff --git a/src/Testing/CoreTests/Runtime/Handlers/HandlerGraphTests.cs b/src/Testing/CoreTests/Runtime/Handlers/HandlerGraphTests.cs index a3de0368f..519d815f7 100644 --- a/src/Testing/CoreTests/Runtime/Handlers/HandlerGraphTests.cs +++ b/src/Testing/CoreTests/Runtime/Handlers/HandlerGraphTests.cs @@ -1,8 +1,12 @@ -using System.Diagnostics; +using ImTools; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Module1; +using System.Diagnostics; +using System.Reflection; +using JasperFx.Core.Reflection; using Wolverine.Attributes; +using Wolverine.Runtime; using Wolverine.Runtime.Handlers; using Wolverine.Util; using Xunit; @@ -65,6 +69,25 @@ await Should.ThrowAsync(async () => }).StartAsync(); }); } + + [Fact] + public async Task Concurrent_Registration_No_Race_Condition() + { + // just make sure the list consists of at least a couple of messages + var typesToRegister = typeof(DummyMessage).Assembly.GetTypes().Where(x => x.Name.EndsWith("Message")).ToArray(); + + using var host = await Host.CreateDefaultBuilder() + .UseWolverine() + .StartAsync(); + + var graph = host.Services.GetRequiredService(); + var runtime = host.Services.GetRequiredService(); + Parallel.ForEach(typesToRegister, t => runtime.RegisterMessageType(t)); + + var missingTypes = typesToRegister.Select(t => t.ToMessageTypeName()) + .Where(t => graph.TryFindMessageType(t, out var _) is false).ToArray(); + missingTypes.ShouldBeEmpty(); + } } public class DummyMessage { } diff --git a/src/Transports/AWS/Wolverine.AmazonSqs.Tests/Internal/AmazonSqsQueueTests.cs b/src/Transports/AWS/Wolverine.AmazonSqs.Tests/Internal/AmazonSqsQueueTests.cs index 84e2ecad6..0198daa9e 100644 --- a/src/Transports/AWS/Wolverine.AmazonSqs.Tests/Internal/AmazonSqsQueueTests.cs +++ b/src/Transports/AWS/Wolverine.AmazonSqs.Tests/Internal/AmazonSqsQueueTests.cs @@ -66,7 +66,8 @@ public void configure_request() { MaxNumberOfMessages = 8, VisibilityTimeout = 3, - WaitTimeSeconds = 11 + WaitTimeSeconds = 11, + MessageAttributeNames = ["All"], }; var request = new ReceiveMessageRequest(); @@ -76,6 +77,39 @@ public void configure_request() request.VisibilityTimeout.ShouldBe(endpoint.VisibilityTimeout); request.MaxNumberOfMessages.ShouldBe(endpoint.MaxNumberOfMessages); request.WaitTimeSeconds.ShouldBe(endpoint.WaitTimeSeconds); + request.MessageAttributeNames.ShouldBe(endpoint.MessageAttributeNames); + } + + [Fact] + public void default_message_attribute_names_is_null_on_endpoint() + { + new AmazonSqsQueue("foo", new AmazonSqsTransport()) + .MessageAttributeNames.ShouldBeNull(); + } + + [Fact] + public void configure_request_does_not_set_message_attribute_names_when_null() + { + var endpoint = new AmazonSqsQueue("foo", new AmazonSqsTransport()); + var request = new ReceiveMessageRequest(); + + endpoint.ConfigureRequest(request); + + request.MessageAttributeNames.ShouldBeNull(); + } + + [Fact] + public void configure_request_does_not_set_when_empty_list() + { + var endpoint = new AmazonSqsQueue("foo", new AmazonSqsTransport()) + { + MessageAttributeNames = [] + }; + var request = new ReceiveMessageRequest(); + + endpoint.ConfigureRequest(request); + + request.MessageAttributeNames.ShouldBeNull(); } } diff --git a/src/Transports/AWS/Wolverine.AmazonSqs/Internal/AmazonSqsQueue.cs b/src/Transports/AWS/Wolverine.AmazonSqs/Internal/AmazonSqsQueue.cs index 648d249f1..3a051b5d4 100644 --- a/src/Transports/AWS/Wolverine.AmazonSqs/Internal/AmazonSqsQueue.cs +++ b/src/Transports/AWS/Wolverine.AmazonSqs/Internal/AmazonSqsQueue.cs @@ -90,6 +90,13 @@ public int VisibilityTimeout /// public string? DeadLetterQueueName { get; set; } = AmazonSqsTransport.DeadLetterQueueName; + /// + /// Optional list of message attribute names to request in ReceiveMessage. + /// Use "All" to retrieve all message attributes. If null or empty, nothing is requested. + /// (Attention: this is different from .) + /// + public List? MessageAttributeNames { get; set; } + public async ValueTask CheckAsync() { var response = await _parent.Client!.GetQueueUrlAsync(QueueName); @@ -299,6 +306,11 @@ internal void ConfigureRequest(ReceiveMessageRequest request) request.WaitTimeSeconds = WaitTimeSeconds; request.MaxNumberOfMessages = MaxNumberOfMessages; request.VisibilityTimeout = VisibilityTimeout; + + if (MessageAttributeNames is { Count: > 0 }) + { + request.MessageAttributeNames = MessageAttributeNames; + } } public async Task TeardownAsync(IAmazonSQS client, CancellationToken token) diff --git a/src/Wolverine/HostBuilderExtensions.cs b/src/Wolverine/HostBuilderExtensions.cs index c6ddf8589..72ead04a6 100644 --- a/src/Wolverine/HostBuilderExtensions.cs +++ b/src/Wolverine/HostBuilderExtensions.cs @@ -20,6 +20,8 @@ using Wolverine.Persistence.Sagas; using Wolverine.Runtime; using Wolverine.Runtime.Handlers; +using IServiceContainer = Wolverine.Runtime.IServiceContainer; +using ServiceContainer = Wolverine.Runtime.ServiceContainer; namespace Wolverine; diff --git a/src/Wolverine/Runtime/Agents/AgentCommandHandler.cs b/src/Wolverine/Runtime/Agents/AgentCommandHandler.cs index 4fa42e8ed..ceeba537f 100644 --- a/src/Wolverine/Runtime/Agents/AgentCommandHandler.cs +++ b/src/Wolverine/Runtime/Agents/AgentCommandHandler.cs @@ -18,7 +18,10 @@ public AgentCommandHandler(WolverineRuntime runtime) .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds()) .Then.Discard(); - Chain.ExecutionLogLevel = LogLevel.Debug; + // Reducing log noise + Chain.ExecutionLogLevel = LogLevel.None; + Chain.ProcessingLogLevel = LogLevel.None; + Chain.SuccessLogLevel = LogLevel.None; Chain.TelemetryEnabled = false; } diff --git a/src/Wolverine/Runtime/Handlers/HandlerGraph.cs b/src/Wolverine/Runtime/Handlers/HandlerGraph.cs index 0b7c25cfe..bf3e3ec84 100644 --- a/src/Wolverine/Runtime/Handlers/HandlerGraph.cs +++ b/src/Wolverine/Runtime/Handlers/HandlerGraph.cs @@ -42,6 +42,8 @@ public partial class HandlerGraph : ICodeFileCollectionWithServices, IWithFailur private bool _hasGrouped; + private object _messageTypesLock = new(); + private ImHashMap _messageTypes = ImHashMap.Empty; private ImmutableList _replyTypes = ImmutableList.Empty; @@ -326,24 +328,28 @@ private void tryApplyLocalQueueConfiguration(WolverineOptions options) private void registerMessageTypes() { - _messageTypes = - _messageTypes.AddOrUpdate(typeof(Acknowledgement).ToMessageTypeName(), typeof(Acknowledgement)); - - foreach (var chain in Chains) + lock (_messageTypesLock) { - _messageTypes = _messageTypes.AddOrUpdate(chain.MessageType.ToMessageTypeName(), chain.MessageType); + _messageTypes = + _messageTypes.AddOrUpdate(typeof(Acknowledgement).ToMessageTypeName(), typeof(Acknowledgement)); - if (chain.MessageType.TryGetAttribute(out var att)) + foreach (var chain in Chains) { - _messageTypes = _messageTypes.AddOrUpdate(att.InteropType.ToMessageTypeName(), chain.MessageType); - } - else - { - foreach (var @interface in chain.MessageType.GetInterfaces()) + _messageTypes = _messageTypes.AddOrUpdate(chain.MessageType.ToMessageTypeName(), chain.MessageType); + + if (chain.MessageType.TryGetAttribute(out var att)) { - if (InteropAssemblies.Contains(@interface.Assembly)) + _messageTypes = _messageTypes.AddOrUpdate(att.InteropType.ToMessageTypeName(), chain.MessageType); + } + else + { + foreach (var @interface in chain.MessageType.GetInterfaces()) { - _messageTypes = _messageTypes.AddOrUpdate(@interface.ToMessageTypeName(), chain.MessageType); + if (InteropAssemblies.Contains(@interface.Assembly)) + { + _messageTypes = + _messageTypes.AddOrUpdate(@interface.ToMessageTypeName(), chain.MessageType); + } } } } @@ -457,8 +463,11 @@ public void RegisterMessageType(Type messageType) return; } - _messageTypes = _messageTypes.AddOrUpdate(messageType.ToMessageTypeName(), messageType); - _replyTypes = _replyTypes.Add(messageType); + lock (_messageTypesLock) + { + _messageTypes = _messageTypes.AddOrUpdate(messageType.ToMessageTypeName(), messageType); + _replyTypes = _replyTypes.Add(messageType); + } } public void RegisterMessageType(Type messageType, string messageAlias) @@ -473,8 +482,11 @@ public void RegisterMessageType(Type messageType, string messageAlias) return; } - _messageTypes = _messageTypes.AddOrUpdate(messageAlias, messageType); - _replyTypes = _replyTypes.Add(messageType); + lock (_messageTypesLock) + { + _messageTypes = _messageTypes.AddOrUpdate(messageAlias, messageType); + _replyTypes = _replyTypes.Add(messageType); + } } public IEnumerable AllChains() diff --git a/src/Wolverine/Wolverine.csproj b/src/Wolverine/Wolverine.csproj index ea05bb39c..062f29b2b 100644 --- a/src/Wolverine/Wolverine.csproj +++ b/src/Wolverine/Wolverine.csproj @@ -4,7 +4,7 @@ WolverineFx - +