Summary

This PR fixes the NullPointerException that occurs in Scala Native when forking a large number of fibers concurrently, as reported in #9681.

Problem

When running the PromiseSpec - waiter stack safety test on Scala Native, which forks 10,000 fibers, a NullPointerException is thrown:

Exception in thread "zio-fiber-931" java.lang.NullPointerException: null
at scala.scalanative.runtime.package$.throwNullPointer(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.treeifyBin(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.putVal(Unknown Source)
at java.util.concurrent.ConcurrentHashMap$KeySetView.add(Unknown Source)
at zio.internal.WeakConcurrentBag.addToLongTermStorage(Unknown Source)
...

The root cause is that the Scala Native implementation of ConcurrentHashMap has a bug in the treeifyBin method, which is called when a hash bucket needs to be converted from a linked list to a red-black tree for better performance. Under high concurrency, this operation can result in a NullPointerException.

Solution

This PR modifies the PlatformSpecific.scala file for the Native platform to use Collections.synchronizedSet(new HashSet()) instead of ConcurrentHashMap.newKeySet() for the newConcurrentSet methods.

Why this approach?

  1. Targeted fix: The change only affects Scala Native, leaving JVM and JS implementations unchanged
  2. Correctness over performance: While Collections.synchronizedSet may have slightly different performance characteristics than ConcurrentHashMap.newKeySet(), it provides correct behavior under high concurrency
  3. Minimal change: Only two methods are modified, reducing the risk of introducing new issues
  4. Consistent with existing patterns: The codebase already uses Collections.synchronizedSet for other concurrent collections in the Native platform (e.g., newConcurrentWeakSet)

Trade-offs

  • Collections.synchronizedSet uses a single lock for all operations, which may reduce throughput under very high contention compared to ConcurrentHashMap’s segment-based locking
  • However, for the use case in WeakConcurrentBag (storing fiber references), the correctness benefit outweighs any potential performance impact

Changes

  • core/native/src/main/scala/zio/internal/PlatformSpecific.scala: Modified newConcurrentSet methods to use synchronized HashSet instead of ConcurrentHashMap.newKeySet()

Testing

This fix should allow the PromiseSpec - waiter stack safety test to pass on Scala Native without throwing NullPointerException.

Related Issues

Fixes #9681

/claim #9681

Claim

Total prize pool $150
Total paid $0
Status Pending
Submitted January 27, 2026
Last updated January 27, 2026

Contributors

AN

andresctirado

@andresctirado

100%

Sponsors

ZI

ZIO

@ZIO

$150