Skip to content

[TrimmableTypeMap] Fix thread safety in TrimmableTypeMap.Initialize() #11048

@simonrozsival

Description

@simonrozsival

Part of #10788

Problem

TrimmableTypeMap.Initialize() has a race condition. The Interlocked.CompareExchange correctly ensures only one instance wins the singleton slot, but the losing thread still proceeds to call RegisterBootstrapNativeMethod():

// TrimmableTypeMap.cs:41-48
internal static void Initialize()
{
    var instance = new TrimmableTypeMap();
    var previous = Interlocked.CompareExchange(ref s_instance, instance, null);
    Debug.Assert(previous is null, "TrimmableTypeMap must only be created once.");
    instance.RegisterBootstrapNativeMethod();  // runs even if CompareExchange lost
}

If two threads race on Initialize():

  1. Both create a TrimmableTypeMap instance (expensive — scans type map attributes)
  2. Only one wins the CompareExchange
  3. Both call RegisterBootstrapNativeMethod() — the losing thread registers on its discarded instance, which is a no-op, but it still creates JNI types and calls RegisterNatives unnecessarily

Fix

Add an early return guard after the CompareExchange:

internal static void Initialize()
{
    var instance = new TrimmableTypeMap();
    var previous = Interlocked.CompareExchange(ref s_instance, instance, null);
    if (previous is not null)
        return;
    instance.RegisterBootstrapNativeMethod();
}

Impact

Low — Initialize() is typically called once during startup. But the fix is trivial and prevents unnecessary work + potential double JNI registration.

Metadata

Metadata

Labels

copilot`copilot-cli` or other AIs were used to author thistrimmable-type-map

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions