Skip to content

(fix): Scope-aware runtime escape detection#36

Merged
mgyoo86 merged 5 commits intomasterfrom
fix/runtime_check
Mar 25, 2026
Merged

(fix): Scope-aware runtime escape detection#36
mgyoo86 merged 5 commits intomasterfrom
fix/runtime_check

Conversation

@mgyoo86
Copy link
Member

@mgyoo86 mgyoo86 commented Mar 25, 2026

Problem

Runtime escape detection (RUNTIME_CHECK=1) iterated all pool vectors when
checking for escaping arrays. This caused false-positive PoolRuntimeEscapeError
for arrays acquired in an outer @with_pool scope and used inside a nested
inner scope — a valid and common pattern.

@with_pool pool begin
    v = acquire!(pool, Float64, 100)      # outer scope
    @with_pool pool begin
        w = acquire!(pool, Float64, 50)   # inner scope
        result = compute!(v, w)           # v is safe here, but was flagged ✗
    end
end

Additionally, the direct-rewind @with_pool macro validated before cleaning
up leaked inner scopes, causing _scope_boundary to use an incorrect (leaked)
depth when an inner scope threw without rewind.

Solution

1. _scope_boundary — scope-aware overlap check

New @inline function that uses the existing checkpoint stack to compute the
boundary index: vectors at 1:boundary belong to outer scopes and are skipped.

@inline function _scope_boundary(tp::AbstractTypedPool, depth::Int)
    @inbounds if tp._checkpoint_depths[end] == depth
        return tp._checkpoint_n_active[end]
    end
    return tp.n_active   # no checkpoint → nothing acquired here → all safe
end

Applied to all three backends:

  • CPU: _check_pointer_overlap, _check_bitchunks_overlap
  • CUDA: _check_tp_cuda_overlap
  • Metal: _check_tp_metal_overlap

2. Macro reorder — leaked scope cleanup before validation

In the direct-rewind @with_pool expansion (both block-form and function-form),
leaked inner scope cleanup now runs before _validate_pool_return:

Before: execute → validate → cleanup leaked → rewind
After:  execute → cleanup leaked → validate → rewind

This ensures _scope_boundary always sees the correct _current_depth.

3. Robust poison for custom isbits types

_poison_fill! now handles custom isbits structs via duck-type zero
(0 * first(v)) when zero(T) is not defined, and skips poisoning entirely
for non-isbits reference types (where resize!(v, 0) handles invalidation).

Loading
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.

2 participants