Closed ogaudefroy closed 5 years ago
How does this happen? That method is only supposed to be called once on app startup. How can multiple threads be initializing?
Our DI container is configured like below:
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);
services.AddSingleton<ICommandBus, CommandBus>();
services.AddSingleton<IDomainEventBus, DomainEventBus>();
services.AddSingleton<IQueryBus, QueryBus>();
services.AddScoped(typeof(IPipelineBehavior<,>, typeof(AxCommandExceptionHandler<,>) )
The concurrent replacement occurs on a registered pipeline behavior which has a per request scope:
Here is a trace with concurrent failure
[13:45:58 INF] MediatR resolving service: System.Collections.Generic.IEnumerable`1[[MediatR.IPipelineBehavior`2[[Procurement.PurchaseOrders.Queries.V1.FindPurchaseOrdersQuery, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Procurement.PurchaseOrders.Queries.PagedResult`1[[Procurement.PurchaseOrders.Queries.V1.PurchaseOrderSearchResultItem, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], MediatR, Version=5.1.0.0, Culture=neutral, PublicKeyToken=null]]
[13:45:58 INF] MediatR resolving service: System.Collections.Generic.IEnumerable`1[[MediatR.IPipelineBehavior`2[[Procurement.PurchaseOrders.Queries.V1.FindPurchaseOrdersQuery, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Procurement.PurchaseOrders.Queries.PagedResult`1[[Procurement.PurchaseOrders.Queries.V1.PurchaseOrderSearchResultItem, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], MediatR, Version=5.1.0.0, Culture=neutral, PublicKeyToken=null]]
[13:45:58 INF] MediatR resolving service: MediatR.IRequestHandler`2[[Procurement.PurchaseOrders.Queries.V1.FindPurchaseOrdersQuery, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Procurement.PurchaseOrders.Queries.PagedResult`1[[Procurement.PurchaseOrders.Queries.V1.PurchaseOrderSearchResultItem, Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Procurement.PurchaseOrders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
[13:45:58 ERR] Unexpected exception raised. Collection was modified; enumeration operation may not execute.
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at MediatR.ServiceCollectionExtensions.<>c__DisplayClass15_1.<AddRequiredServices>b__1(Type type) in C:\projets\procurement\purchase-orders\src\PurchaseOrders\Infrastructure\DependencyInjection\InternalMediatr.cs:line 304
at MediatR.ServiceFactoryExtensions.GetInstances[T](ServiceFactory factory)
at MediatR.Internal.RequestHandlerWrapperImpl`2.Handle(IRequest`1 request, CancellationToken cancellationToken, ServiceFactory serviceFactory)
at MediatR.Mediator.Send[TResponse](IRequest`1 request, CancellationToken cancellationToken)
at Procurement.PurchaseOrders.SharedKernel.QueryBus.Send[TQuery,TResponse](TQuery query) in C:\projets\procurement\purchase-orders\src\PurchaseOrders\SharedKernel\Queries\QueryBus.cs:line 24
at Procurement.PurchaseOrders.Controllers.V1.PurchaseOrdersController.FindBySearchCriteria(FindPurchaseOrdersQuery query) in C:\projets\procurement\purchase-orders\src\PurchaseOrders\Controllers\V1\PurchaseOrdersController.cs:line 40
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at System.Threading.Tasks.ValueTask`1.get_Result()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()
[13:45:58 ERR] HTTP GET /api/v1/purchaseOrders/search responded 500 in 75.7205 ms
[13:45:59 INF] HTTP GET /api/v1/purchaseOrders/search responded 200 in 204.5497 ms
Ahhhhh yes the constrained generic stuff. I think we can just call ToList or ToArray on the services before enumerating instead of locking. We're not modifying the list inside of the enumerable.
You're right your solution avoids locking and is by far better. Thx for Mediatr and taking our problem in consideration.
No problem! Took me a while to understand the problem, sorry about that!
Hey there,
This PR attempts to solve a concurrency issue we encountered using this library.
Here is the stack trace we occasionally have:
Unexpected exception raised. Collection was modified; enumeration operation may not execute. System.InvalidOperationException: Collection was modified; enumeration operation may not execute. at System.Collections.Generic.List1.Enumerator.MoveNextRare() at MediatR.ServiceCollectionExtensions.<>c__DisplayClass15_1.<AddRequiredServices>b__1(Type type) at MediatR.ServiceFactoryExtensions.GetInstances[T](ServiceFactory factory) at MediatR.Internal.RequestHandlerWrapperImpl2.Handle(IRequest1 request, CancellationToken cancellationToken, ServiceFactory serviceFactory) at MediatR.Mediator.Send[TResponse](IRequest1 request, CancellationToken cancellationToken) at Procurement.PurchaseOrders.Common.Domain.QueryBus.Send[TQuery,TResponse](TQuery query) in C:\projets\procurement\purchase-orders\src\PurchaseOrders.Common\Domain\Queries\QueryBus.cs:line 24 etc...
The registered
ServiceFactory
implementation iterates over services and hot replaces service description ; I added a locking mechanism to ensure that services aren't modified while doing that overall process.Olivier