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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ fn expected_worker_reservation_on(runtime: &CodeIndexWorkerRuntimeV1, remaining_
)
}

/// Ledger scenarios own their measured input independently of sibling tests.
pub(super) fn ledger_memory(limit: NonZeroU64) -> Arc<ProcessResidentMemoryV1> {
Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::with_sampler(
limit,
Arc::new(|| None),
)),
))
}

#[test]
fn captured_source_bytes_are_charged_until_the_snapshot_drops() {
let project = fixture();
Expand All @@ -84,9 +95,7 @@ fn captured_source_bytes_are_charged_until_the_snapshot_drops() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(
NonZeroU64::new(1024 * 1024).expect("source memory limit"),
));
let authority = ledger_memory(NonZeroU64::new(1024 * 1024).expect("source memory limit"));
scheduler.bind_resident_memory(Arc::clone(&authority));

let captured = scheduler
Expand Down Expand Up @@ -119,9 +128,7 @@ fn reconcile_leaves_no_captured_source_charged_after_the_build() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));
let captured = scheduler
.capture_authoritative_snapshot(None)
Expand Down Expand Up @@ -171,7 +178,7 @@ fn source_capture_refuses_before_build_when_retention_cannot_be_charged() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(NonZeroU64::MIN));
let authority = ledger_memory(NonZeroU64::MIN);
scheduler.bind_resident_memory(Arc::clone(&authority));

assert!(matches!(
Expand Down Expand Up @@ -291,9 +298,7 @@ fn worker_memory_reservation_is_charged_and_released_by_raii() {
2,
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1.get(),
);
let authority = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));
let reservation = scheduler
.reserve_worker_memory()
Expand Down Expand Up @@ -325,14 +330,7 @@ fn default_authority_worker_reserve_leaves_typed_snapshot_headroom() {
let runtime = bind_automatic_worker_runtime(&scheduler, 128, 128 * 1024 * 1024 * 1024);
// This case isolates the ledger's worker/snapshot split. Live process
// pressure can narrow the slab and is exercised with explicit samples below.
let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
Arc::new(|| None),
));
let authority = Arc::new(ProcessResidentMemoryV1::with_pressure(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
pressure,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));

