-
-
Notifications
You must be signed in to change notification settings - Fork 120
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
Why is there a static/global lock on a lightinject Scope #411
Comments
Thanks for reporting this. |
Hey, thanks for the reply. The main tool I've used is to just take multiple memory dumps while this behavior is happening. Taken these dumps across different machines over many days now and in all of them every single thread is waiting behind this lock. The issue I'm having is only in production and all requests are waiting behind a lock so CPU goes up to 100% and all additional requests get queued by IIS. I haven't been able to replicate the same scenario on my local pc yet. To be honest I am quite surprised as the operation in the lock should be really quick. Though when I looked at this code and saw that the lock was static it seemed like an easy win. My assumption is that due to the high number of requests + number of objects I generate each request it's causing a backup while everything waits behind this lock |
I'm trying to fire up a lot of threads and see how it looks in the Concurrency Visualizer in VS. |
Yup I will do that and let you know. Thanks |
Thanks a lot. Appreciate it 👍 |
Here are the results of trying to investigate this. Created the following console app.
Then I ran the app using the Concurrency Vizualizer https://marketplace.visualstudio.com/items?itemName=Diagnostics.ConcurrencyVisualizer2017 With static lock With instance lock I'm not an expert when it comes to interpreting the results of this tool, but the number of milliseconds spent on blocking has decreased dramatically in the second run so I guess that is a good thing 😄 |
@Kadajski Any updates on this? Too soon to tell? |
@seesharper unfortunately it just moved the contention to the lock in the I'm not overly familiar with the LightInject codebase but I suspect our use case, which is a very large dependency graph (sometimes thousands of objects) per request and hundreds of concurrent requests, is going to be the root of our problems. We have some ideas on how we're going to fix (or minimize) the contention we're seeing so we will open another issue (or submit a PR) if there is something that can be improved in LightInject that would help our scenario. |
Yes, I am aware of the lock related to Maybe implemented as an extension method
|
Just to clarify my understanding here. From what I understand of the code the |
You are right on the money with your understanding 😄 |
Stealing the idea ;P |
We need to be a little careful here though. It should be narrowed down to the "root" services that is actually resolved through a delegate. If we just blindly create delegates for all services that is registered with the container, we will most probably create a LOT of delegates that are never executed. Bad for memory and possibly bad for service lookup later on. For instance it should be fair to assume that a service injected into another service is not a root service. On the other hand To illustrate this
Register both services with the container
Resolve
In this case we only create a delegate for So here, The first draft of something providing compilation of delegates.
This allows the developer to filter which services should be "precompiled". @Kadajski , @wayneduke Is this sufficient or would you expect LightInject to provide the list of root services or at least what it "thinks" are root services? |
Hey @seesharper this seems amazing. I definitely think this is sufficient for our use-case. I actually think I prefer this to an approach of lightinject trying to figure out the list of roots. The application developer will know more than lightinject about the usages of the services and to try and cater for every scenario inside of the lightinject library is probably going to be really tricky to perfect. The predicate seems most flexible to all scenarios I can think of. Surely you can only really know from lightinject the "roots" that exist from all the registered types at the time of "compilation", having an assumption that none of the child types will ever be created independently seems wrong to me. At least for most projects I have been involved on child types are often created independently of the root, so I'd still like to compile these independently |
What if the service is open-generic, and I want to precompile a specific closed variants (which is the only possibility)? |
|
@seesharper being able to compile these delegates is going to be a big win for us! I echo the sentiments of @Kadajski - leaving it up to the developer to provide a predicate gives the most flexibility. |
@wayneduke @Kadajski Wrapping up these changes now. |
@wayneduke @Kadajski LightInject 5.1.3 has just been published to the official NuGet feed introducing support for compiling delegates up front. This version also fixes the static lock in the |
Thanks @seesharper! We were following and have already started preparing to release our app with the new version. We'll update and let you know if using the |
It should be quite interesting to see the effect of this change.Let me know if anything is unclear with regards to the API 👍 |
@Kadajski @wayneduke Did you guys experience any improvement after upgrading to 5.1.3? |
hey @seesharper we haven't been able to use the compile feature yet as we do actually need some changes in our application to get this working. Removing the static lock though does seem to remove that lock from showing up in any of our analysis though. The compile feature has helped a lot in exposing some of the issues in our application though. To give some insight into our problems if you're interested or can give any advice: Another thing we found is that we're setting lightinject as the default dependency resolver in ASP.net mvc. This causes MVC to do a We also have a lot of our registrations chain factory resolutions. So what you mentioned before about only needing to compile the "root" types sadly isn't as simple in our case. For example we'd have something like:
So I'd assume we'll need to compile both |
I've been experiencing some issues recently related to Lightinject scope and the
static readonly object disposableObjectsLock = new object()
lock. I have a MVC application using the normal per-request scope in Lightinjec.Web but on my application startup I notice a large amount of requests waiting behind the lock fordisposableObjectsLock
.Why is this lock static? Why don't we have a lock inside of each scope. Since the lock is only used to avoid concurrency issues with the
HashSet<IDisposable>
which is a local scoped object on the lightject Scope. We could change the lock to be a local variable on the Scope object too and this would avoid a lot of lock contention. Alternatively, if this was done to avoid allocations for additionaldisposableObjectsLock
objects, we could just lock on the HashSet itself?The text was updated successfully, but these errors were encountered: