You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Android: data race on ArgConverter's process-wide long-conversion cache when workers start or stop concurrently #93
ArgConverter::s_type_long_operations_cache is a single, process-wide robin_hood::unordered_map. Each runtime finds, inserts and erases its own entry from its own thread, and nothing synchronizes those accesses:
find / insert:GetTypeLongCache(), on the first long conversion in that runtime.
erase:onDisposeEnv() / onDisposeRuntime(), when the runtime is disposed.
When several workers start or stop at once, one thread can rehash the table while another is reading or writing it. That is a data race on the map, and in practice a heap use-after-free.
Affects both binding layers:
NativeScript/ffi/jni/napi/conversion/ArgConverter.{h,cpp}: the map is keyed by napi_env.
NativeScript/ffi/jni/jsi/conversion/ArgConverter.{h,cpp}: the map is keyed by rt.identity().
How it was found
A worker stress test, run while testing #91 and #92 on an API 36 arm64 emulator. HWASan was enabled locally by uncommenting the -fsanitize=hwaddress lines in platforms/android/test-app/runtime/CMakeLists.txt; it is off by default. The test:
12 workers at a time are created and terminated after 20–300 ms;
each worker churns Java objects and forces JS and Java GC;
the main runtime forces GC every 25 ms.
HWASan reported a memory error inside ArgConverter::GetTypeLongCache, in both the napi and jsi trees. On the old code (without #91), the crash happened in 1 of 3 napi runs and 1 of 2 jsi runs. It is independent of the GC-delivery race that #91 fixes.
Expected
Each runtime's cache is reachable only by that runtime, and creating or disposing one runtime can't corrupt another's entry. Possible fixes:
make the cache per-runtime state, reached the way MetadataNode's per-runtime cache is;
or guard the map with a mutex. This is cheap: a lookup happens once per long conversion, and the pointer could be cached after that.
Activity