let _worker = scheduler
Expand Down Expand Up @@ -376,12 +374,12 @@ fn worker_memory_reservation_refusal_is_typed() {
2,
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1.get(),
);
let authority = Arc::new(ProcessResidentMemoryV1::new(
let authority = ledger_memory(
NonZeroU64::new(
tracedecay_code_index::parallelism::INDEX_WORKER_RESIDENT_BUDGET_BYTES_V1 - 1,
)
.expect("positive test limit"),
));
);
scheduler.bind_resident_memory(authority);
assert!(matches!(
scheduler.reserve_worker_memory(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use tracedecay_domain::{
};
use tracedecay_graph_db::GraphConflictContextV1;
use tracedecay_privacy::CODE_SOURCE_SANITIZER_VERSION_V1;
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::ResidentMemoryPressureV1;
use tracedecay_runtime_core::resident_memory::{
ProcessResidentMemoryV1, RESIDENT_MEMORY_PRESSURE_ADMISSION_FLOOR_BYTES_V1,
RESIDENT_OWNER_IDLE_WINDOW_V1, ResidentMemoryAdmissionFailureV1, ResidentMemoryComponentIdV1,
Expand Down Expand Up @@ -1250,22 +1252,33 @@ impl HistoricalCodeIndexGenerationOwnerV1 {
}

impl CodeIndexWorktreeSchedulerV1 {
/// A standalone owner under the path policy a fresh profile resolves.
/// A standalone fixture owner under the path policy a fresh profile resolves.
/// Concurrent cases do not contribute measured pressure to this owner;
/// pressure scenarios bind their own explicit readings after opening it.
#[cfg(any(test, feature = "test-helpers"))]
pub fn open(
project_id: ProjectId,
project_root: &Path,
store_root: PathBuf,
byte_pool: Arc<SharedCodeIndexBytePoolV1>,
) -> Result<Self, CodeIndexSchedulerErrorV1> {
Self::open_with_policy(
let mut scheduler = Self::open_with_policy(
project_id,
project_root,
store_root,
byte_pool,
CodeIndexHintPolicyV1::default(),
crate::config::registry_default_index_path_policy(),
)
)?;
let limit = detected_process_resident_memory_limit_v1();
scheduler.bind_resident_memory(Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::with_sampler(
limit,
Arc::new(|| None),
)),
)));
Ok(scheduler)
}

pub fn open_with_policy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

use std::sync::Arc;

#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1;
pub use tracedecay_runtime_core::resident_memory::ProcessResidentMemoryV1;
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::RESIDENT_OWNER_IDLE_WINDOW_V1;
use tracedecay_runtime_core::resident_memory::{
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1, RESIDENT_OWNER_IDLE_WINDOW_V1,
ResidentMemoryPressureV1,
};
use tracedecay_runtime_core::resident_memory::{ResidentOwnersV1, process_resident_owners_v1};

use super::CodeIndexSchedulerRegistryV1;
Expand All @@ -15,10 +16,17 @@ impl CodeIndexSchedulerRegistryV1 {
#[cfg(any(test, feature = "test-helpers"))]
#[cfg_attr(not(test), allow(dead_code))]
pub fn new(max_worktrees: usize) -> Self {
// A fixture owns its ledger and pressure readings. Concurrent tests'
// RSS is not an input; pressure journeys pass an explicit authority.
let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
Arc::new(|| None),
));
Self::with_resident_memory_and_progress_producer_incarnation(
max_worktrees,
Arc::new(ProcessResidentMemoryV1::new(
Arc::new(ProcessResidentMemoryV1::with_pressure(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
pressure,
)),
1,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1476,14 +1476,6 @@ fn ledger_leaving_one_mib(
.expect("the held memory fits the ledger")
}

fn sixteen_gib_authority() -> Arc<ProcessResidentMemoryV1> {
let limit = NonZeroU64::new(16 * 1024 * 1024 * 1024).unwrap();
Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::new(limit)),
))
}

/// A 16 GiB authority reading a fixed process size, so a held ledger refuses
/// a refresh however much memory the tests beside it hold.
fn fixed_reading_authority() -> Arc<ProcessResidentMemoryV1> {
Expand All @@ -1510,7 +1502,7 @@ async fn a_refresh_refused_for_memory_publishes_when_an_idle_owner_releases_it()
let fixture = GitFixture::new(&[("src/main.rs", "fn main() {}\n")]);
let store = TempDir::new().expect("store root");
let owners = Arc::new(ResidentOwnersV1::new(IDLE_WINDOW));
let resident_memory = sixteen_gib_authority();
let resident_memory = fixed_reading_authority();
let (registry, _) = mounted_core_query_worktree_in(
CodeIndexSchedulerRegistryV1::with_resident_memory(1, Arc::clone(&resident_memory))
.with_resident_owners(Arc::clone(&owners)),
Expand Down Expand Up @@ -1576,7 +1568,7 @@ async fn a_refresh_refused_by_the_ledger_publishes_when_the_blocking_reservation
let fixture = GitFixture::new(&[("src/main.rs", "fn main() {}\n")]);
let store = TempDir::new().expect("store root");
let owners = Arc::new(ResidentOwnersV1::new(IDLE_WINDOW));
let resident_memory = sixteen_gib_authority();
let resident_memory = fixed_reading_authority();
let (registry, _) = mounted_core_query_worktree_in(
CodeIndexSchedulerRegistryV1::with_resident_memory(1, Arc::clone(&resident_memory))
.with_resident_owners(Arc::clone(&owners)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ use tracedecay_domain::{
use tracedecay_runtime_core::resident_memory::{
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1, ProcessResidentMemoryV1, ResidentHoldingV1,
ResidentOwnerBytesV1, ResidentOwnerKindV1, ResidentOwnerReleaseV1, ResidentOwnerSampleV1,
ResidentOwnerScopeV1, ResidentOwnerV1, ResidentOwnersV1, sampled_process_resident_bytes_v1,
ResidentOwnerScopeV1, ResidentOwnerV1, ResidentOwnersV1,
detected_process_resident_memory_limit_v1, sampled_process_resident_bytes_v1,
};

use super::super::memory_tests::ledger_memory;
#[cfg(unix)]
use super::stage_raw_git_path;
use super::{
Expand Down Expand Up @@ -1437,12 +1439,17 @@ fn retained_decode_incremental_seal_and_text_publish_stay_below_the_high_waterma
let fixture = GitFixture::new(&sources);
let store = TempDir::new().expect("store root");

// This scale journey measures real process pressure through admission
// as well as its RSS assertions, so retain the production pressure cell.
let memory_limit = detected_process_resident_memory_limit_v1();

{
let mut seed = scheduler(
&fixture,
store.path().to_path_buf(),
Arc::new(SharedCodeIndexBytePoolV1::default()),
);
seed.bind_resident_memory(Arc::new(ProcessResidentMemoryV1::new(memory_limit)));
published(seed.reconcile_now().expect("seed retained generation"));
}

Expand All @@ -1455,6 +1462,7 @@ fn retained_decode_incremental_seal_and_text_publish_stay_below_the_high_waterma
store.path().to_path_buf(),
Arc::new(SharedCodeIndexBytePoolV1::default()),
);
scheduler.bind_resident_memory(Arc::new(ProcessResidentMemoryV1::new(memory_limit)));
assert_eq!(scheduler.publication.sealed_decode_count(), 0);
assert!(matches!(
scheduler
Expand Down Expand Up @@ -11410,7 +11418,7 @@ fn graph_off_changed_source_worker_memory_denial_retries_without_decode() {
) > tight_limit.get(),
"the fixture limit must deny the installed worker plan"
);
let tight = Arc::new(ProcessResidentMemoryV1::new(tight_limit));
let tight = ledger_memory(tight_limit);
scheduler.bind_resident_memory(Arc::clone(&tight));
let denied = scheduler.reconcile_retained_text_generation_with(&metadata_a, true);
assert!(
Expand All @@ -11435,9 +11443,7 @@ fn graph_off_changed_source_worker_memory_denial_retries_without_decode() {
"a denied worker reservation must not leak a charge"
);

let adequate = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let adequate = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&adequate));
let outcome = scheduler
.reconcile_retained_text_generation_with(&metadata_a, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use tracedecay_runtime_core::resident_memory::{

use tracedecay_session_temporal_store::SessionTemporalAccess;

use super::super::memory_tests::ledger_memory;
use super::{
ALPHA_LIB_V1, CALLER_PAGE, CALLER_STAR, GitFixture, OwnerSignals, ReadyRetrievalControlV1,
SERVING_SEAT_FAILURE_CEILING, active_text_artifact_path, application_context,
Expand Down Expand Up @@ -2072,9 +2073,9 @@ fn reader_reservation_refusal_precedes_missing_artifact_access() {
store.path().to_path_buf(),
Arc::new(SharedCodeIndexBytePoolV1::default()),
);
scheduler.bind_resident_memory(Arc::new(ProcessResidentMemoryV1::new(
scheduler.bind_resident_memory(ledger_memory(
std::num::NonZeroU64::new(1024 * 1024).expect("tight memory limit"),
)));
));
let latest = scheduler.latest_complete().expect("restored generation");
assert!(
matches!(
Expand Down Expand Up @@ -2391,9 +2392,7 @@ fn text_artifact_ceilings_reserve_through_process_resident_memory() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
);
published(scheduler.reconcile_now().expect("publish"));
let tight = Arc::new(ProcessResidentMemoryV1::new(
std::num::NonZeroU64::new(1024 * 1024).expect("tight limit"),
));
let tight = ledger_memory(std::num::NonZeroU64::new(1024 * 1024).expect("tight limit"));
scheduler.bind_resident_memory(Arc::clone(&tight));
let latest = scheduler.latest_complete().expect("latest generation");
let denied = latest.advance_text_serving(usize::MAX);
Expand Down Expand Up @@ -2471,9 +2470,7 @@ fn text_artifact_ceilings_reserve_through_process_resident_memory() {

// An adequate authority admits the build and holds the reader charge
// for as long as the artifact owners serve.
let adequate = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let adequate = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
{
// A fresh scheduler restores the durable generation; the unchanged
// fixture needs no republish.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async fn ready_reading(
.await
.unwrap_or_else(|error| panic!("{phase} readiness read failed: {error:?}"));
let CodeIndexReadinessWaitReadV1::Reached { reading } = reading else {
panic!("exact worktree did not become ready: {reading:?}");
panic!("exact worktree did not become ready during {phase}: {reading:?}");
};
*reading
}
Expand Down
9 changes: 5 additions & 4 deletions crates/tracedecay/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,9 +1278,10 @@ fn spawn_tracedecay_daemon_process(
#[cfg(unix)]
daemon.release_socket_on_stop(socket_path.clone());

// Startup has no product deadline. The daemon logs `daemon_listening`
// once its endpoint is bound and published, so that line is readiness;
// stderr closing first means it exited.
// Startup has no product deadline. The daemon logs `daemon_ready`
// after bootstrap, immediately before polling its accept loop.
// `daemon_listening` only proves bind: setup can still hold connections
// in the backlog. Stderr closing before readiness means it exited.
let Some(stderr) = daemon.child.stderr.take() else {
// A caller redirected stderr, so only the endpoint can show readiness.
loop {
Expand Down Expand Up @@ -1321,7 +1322,7 @@ fn spawn_tracedecay_daemon_process(
startup.trim()
);
}
if startup[line_start..].starts_with("[tracedecay] event=daemon_listening ") {
if startup[line_start..].starts_with("[tracedecay] event=daemon_ready ") {
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,40 @@ fn twelve_mcp_cli_and_hook_clients_share_one_daemon_profile_store_owner() {
let project_id = tracedecay_runtime_core::storage::default_profile_project_id(&project_path);
let data_root =
tracedecay_runtime_core::storage::profile_sharded_data_root(&profile_root, &project_id);
let host = tracedecay_domain::NativeHostIdentityV1::CursorDesktop;
let (_, capture_report) = tracedecay_hooks::HookSpoolV1::open(
data_root.join("hook-v2-spool").join(host.hook_key()),
tracedecay_hooks::HookSpoolConfigV1::stock(host),
tracedecay_contracts::now_micros(),
)
.expect("open native capture receipt");
// Sequence survives daemon replay/acknowledgement, so this proves each
// callback was captured even if the owner already drained its envelope.
// A commit-lock timeout can stage an envelope whose first spool frame
// the daemon already acknowledged. Importing it appends a duplicate
// frame, so the sequence counts attempts rather than distinct callbacks.
// Each callback's completed receipt instead proves that its own capture
// landed, including one safely staged for replay, rather than returning
// the same transport response for an unbound hook.
let analytics = std::fs::read_to_string(data_root.join("hook_analytics.jsonl"))
.expect("read native capture outcomes");
let mut captured_sessions = analytics
.lines()
.map(|line| serde_json::from_str::<serde_json::Value>(line).expect("hook analytics row"))
.filter(|row| {
row["event"] == "hook_completed"
&& row["agent"] == "cursor"
&& row["hook_name"] == "afterFileEdit"
})
.map(|row| {
assert_eq!(
row["disposition"]["status"], "accepted_for_replay",
"native callback must be durably captured: {row}"
);
row["session_id"]
.as_str()
.expect("capture names the callback session")
.to_owned()
})
.collect::<Vec<_>>();
captured_sessions.sort();
assert_eq!(
capture_report.next_sequence,
1 + CONCURRENT_CLIENTS_PER_PATH as u64,
"every native callback must be captured, not merely accepted as unbound"
captured_sessions,
(0..CONCURRENT_CLIENTS_PER_PATH)
.map(|ordinal| format!("session-{ordinal}"))
.collect::<Vec<_>>(),
"every native callback must have exactly one successful capture outcome"
);
let daemon_stderr = std::fs::read_to_string(&daemon_stderr_path).expect("read daemon stderr");
assert!(
Expand Down
Loading