Skip to content

Make mmc cache replacement signal-safe - #51

Draft
cxzhong wants to merge 1 commit into
malb:masterfrom
cxzhong:agent/make-mmc-cache-replacement-signal-safe
Draft

Make mmc cache replacement signal-safe#51
cxzhong wants to merge 1 commit into
malb:masterfrom
cxzhong:agent/make-mmc-cache-replacement-signal-safe

Conversation

@cxzhong

@cxzhong cxzhong commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Make MMC cache state transitions robust when an embedding application leaves an M4RI call through a signal-driven non-local exit.

The cache now preserves a recoverable ownership state at every interruptible store, avoids exposing released storage through stale slot metadata, and makes a retried cache insertion idempotent. MMC is also disabled in OpenMP builds, where a non-local exit from an OpenMP critical region could otherwise leave the runtime lock held.

Problem

Each MMC slot stores a pointer and its allocation size. The previous replacement path released the pointer already stored in a slot before publishing the replacement pointer and size:

m4ri_mm_free(mm[j].data);
mm[j].size = size;
mm[j].data = condemned;

If control left the function between these operations, the global cache could still describe storage that had already been released. A later allocation could return that stale pointer, causing allocator corruption, a double free, or a segmentation fault.

There were related partial-update states in cache allocation and cleanup:

  • a slot could retain a nonzero size while its pointer had already been removed
  • cleanup used the size field as the ownership indicator, so an interrupted publication with a valid pointer and a zero size could be missed
  • retrying a free after the pointer had already been published could insert the same pointer into a second slot
  • an OpenMP critical region could be abandoned before the OpenMP runtime released its lock

Cache state model

The updated code treats the pointer field as the authoritative ownership indicator:

  • data == NULL, size == 0 is an empty slot
  • data != NULL, size == 0 is an interrupted but recoverable publication
  • data != NULL, size != 0 is a fully published cached allocation

Stores are performed through a volatile cache view so optimized builds preserve the required publication order. A new pointer is published only after the size is cleared, and the size is written last. Removing a pointer clears the size before clearing the pointer.

This means an interruption can leave either a valid published entry or a recoverable entry, but not a nonzero size paired with a missing or unrelated pointer.

Allocation changes

m4ri_mmc_malloc() now:

  • bypasses the cache for zero-sized allocations, keeping zero reserved for cache state
  • accepts a cache hit only when both the requested size matches and the pointer is non-NULL
  • clears the slot metadata in an order that cannot expose a stale nonzero size with a NULL pointer
  • falls back to the normal allocator when no valid cache entry matches

Release changes

m4ri_mmc_free() now scans the complete cache before publishing a pointer:

  • if the pointer is already present, the operation is treated as a retry rather than creating another ownership entry
  • if the existing entry has a zero size, the interrupted publication is completed by publishing the size
  • the first empty slot is remembered, but it is not written until the duplicate-pointer scan has completed
  • a new slot is published in size = 0, data = pointer, size = allocation_size order
  • when the cache is full, the incoming allocation is released directly and existing cache entries are left unchanged

The complete scan is important because an empty slot can appear before a later slot that already owns the same pointer. Returning at the first empty slot would reintroduce duplicate ownership.

NULL pointers, zero-sized allocations, and allocations at or above the cache threshold continue to use the normal allocator path.

Cleanup changes

m4ri_mmc_cleanup() now uses data != NULL to decide whether a slot owns storage. It clears the slot before releasing the pointer, which prevents a non-local exit from leaving a globally reachable dangling pointer that could later be returned or freed again.

Cleanup also resets stale size metadata for empty slots and reclaims interrupted publications where the pointer is present but the size has not yet been published.

OpenMP behavior

MMC is disabled when OpenMP support is enabled. The cache is process-global and its critical regions cannot be made robust when a signal handler performs a non-local exit past the OpenMP runtime's unlock path. Bypassing MMC in this configuration avoids both stale cache state and a permanently held OpenMP lock.

The implementation also keeps a local compile-time guard so an inconsistent configuration cannot accidentally activate the cache in an OpenMP build.

Scope

This change makes the MMC cache's own state transitions recoverable and prevents the stale-pointer and duplicate-ownership failures described above. It does not make the system allocator generally async-signal-safe, nor can a pointer-returning API atomically transfer ownership to a caller that may perform a non-local exit before accepting the return value.

Applications that require recovery from arbitrary signal-handler non-local exits across allocation or release calls still need to block the relevant signals across that API boundary or provide an explicit ownership acknowledgement protocol.

@cxzhong
cxzhong force-pushed the agent/make-mmc-cache-replacement-signal-safe branch 2 times, most recently from 9ee951e to eab7db6 Compare August 4, 2026 10:40
Keep MMC cache slot state valid at each interruptible store so a stale size cannot be paired with a NULL or smaller data pointer after a signal-driven non-local exit. Bypass MMC caching in OpenMP builds to avoid leaving the OpenMP critical lock held if a signal handler siglongjmp skips the runtime unlock path.

Add MMC coverage for cache reuse, stale empty slot repair, cleanup of unpublished slots, and the OpenMP cache-bypass configuration.
@cxzhong
cxzhong force-pushed the agent/make-mmc-cache-replacement-signal-safe branch from eab7db6 to ad807de Compare August 4, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant