Running the published stack, this appears in the backend log whenever a query completes:
fail: Schuly.Application.Behaviors.PluginEventBehavior[0]
Plugin event dispatch failed for GetSchoolSystemsQuery
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
at Schuly.Application.Behaviors.PluginEventBehavior`2...<Handle>b__0
The behaviour injects IServiceProvider and then uses it from a fire-and-forget task:
public class PluginEventBehavior<TRequest, TResponse>(IServiceProvider serviceProvider, ...)
...
_ = Task.Run(async () =>
{
using var scope = serviceProvider.CreateScope();
The injected provider is the request scope. Task.Run returns immediately, the request finishes, ASP.NET disposes that scope, and the continuation then calls CreateScope() on a disposed provider. Whether it wins the race is pure timing, so plugin event handlers fire sometimes and not others, and the failure is only visible as a log line - the caller still gets its 200.
Injecting IServiceScopeFactory instead of IServiceProvider fixes it: the factory is a singleton and outlives the request, so the detached task can create its own scope.
The wider question is whether these events should be fire-and-forget at all. Nothing awaits or retries them, so an event lost to a restart or an exception is simply gone - which may matter more once plugin work moves onto a real scheduler (#233).
Reproduced against ghcr.io/schulydev/schuly:1.3.3 by calling GET /api/app/school-systems.
Running the published stack, this appears in the backend log whenever a query completes:
The behaviour injects
IServiceProviderand then uses it from a fire-and-forget task:The injected provider is the request scope.
Task.Runreturns immediately, the request finishes, ASP.NET disposes that scope, and the continuation then callsCreateScope()on a disposed provider. Whether it wins the race is pure timing, so plugin event handlers fire sometimes and not others, and the failure is only visible as a log line - the caller still gets its 200.Injecting
IServiceScopeFactoryinstead ofIServiceProviderfixes it: the factory is a singleton and outlives the request, so the detached task can create its own scope.The wider question is whether these events should be fire-and-forget at all. Nothing awaits or retries them, so an event lost to a restart or an exception is simply gone - which may matter more once plugin work moves onto a real scheduler (#233).
Reproduced against
ghcr.io/schulydev/schuly:1.3.3by callingGET /api/app/school-systems.