EF and Multiple Instances of IHostedService - c#

I have multiple IHostedService (BackgroundService) instances registered in my DI container.
services.AddHostedService<ResourceMatchingMessageListeningService>();
services.AddHostedService<ResourceMatchingMessageListeningService2>();
services.AddHostedService<ResourceMatchingMessageListeningService3>();
services.AddHostedService<ResourceMatchingMessageListeningService4>();
services.AddHostedService<ResourceMatchingMessageListeningService5>();
Each service listens to a different Azure Service Bus queue. When receiving a message it writes to an database through EF Core. If I run a single service it works fine, as soon as I run 2 or more I get the error:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext.
I'm assuming this error comes up because EF Core is not thread safe and the hosted services all run in different threads.
I tired to fix this by setting EF's service lifetime to be transient), but I still get the same error.
services.AddDbContext<MyDataContext>(options =>
options.UseSqlServer(connectionString, sqlServerOptions =>
sqlServerOptions.CommandTimeout(120)),
ServiceLifetime.Transient, ServiceLifetime.Transient);
Any suggestion on a potential solution here?

You'll need to Create a new service scope for each hosted service, then request your DbContext from that scope.
using var scope = services.CreateScope();
var context = scope.ServiceProvider.GetService<MyDataContext>();
You might want to create a service scope for each message you process. Perhaps even a separate scoped service to handle the processing of each message.
Unfortunately the implementation then starts to look like a service locator anti-pattern.

Related

Why do I have a .NET Core API performance issue using IHttpClientFactory when ramping up users?

I have a .NET Core WebAPI my React UI is calling to Authenticate Users. It calls a 3rd Party API for this. Everything works fine but we have started to do Performance testing on it and it is not scaling well when we ramp up the Users attempting to log on concurrently. (taking 30 secs)
The 3rd Party API we call are saying they are responding in milliseconds.
My API is hosted in Kubertnetes container on AWS. I have added AWS X-ray to the code to try and get further information though I am not really sure on how to interpret the results.
The code is quite straightforward - This is a snippet from MyAuthenticationProvider class (the constructor takes a metric collector (for AWS X-Ray and and securityProvider http client for making the call)
metricCollector.StartCollection("Stage 1");
HttpResponseMessage response = await securityProvider.SendAsync(requestMessage);
metricCollector.EndCollection();
The X-Ray image for the above code is:
Is X-Ray showing that it is indeed waiting 30+ Seconds for this API to return a response and I should reach out to that company for further investigation on there side even though they are telling me all traffic is getting responded too in milli-seconds.
Or could it be how I have defined the http client used in MyAuthProvider class in Startup.cs that is not scaling correctly when the concurrent users ramps up?
This is the code for that in Startup.cs
services.AddTransient<IMyAuthenticationProvider>(ctx =>
{
IHttpClientFactory clientFactory = ctx.GetRequiredService<IHttpClientFactory>();
return new MyAuthenticationProvider(
clientFactory.CreateClient("3RDPARTYAUTHCLIENT"),
ctx.GetService<IMetricCollector>());
});
Another thing I was thing to improve performance is introducing Redis to cache some of these responses as they are getting calling multiple times for different operations but the result will be the same
While you're only creating 1 named HttpClient, you've set the service lifetime of IMyAuthenticationProvider to transient.
This means that essentially you're losing out on most of the benefits of a single HttpClient by creating a new instance of IMyAuthenticationProvider every time something requests for one (which in the best-case scenario, will be synonymous with every client request but not to be mistaken with scoped services).
This can massively slow down your application & may be the cause of the badly performing scaling of the application.
You're trying to clearly use a single HttpClient, which would typically be static or wrapped as a non-static instance inside a singleton class. and is still a good solution for short-lived console applications etc. however in this case I'd allow IHttpClientFactory to resolve the client.
The primary goal of IHttpClientFactory in ASP.NET Core is to ensure that HttpClient instances are created appropriately (taking into account things like DNS changes which a single HttpClient instance won't take care of) while at the same time eliminating socket exhaustion.
Injected HttpClient instances by IHttpClientFactory have a transient lifetime (documentation is conflicting & mentions transient 2x & scoped 1x for some absurd reason) and so I'd set the lifetime of IMyAuthenticationProvider to scoped to allow it to be reused as much as possible.
Having a longer running singleton IHttpClientFactory, in this case, with an injected shorter-lived scoped HttpClient should not be done.
MSFT:
Do not resolve a scoped service from a singleton and be careful not to do so indirectly
While the injected HttpClient object is transient, using the IHttpClientFactory enables pooling of HttpMessageHandler objects that can and will be reused by multiple HttpClient instances.
Try:
services.AddHttpClient<IMyAuthenticationProvider, MyAuthenticationProvider>();
services.AddHttpClient<IMetricCollector, MetricCollector>();
...
services.AddScoped<IMyAuthenticationProvider, MyAuthenticationProvider>();
public class MyAuthenticationProvider : IMyAuthenticationProvider
{
private readonly HttpClient _httpClient;
public MyAuthenticationProvider(HttpClient httpClient)
{
_httpClient = httpClient;
}
...
}

Meaning of a Scoped Service in the context of a (non web) host

Before reporting this question as duplicate, this SO Question covers the use of Transient, Scoped, and Singleton, but the answers and discussion target meanings mainly within the context of configuring a WebHost, but my question is relevant to the meaning of these types of services within the context of a (non-web) host, specifically confusion on Scoped Services.
Let's start with the code below:
In Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
I can wrap my head around what Transient and Singleton mean in the non-web host context.
Transient lifetime services are created each time they are requested. (not to be confused with "http request", but to mean literally "each time the service is requested.")
Singleton lifetime services is for the lifetime of the application domain.
But everywhere I read, a Scoped Service in the context of http requests would mean that a new instance is created for each (http?) request. The docs and other discussion is usually geared toward behavior when dealing with a web host, so I am in need of clarification...
What then, is the meaning of Scoped within the context of configuring IServiceCollection on a Default (non-web) HostBuilder?
Host.CreateDefaultBuilder(args)..ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
});
Also, see here for a practical example.
The closest understanding I came to is "Scoped lifetime services are created once per request (again, not http-request, but request for the service) within the scope. It is equivalent to a singleton in the current scope." ... meaning that if you AddScoped when configuring services on the DefaultBuilder (non-web host), when requested from that same scope, a new, completely separate instance of the service will be generated? Is this ballpark correct?
If I am way off here, I apologize. Thank you for your time.
Yes, in a web host, a lifetime scope is created for the request.
In a console app, windows service, or any type of app, including a web hosted app, you can create a lifetime scope whenever you feel it is needed.
For example you might create a windows service that processes something on a schedule. It might make sense to create a lifetime scope each time the scheduled job is run. Now all components resolved in that scope that are registered as scoped will logically be scoped to it. This will keep your instances separate per run and they will be disposed along with the scope.
This could be useful if you have several different types of task that run independently and asynchronously. This way any component dependencies they have will be scoped properly.

How to do DI for non-request bound code in ASP.NET Core?

We're all being taught to use Dependency-Injection for coding in ASP.NET Core applications, but all of the examples I've seen so far that related to the retrieval of services via DI relate to situations where the method that has the service reference injected is strictly bound to a specific HTTP request (HttpContext) (e.g. MVC controllers, Routing delegates).
Service location is warned against as an anti-pattern, but I'm not sure on how to obtain a proper service (e.g. DbContext) reference via DI in code that is not bound a specific HTTP request, e.g. code that has to respond to messages arriving over a websocket.
Although the websocket itself is set-up initially with a specific HTTP request, messages will get responses over potentially a long lifetime of the websocket (as long as the user web session lasts). The server should not reserve/waste a DbContext/DB connection over this entire lifetime (this would result in exhaustion quickly), but rather obtain a DB connection temporarily when a message arrives and requires a response; discarding the DbContext/connection immediately afterwards - while the original HTTP request that set-up the websocket in the very beginning of the user-session technically is still there.
I haven't been able to find anything else but using:
httpContext.RequestServices.GetService(typeof(<MyNeededDbContext>)
This way I use the initial httpContext (obtained via DI when the websocket was set up), but at multiple times after that whenever a websocket message needs a response I can request a transient service object (a DbContext in this example), that may be recycled or pooled after the message response is complete, but while the original httpContext is very much still alive.
Anyone aware of a better approach?
You can create a new service scope to manage the lifetime of services yourself;
IServiceProvider provider = ...;
using (var scope = provider.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyNeededDbContext>();
...
}

How to use DbContextPool with Singleton?

In my application I'm doing integrations using NMS and ActiveMQ.
I have some listeners that are singletons listening to some queues for messages.
Upon receiving a message, the listener should process it and log it on the database.
My DbContext is configured using the DbContextPool option:
services.AddEntityFrameworkSqlServer();
services.AddDbContextPool<MyContext>((serviceProvider, options) =>
{
options.UseSqlServer(connectionString);
options.UseInternalServiceProvider(serviceProvider);
});
So, when I try to inject the DbContext into my ActiveMqListener class, I get the error:
InvalidOperationException: Cannot consume scoped service 'MyApp.Data.MyContext' from singleton 'MyApp.Integrations.ActiveMqListener'.
How can I get one of the Contexts in the pool and free it once my work is done processing one message? Is there any other recommend way of doing this?
Thanks in advance.
According to the ASP.NET Core DI Service lifetimes documentation:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
By default AddDbContext or AddDbContextPool register the DbContext as Scoped service. You are consuming your DbContext in ActiveMqListener class which has been registered as Singleton service. That's the problem!
Solution is: register your ActiveMqListener to ASP.NET Core DI as ScopedService in Startup.ConfigureServices method.
Note: If you are obliged to use ActiveMqListener as Singleton then register your DbConext as Singleton too as follows:
services.AddDbContext<MyContext>((serviceProvider, options) =>
{
options.UseSqlServer(connectionString);
options.UseInternalServiceProvider(serviceProvider);
}, ServiceLifetime.Singleton); // <-- Here it is

Should RedisMqServer/RedisMqHost be configured once per application?

I have a web app and a background service that processes messages from Redis. However, I'm unsure as to whether or not the web application's RedisMqServer should be configured as a singleton (I'm using Ninject as my IoC container). Each request that comes is will need to send messages to the background service (one-way), but I'm not sure it it should be instantiated per-request or per-application.
I was thinking that the container would be configured like this:
var clientManager = new PooledRedisClientManager();
var mqHost = new RedisMqHost(clientManager);
Bind<IMessageProducer>()
.ToMethod(_ => mqHost.MessageFactory.CreateMessageProducer())
.InRequestScope();
Or maybe the RedisMqHost/RedisMqServer isn't necessary when the messages are one-way? Therefore, reducing the configuration to:
var clientManager = new PooledRedisClientManager();
Bind<IMessageProducer>()
.ToMethod(_ => new RedisMessageProducer(clientManager))
.InRequestScope();
You don't actually need to register the IMessageService if your services don't need access to the host directly. But if you do end up using it, then Yes it should be registered as a singleton.
The only thing that needs to be registered is IMessageFactory. In this case RequestScope is the same as Ninject's default TransientScope since if it's being used, it'll only ever be resolved once per request, in your Service class.
The IMessageFactory is used in the base Service to lazily load a IMessageProducer so you can publish a message in your services with:
base.MessageProducer.Publish(new RequestDto());
Note: You're using RedisMqHost in code which process all messages on a single background thread. Changing to use RedisMqServer will create a background thread for each message type, allowing processing of different messages to happen in parallel.

Categories