Hey everyone, I need a bit of help with Kodein in...
# kodein
o
Hey everyone, I need a bit of help with Kodein in the shared code of our KMP app, which is causing crashes for some of our iOS users. While our iOS devs can't reproduce the issue, Firebase logs show it happens almost daily. Our Android users are not experiencing this problem, even though both platforms use the same shared code. The problem concerns Kotlin Native's
HashMap
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!
s
Hi @Olaf Hoffmann. Thanks for a very detailed report. I think the reason why
maySynchronized
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.
o
Hi @salomonbrys. Thank you so much for the entertaining response and especially for the insights provided! I don't think you need to rush a fix just because of us, and we'd be perfectly fine if it were made available as part of a "regular" patch release sometime soon. Fortunately, only a small number of users are affected per day, and from what our logs suggest, those users can resolve it by trying to start the app again. It was more the frequency of the issue that prompted us to investigate, not necessarily the volume of occurrences. Thanks again for your time and feedback!
By the way @salomonbrys, sorry for pinging, but should I open an issue in the Kodein repository to keep track of this?
s
Yep, that would surely help 😉
👍 1