Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception in RequestHandler constructor/dependency breaks all handlers #157

Open
andrewhobbins opened this issue Sep 25, 2024 · 3 comments

Comments

@andrewhobbins
Copy link

If an exception occurs in the constructor of an IRequestHandler or in one of the dependencies required for the constructor, this exception occurs at app startup before any Mediator requests have been sent. This then stops any other requests from being sent.

Issue can be recreated by throwing an exception in the constructor of an IRequestHandler. The request relating to this handler does not need to be sent. At no point have I called mediator.Send(new TestRequest());

Any subsequent unrelated requests will not send successfully.

public class TestRequest : IRequest;

public class TestHandler : IRequestHandler<TestRequest>
{
    public TestHandler()
    {
        throw new Exception("Oh no");
    }
    public ValueTask<Unit> Handle(TestRequest request, CancellationToken cancellationToken)
    {
        return default;
    }
}
[09:47:53 INF] Application started. Press Ctrl+C to shut down.
[09:47:53 INF] Hosting environment: Development
[09:47:53 INF] Content root path: D:\src\myapplication\myapplication.Web
[09:47:53 INF] Registered with MainDom, localContentDbExists? True, localMediaDbExists? True
[09:47:53 INF] Creating the content store, localContentDbExists? True
[09:47:53 INF] Creating the media store, localMediaDbExists? True
[09:47:54 INF] Loading content from local cache file [Timing 241e0b2]
[09:47:54 INF] Completed. (42ms) [Timing 241e0b2]
[09:47:54 INF] Loading media from local cache file [Timing 5d44d0d]
[09:47:54 INF] Tried to load media from the local cache file but it was empty.
[09:47:54 INF] Completed. (0ms) [Timing 5d44d0d]
[09:47:54 WRN] Loading media from local db raised warnings, will reload from database.
[09:47:54 INF] Loading media from database [Timing 04e5ca1]
[09:47:54 INF] Completed. (141ms) [Timing 04e5ca1]
[09:48:00 ERR] An unhandled exception has occurred while executing the request.
System.Exception: Oh no
   at myapplication.Application.Features.TestHandler..ctor() in D:\src\myapplication\myapplication.Application\Features\TestHandler.cs:line 11
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.MediatorDependencyInjectionExtensions.<>c.<AddMediator>b__2_3(IServiceProvider sp) in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 65
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Mediator.Mediator.DICache..ctor(IServiceProvider sp) in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 613
   at Mediator.Mediator.<.ctor>b__6_0() in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 525
   at Mediator.Mediator.FastLazyValue`1.get_ValueSlow() in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 578
   at Mediator.Mediator.Send(GetStoreLocationsRequest message, CancellationToken cancellationToken) in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 686
   at Mediator.Mediator.Send[TResponse](IRequest`1 request, CancellationToken cancellationToken) in D:\src\myapplication\myapplication.Web\Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs:line 792
   at myapplication.Web.Controllers.StoreLocatorApiController.Get() in D:\src\myapplication\myapplication.Web\Controllers\StoreLocatorApiController.cs:line 17
   at lambda_method80(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Umbraco.Cms.Web.Common.Middleware.BasicAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.BackOffice.Middleware.BackOfficeExternalLoginProviderErrorMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at StackExchange.Profiling.MiniProfilerMiddleware.Invoke(HttpContext context) in C:\projects\dotnet\src\MiniProfiler.AspNetCore\MiniProfilerMiddleware.cs:line 112
   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.Common.Middleware.PreviewAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestLoggingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, Boolean retry)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Is this expected behaviour? Is there a way to mitigate this as I feel that an issue in one dependency / request handler shouldn't affect the behaviour of the rest of the requests.

@martinothamar
Copy link
Owner

I think this only happens for the Singleton lifetime? It is eagerly instantiating handlers for performance reasons. To be honest, I kind of like that it fails early like that, usually it comes down to some misconfiguration probably?

@andrewhobbins
Copy link
Author

andrewhobbins commented Sep 26, 2024

Yes, I've just checked and the issue doesn't happen when registered as Transient. I agree with failing early, however in this case the app still boots up, my razor views render, and the issue is pushed onto the next Mediator request that is sent, making the original issue much harder to diagnose.

For example a misconfiguration in my SendContactUsEmailHandler that causes it to not instantiate during the Mediator startup pipeline is presenting as my GetStoreLocatorLocationsRequest not functioning, simply because that was the first request I tried to send, despite that having no relation to the issue.

Would it not make more sense to either:
A) Fail the handler in isolation and throw an error when it is called, leaving the rest of the app functionality intact.
B) Stop the app booting so we are aware of some critical configuration issue at startup. Similar to what happens when a dependency isn't registered correctly.

@martinothamar
Copy link
Owner

Hmm yeah I would be tempted to go for B, but Mediator doesn't know about the hosting of the code. If it did, we could add an IHostedService that fails on startup when trying to instantiate IMediator (and I don't remember if crashing hosted services stops the process by default or if that is opt-in config)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants