Skip to content

Check unsynchronized counters and recycle probes in the large-allocation path #178

Closed

Description

@dimensionscape

The large-allocation path still has shared state accessed without consistent synchronization in src/hx/gc/Immix.cpp.

  • mRecycleSize is declared volatile int. AllocLarge reads it before taking mLargeListLock, while other allocating threads update it under that lock. volatile does not make that concurrent access atomic in C++.
  • AllocLarge reads mLargeAllocated to decide whether to collect before acquiring mLargeListLock. Other threads change the same size_t under that lock in AllocLarge, FreeLarge and onMemoryChange.
  • mLargeAllocSpace is read and conditionally written before taking the lock in AllocLarge and onMemoryChange. Concurrent allocations can access it together, and racing updates can lose a larger budget.
  • FreeLarge checks largeObjectRecycle.hasExtraCapacity(1) before taking mLargeListLock. The vector can be changed by another allocating thread under the lock.

The lock protects list mutations, but it does not synchronize these readers. The recycle-hint comment explicitly describes an unlocked read, so that hint needs a C++ synchronization mechanism even if a stale value is acceptable.

This is a source-level finding. I have not reproduced a crash or collected a TSan report for these accesses. The consequences need checking rather than assuming that every race corrupts the heap.

I'd start with a focused TSan workload that allocates and grows large arrays on several threads, exercising both recycling and cpp.vm.Gc.changeManagedMemory. Keep application-owned buffers thread-local so the workload does not introduce its own races. Check the current checkout, since several nearby races have already been fixed.

Possible fixes include a relaxed atomic recycle hint, synchronized counter snapshots and a monotonic atomic update or locked update for the budget. Keep vector inspection under the mutex. Do not hold mLargeListLock across CollectFromThisThread, since collection can need the same allocator state. Measure throughput and pauses before choosing a locking strategy.

Acceptance: the focused workload has no reports for these fields, the existing GC stress modes pass, and large-buffer allocation throughput does not regress materially. Related: #148 for sanitizer coverage and #177 for the separate budget-decay question.

Activity

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