Olaf Hoffmann
03/07/2024, 10:02 AMHashMap
throwing a NullPointerException
when Kodein's StandardScopeRegistry
attempts to retrieve a dependency reference from its _cache
inside a synchronizedIfNull
predicate.
For native platforms, the newConcurrentMap
function used to create the _cache
returns a Kotlin Native HashMap. The implementation of this Map shows the code making a non-null assertion on the Map's internal value array. When creating the Map, this array is initialized to `null` and is only allocated when writing a value. Hence, the Exception in this place seems to happen because the Map was never written before reading from it.
What's still puzzling to me is that the only place where _cache
is being written is when the predicate in the registry returns null
. Given that a fresh HashMap should always throw, I'm not sure how the predicate is not always leading to the NPE.
Since this issue occurs on iOS only, I looked into Kodein's platform implementations of maySynchronized
, which is being called internally by synchronizedIfNull
. In the native implementation of that function, the block
passed in is being called immediately without checking the lock
parameter. I assume that's intentional due to missing synchronization facilities in native, but to be fair, it looks suspicious when considering the possibility of a race condition.
While I'm reasonably confident about my findings, this last assumption is merely a guess. Also, it doesn't explain how the `_cache`'s internal array manages to get initialized in the first place when the code suggests that a get
is always the first operation performed on that Map.
I appreciate any help figuring out what's happening here, how we may fix it on our end, or whether this is a problem with the Kodein implementation. For completeness, I've attached the Crashlytics stack trace exported from Firebase so you can investigate it if desired.
Thanks a lot in advance for all your assistance!salomonbrys
03/08/2024, 1:37 PMmaySynchronized
is implemented as such is that it was implemented 4 years ago, when Kotlin/Native had its infuriating memory model. At that time Kodein was therefore not multi-thread compatible on Kotlin/Native (which was a documented limitation). As any multi-thread usage of Kodein would make the app crash (maybe you remember InvalidMutabilityException
which would make you want to throw your computer from a very tall position), there was no need to implement any type of sychronization.
Since Kotlin/Native transitioned to a memory model more lenient on the developer's sanity, Kodein became multi-thread compatible on Kotlin/Native without any intervention on our part... which led to this stupid yet historical innacuracy : a synchronize
method that does not synchronize!
Fixing this should not be very difficult, and we can rush a new version of the Kodein library for next week, if that works for you.Olaf Hoffmann
03/08/2024, 3:51 PMOlaf Hoffmann
03/11/2024, 8:48 AMsalomonbrys
03/11/2024, 8:48 AMedenman
11/26/2024, 6:13 PMedenman
11/26/2024, 6:15 PMsalomonbrys
11/29/2024, 10:51 AMromainbsl
11/29/2024, 11:37 AMromainbsl
11/29/2024, 11:40 AMromainbsl
01/07/2025, 9:00 PM