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