Skip to content

Android: MetadataNode's shared class caches are not thread-safe across runtimes #100

Open

Description

@NathanWalker

Summary

MetadataNode's name caches are process-wide and shared by every runtime, but no lock guards them. Each runtime's thread can find and insert into them:

  • s_name2TreeNodeCache (std::string → MetadataTreeNode*): GetOrCreateTreeNodeByName;
  • s_name2NodeCache (std::string → MetadataNode*): GetOrCreate, GetOrCreateInternal, and class extension (fullExtendedName);
  • s_treeNode2NodeCache (MetadataTreeNode* → MetadataNode*): GetOrCreateInternal.

They are in both binding layers: NativeScript/ffi/jni/{napi,jsi}/metadata/MetadataNode.{h,cpp}, all three robin_hood::unordered_map.

The main runtime and every worker resolve Java classes through these caches. The first time two runtimes touch a class that isn't cached yet, both insert concurrently. One can rehash the table while the other is reading it, and a find / emplace pair can also leave two MetadataNodes for the same class.

Why this is separate from the other statics

These caches aren't keyed by runtime. They are meant to be shared, and they hand out raw pointers that callers keep. So a per-runtime map or a plain ConcurrentMap swap won't do. A fix has to keep returned pointers stable and make "find, else create and insert" atomic, so that two threads never create two nodes for one class. For example:

  • one mutex around each find-or-create path;
  • or a shared reader lock with an exclusive lock on the miss path.

Environment

Related: #93, #96.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions