Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/auto_manage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,33 @@ end

_stop_auto_manage_timer!() = (lock(_stop_auto_manage_timer_unlocked!, _AUTO_MANAGE_LOCK); nothing)

# Drop every live pool's pending maintenance request and reset the trim cadence. Called from
# the timer-stop path (below) so that once the Timer is gone, no stale flag set by the last
# sweep can still trigger one post-disable `_run_auto_manage!` at a later `@with_pool` entry.
# Runs under `_AUTO_MANAGE_LOCK` (the caller holds it), the same lock the sweep takes, so the
# stop-and-clear is atomic against a concurrent sweep. Dead WeakRefs are skipped (not pruned —
# the next sweep prunes them); the `_trim_requested` clear is guarded for backends lacking it.
function _clear_auto_manage_requests_unlocked!()
for ref in _AUTO_MANAGE_REGISTRY
pool = ref.value
pool === nothing && continue
p = pool::AbstractArrayPool
@atomic :monotonic p._compact_requested = false
if hasfield(typeof(p), :_trim_requested)
@atomic :monotonic p._trim_requested = false
end
end
_AUTO_MANAGE_TICK[] = 0
return nothing
end

function _stop_auto_manage_timer_unlocked!()
t = _AUTO_MANAGE_TIMER[]
if t !== nothing
close(t)
_AUTO_MANAGE_TIMER[] = nothing
end
_clear_auto_manage_requests_unlocked!() # no stale flag outlives the timer
return nothing
end

Expand Down
23 changes: 23 additions & 0 deletions test/test_auto_manage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,29 @@ AAP.disable_auto_manage!() # stop the __init__-started timer for deterministic
_clear_registry!()
end

@testset "disable! clears pending requests so no post-disable auto-manage runs" begin
# Regression: `disable_auto_manage!` must not just stop the Timer — it must also drop
# any flag the last sweep already set, or the NEXT `@with_pool` entry would run one
# more compaction/trim AFTER the user explicitly turned auto-manage off (surprising an
# allocation on a hot path someone disabled precisely to keep zero-alloc).
_clear_registry!()
AAP.disable_auto_manage!()
pool = _inactive_bloated(1_000_000, 100)
AAP.register_auto_manage!(pool)

AAP._auto_manage_sweep!(nothing) # last pre-disable sweep sets the flag
@test (@atomic pool._compact_requested) == true

AAP.disable_auto_manage!() # explicit off → must also clear flags
@test AAP.auto_manage_enabled() == false
@test (@atomic pool._compact_requested) == false # stale request dropped

AAP._maybe_auto_manage!(pool) # a later scope entry now no-ops
@test (@atomic pool._compact_requested) == false
@test _cap(pool.float64.vectors[1]) >= 1_000_000 # NOT compacted post-disable
_clear_registry!()
end

@testset "enable! replaces an existing timer (closes the old one)" begin
AAP.disable_auto_manage!()
AAP.enable_auto_manage!(compact_interval = 0.1)
Expand Down
Loading