Skip to content

Collected ThreadLocal owners can leave their values rooted on long-lived threads #181

Closed

Description

@dimensionscape

The fix for #68 prevents a recycled ThreadLocal slot from returning its previous owner's value, but the old value can still stay alive indefinitely on a long-lived thread.

In src/hx/thread/ThreadLocal.cpp, Impl::finalise increments the slot generation and returns the Impl to the free list. It does not clear the values stored on threads.

In src/hx/thread/ThreadImpl.cpp:

  • getLocal clears a stale value only when that slot is read with a different generation.
  • setLocal clears it by overwriting the slot when the replacement owner writes on that same thread.
  • __Mark marks the whole localSlots array without checking whether each slot still has a live owner.

If a ThreadLocal is collected and the thread never accesses its recycled slot again, that thread's ThreadImpl keeps marking the old value and everything reachable from it. Reusing the slot on some other thread does not clear this thread's value either. This can retain large request-local or task-local graphs in persistent workers.

This is a source-level retention finding, not a measured leak reproduction. A regression should establish that the ThreadLocal owner itself has been collected, then check a weak reference to its former payload after further collections while the worker remains alive. Keep the payload off conservative stacks and do not read or overwrite the slot, since either would hide the problem.

The generation check should also allow stale values to stop being GC roots without requiring another access on that thread. Possible approaches include a GC-aware slot-owner registry or clearing dead-owner slots while mutators are stopped. Account for finalizer ordering, surviving ThreadLocals, generational barriers and slot reuse before choosing an approach. Do not mutate a running thread's Haxe arrays from the ThreadLocal finalizer without synchronization.

This follows up on the retention part of #68. Its existing regression checks that reads return null for a recycled slot; it does not check that an unaccessed stale payload becomes collectible.

Activity

  1. added a commit that references this issue on Oct 20, 2026
    67dc321
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions