Guice Singletons Parallelization
Guice is a Java library for dependency injection. It’s a very widespread one and a lot of servers written in Java use it. Surprisingly until recently all singleton objects created by Guice were created sequentially.
It had two downsides:
- When several tests are running within the same JVM all their respective singletons are created sequentially. One slow (DB-related as an example) singleton could bring a whole test suite to a halt.
- Guice itself can create deadlocks in such a way that it could not be prevented from the library’s client’s code.
Reimplementing Guice singleton scope with a support for a proper multi-threading, turned out to be surprisingly hard.
Getting a deadlock with Guice’s singletons: Thread
TA
creates singleton objectA
, using injectorIA
. ThreadT2
takes lockL
and tries to create a singleton objectB
using injectorIB
. ThreadTA
tries to take lockL
. Now both threads wait for each other, we have a deadlock. This is a very puzzling behaviour as threads use their individual injectors and even create different objects.
Naïve way to fix it with lock per injector
Giving each injector its own lock for singletons provides a simple way to parallelize common scenario when different injectors are used for different tests running in parallel.
Making Singleton’s creation lock less coarse.
As it turns out Guice supports parent-child relations for injectors, one lock per such a tree is used.
Proper way to fix it with a lock per singleton
Giving each singleton its own lock allows full parallelization.
Reimplementing Guice’s singleton scope with a support for a proper multi-threading, turned out to be surprisingly hard.
As it turns out Guice supports dependency cycles and resolves it automatically using dynamic proxy objects when a flag is on for a specific injector. This increases complexity of a solution dramatically as dependency cycle can span several threads and it will guarantee a deadlock.
Solving this problem turned out to be very nontrivial. Guice guarantees that no proxy object would be created when no dependency cycle for the object under creation is detected. Therefore we have to detect lock cycles ourselves. It’s tricky as there are no common Java library that does it: even Google Commons (Guava) detect only static locks.
Implement more granular locks for a Singleton scope in Guice
As a side effect CycleDetectingLock was written. It allows dynamic lock cycle detection with O(N)
time where N
is number of threads that are part of dependency tree.
It worked well and negatively affected less than 0.01% projects in terms of performance or functionality. It was surprising considering ~1K lines of heavily multi-threaded code in this change.