-
Notifications
You must be signed in to change notification settings - Fork 375
Fix a race condition that leads to ClassAnalyzer with name CdiInjecteeSkippingClassAnalyzer exceptions #6049
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
Fix a race condition that leads to ClassAnalyzer with name CdiInjecteeSkippingClassAnalyzer exceptions #6049
Conversation
Happens when many threads execute REST client call at the same. Symptom: Occasional IllegalStateException: Could not find an implementation of ClassAnalyzer with name CdiInjecteeSkippingClassAnalyzer from WebTarget.request Reason for the exception: * The injectionManager is set earlier than it's used, it's stored to a shared volatile variable and later picked up * If some thread sets the shared volatile variable to another injectionManager, the original thread would use another threads injectionManager * In case the second thread doesn't bind the ClassAnalyzer class in time before the first thread uses it, the first thread won't have ClassAnalyzer bean available and throws exception
|
@OndroMih |
|
@jansupol , I wanted to set the threadlocal in each target instance initially. But I didn’t find a nice way to access all targets from The targets are not directly accessible from their top-level class, if I’m not wrong. They are referenced from a store, which only supports setting injection manager. I would have to adjust the store contract to somehow allow removing the thread locals from all targets. Do you have an idea how to do it? Is it worth it? |
|
@jansupol , maybe for a better performance, targets could set the thread local only if it's not set already. I read that ThreadLocal.get is faster than ThreadLocal.set, even if setting the same value that it already contains. Within a single invocation, the injection manager would always be the same so with the current change, the first target sets the ThreadLocal value and all others just set it again to the same value. If they call get instead, it could be faster. |
|
By trying that, I found out that the It looks like I struggle to find out a good way to remove the thread local, I don't know how to get the correct instance of |
|
I found the correct component that holds the thread local. It's the same extension which is used in the |
3ad72df to
d768a3c
Compare
Remove the thread local from the correct component (same as retrieved in the CdiComponentProvider.initialize method) Save a few nanoseconds by calling get instead of set on the thread local, log an error if a leaking injectionManager detected.
d768a3c to
dceaeaf
Compare
|
A tested detected a leak - in some cases, ClientRuntime initializes CdiComponentProvider again, after My solution is a bit hacky - move postInit for
The Another solution that also works would be to call Yet another solution would be to add a special method to The code in Or if I also add a default NoOp method to All of these solutions would work equally. The questions is which solution is better? What do you think, @jansupol , @senivam ? Is it better to treat the Or is it better to call |
111af26 to
faeee7a
Compare
If ClientRuntime needs CDI beans, it initializes CdiComponentProvider again, we need to clean up later.
faeee7a to
6189f01
Compare
This solves the race condition - keep the effective injectionManager in a thread local. As multiple threads can modify the injectionManager and the
InjectionManagerInjectedCdiTargetinstances are global, effectiveInjectionManager must be scoped to the thread. It's actually scoped to a single WebTarget initialization and removed at the end of the initialization in thedone()method ofCdiComponentProviderThe problem:
A race condition happens when many threads execute REST client call at the same (more specifically, when
WebTarget.request()is called.Symptom:
Occasional
IllegalStateException: Could not find an implementation of ClassAnalyzer with name CdiInjecteeSkippingClassAnalyzerfromWebTarget.requestReason for the exception:
injectionManageris set in the initialization phase, before it's used. It's stored to a shared volatile variable and later picked upinjectionManager, before the original thread uses it, the original thread would use another thread'sinjectionManagerClassAnalyzerclass in time before the first thread uses it, the first thread won't haveClassAnalyzerbean available and throws exceptionA reproducer is described in payara/Payara#7753.
The original fix in Payara fork (53ae843) addresses this by a workaround - a retry in case of exception, which works, because by the next time, the other thread has enough time to bind the
ClassAnalyzerclass. But that solution isn't efficient.