From b94cdfa8c1350d169f93a0970e719916109743fa Mon Sep 17 00:00:00 2001 From: "zackary.l.jackson" Date: Fri, 2 Oct 2026 02:21:08 +0000 Subject: [PATCH 1/2] fix(memory): resample unconditionally after allocator trim Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/resident_memory.rs | 10 ++++- .../src/resident_memory/tests.rs | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-runtime-core/src/resident_memory.rs b/crates/tracedecay-runtime-core/src/resident_memory.rs index af90cb3e4e..2112c19eeb 100644 --- a/crates/tracedecay-runtime-core/src/resident_memory.rs +++ b/crates/tracedecay-runtime-core/src/resident_memory.rs @@ -726,6 +726,14 @@ impl ResidentMemoryPressureV1 { self.state() } + /// Refresh pressure after reclaim without running reclaimers recursively. + fn resample_after_reclaim(&self) -> Option { + let sample = (self.sampler)()?; + self.publish_observation(sample.admission_bytes()); + self.publish_over_budget_gauge(); + Some(self.state()) + } + fn checkpoint_micros(&self) -> u64 { u64::try_from(self.checkpoint_epoch.elapsed().as_micros()).unwrap_or(u64::MAX - 1) } @@ -1056,7 +1064,7 @@ pub fn register_process_allocator_pressure_reclaimer_v1( Arc::new(move |request| { let trim = release_process_allocator_memory_v1(); if let Some(pressure) = pressure_weak.upgrade() { - pressure.sample_for_checkpoint(); + pressure.resample_after_reclaim(); } tracing::info!( event = "process_allocator_trimmed", diff --git a/crates/tracedecay-runtime-core/src/resident_memory/tests.rs b/crates/tracedecay-runtime-core/src/resident_memory/tests.rs index af033c2f1d..0c0a5e3a38 100644 --- a/crates/tracedecay-runtime-core/src/resident_memory/tests.rs +++ b/crates/tracedecay-runtime-core/src/resident_memory/tests.rs @@ -1163,6 +1163,47 @@ fn allocator_trim_reclaimer_runs_under_pressure_and_reports_only_measured_releas } } +#[test] +fn allocator_trim_reclaimer_resamples_pressure_after_checkpoint_throttle() { + let limit = bytes(PRESSURE_TEST_LIMIT_BYTES); + let measured = Arc::new(AtomicU64::new(0)); + let sampled = Arc::clone(&measured); + let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler( + limit, + Arc::new(move || { + let observed = sampled.load(Ordering::Acquire); + Some(ProcessResidentSampleV1 { + resident_bytes: observed, + unreclaimable_bytes: observed, + swapped_bytes: 0, + cgroup_committed_bytes: None, + }) + }), + )); + let above_high = pressure.high_watermark_bytes() + 1; + let below_low = pressure.low_watermark_bytes().saturating_sub(1); + measured.store(above_high, Ordering::Release); + let _trim = super::register_process_allocator_pressure_reclaimer_v1(&pressure) + .expect("allocator trim registration"); + + assert!(pressure.sample_for_checkpoint().is_over_budget()); + assert!(matches!( + pressure.state(), + ResidentMemoryPressureStateV1::OverBudget { .. } + )); + + measured.store(below_low, Ordering::Release); + pressure.publish_observed_resident_bytes(above_high); + + assert!(matches!( + pressure.state(), + ResidentMemoryPressureStateV1::Nominal { + observed_bytes, + .. + } if observed_bytes == below_low + )); +} + #[test] fn allocator_pressure_reclaimer_installation_preserves_registration_failure() { let pressure = Arc::new(ResidentMemoryPressureV1::new(bytes( From 5b651591440fe3371de8582d87b63b5e525d3eab Mon Sep 17 00:00:00 2001 From: "zackary.l.jackson" Date: Fri, 2 Oct 2026 02:54:49 +0000 Subject: [PATCH 2/2] test(memory): assert allocator-trim recovery through admission Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/resident_memory/tests.rs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/crates/tracedecay-runtime-core/src/resident_memory/tests.rs b/crates/tracedecay-runtime-core/src/resident_memory/tests.rs index 0c0a5e3a38..15614c4895 100644 --- a/crates/tracedecay-runtime-core/src/resident_memory/tests.rs +++ b/crates/tracedecay-runtime-core/src/resident_memory/tests.rs @@ -1164,9 +1164,9 @@ fn allocator_trim_reclaimer_runs_under_pressure_and_reports_only_measured_releas } #[test] -fn allocator_trim_reclaimer_resamples_pressure_after_checkpoint_throttle() { +fn allocator_trim_readmits_growth_when_memory_recovers_inside_checkpoint_throttle() { let limit = bytes(PRESSURE_TEST_LIMIT_BYTES); - let measured = Arc::new(AtomicU64::new(0)); + let measured = Arc::new(AtomicU64::new(PRESSURE_TEST_LIMIT_BYTES)); let sampled = Arc::clone(&measured); let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler( limit, @@ -1180,28 +1180,27 @@ fn allocator_trim_reclaimer_resamples_pressure_after_checkpoint_throttle() { }) }), )); - let above_high = pressure.high_watermark_bytes() + 1; - let below_low = pressure.low_watermark_bytes().saturating_sub(1); - measured.store(above_high, Ordering::Release); + let authority = Arc::new(ProcessResidentMemoryV1::with_pressure( + limit, + Arc::clone(&pressure), + )); let _trim = super::register_process_allocator_pressure_reclaimer_v1(&pressure) .expect("allocator trim registration"); + let owner = key("project-a", "worktree-a", "generation-a", "canonical"); - assert!(pressure.sample_for_checkpoint().is_over_budget()); - assert!(matches!( - pressure.state(), - ResidentMemoryPressureStateV1::OverBudget { .. } - )); + pressure.sample_for_checkpoint(); + let refused = authority + .reserve(owner.clone(), growth_request()) + .expect_err("a process at its limit refuses growth"); + assert!(refused.is_observed_over_budget()); - measured.store(below_low, Ordering::Release); - pressure.publish_observed_resident_bytes(above_high); + measured.store(64 * 1024 * 1024, Ordering::Release); + pressure.publish_observed_resident_bytes(PRESSURE_TEST_LIMIT_BYTES); - assert!(matches!( - pressure.state(), - ResidentMemoryPressureStateV1::Nominal { - observed_bytes, - .. - } if observed_bytes == below_low - )); + let _admitted = authority + .reserve(owner, growth_request()) + .expect("growth is admitted once the trim measures recovered memory"); + assert_eq!(authority.snapshot().used_bytes, 16 * 1024 * 1024); } #[test]