Skip to content

Commit e39201a

Browse files
Merge pull request #1488 from ScriptedAlchemy/agent/sweep-host-crates
fix(hosts): release advisory writer locks explicitly
2 parents 32b097c + c198f0e commit e39201a

8 files changed

Lines changed: 107 additions & 11 deletions

File tree

crates/tracedecay-agent-hosts/src/agents/host_bundle/writer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ pub struct HostBundleWriterV1 {
5454
_writer_lock: fs::File,
5555
}
5656

57+
impl Drop for HostBundleWriterV1 {
58+
fn drop(&mut self) {
59+
if let Err(error) = self._writer_lock.unlock() {
60+
tracing::warn!(error = %error, "host bundle writer lock could not be released");
61+
}
62+
}
63+
}
64+
5765
impl HostBundleWriterV1 {
5866
pub fn open(root_path: impl Into<PathBuf>) -> Result<Self, HostBundleError> {
5967
let root_path = root_path.into();

crates/tracedecay-code-index-runtime/src/code_index_scheduler/publication_store.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,6 @@ pub struct DaemonCodeIndexPublicationStoreV1 {
478478
/// Test-only: observes each batched `segments_root` directory fsync so a
479479
/// test can assert a single publish syncs the directory once regardless
480480
/// of how many new file segments it wrote.
481-
/// CI retrigger marker (no behavior).
482481
#[cfg(test)]
483482
segments_dir_sync_observer: Option<Arc<dyn Fn() + Send + Sync>>,
484483
/// Last generation handed to `publish_atomically`. A transient store
@@ -2633,6 +2632,20 @@ impl CodeChunkProjectionSink for DaemonProjectionSinkV1 {
26332632
output_digest: None,
26342633
}),
26352634
);
2635+
decisions.extend(
2636+
request
2637+
.changes
2638+
.reused
2639+
.iter()
2640+
.map(|change| ChunkProjectionDecisionV1 {
2641+
chunk_id: change.chunk_id.clone(),
2642+
prior_chunk_digest: change.prior_digest.clone(),
2643+
current_chunk_digest: change.current_digest.clone(),
2644+
operation: ProjectionOperationV1::Reused,
2645+
outcome: ProjectionOutcomeV1::Reused,
2646+
output_digest: None,
2647+
}),
2648+
);
26362649
decisions.sort_by(|left, right| left.chunk_id.cmp(&right.chunk_id));
26372650
receipt_builder
26382651
.build(&decisions)

crates/tracedecay-code-index-runtime/src/code_index_scheduler/query_runtime.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,5 +1039,3 @@ mod tests {
10391039
.expect("fallback policy is accepted by the fallback authority mode");
10401040
}
10411041
}
1042-
1043-
// CI retrigger marker (no behavior).

crates/tracedecay-code-index/src/production/lexical_page_source.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ pub(super) const LEXICAL_FILE_PREFETCH_BYTES_V1: u64 = 64 * 1024 * 1024;
5454
/// byte budget above (`LEXICAL_FILE_PREFETCH_BYTES_V1`) be the binding
5555
/// constraint far more often, without changing how many bytes are held in
5656
/// flight at once (the byte cap still applies on top of this file cap).
57-
/// CI retrigger marker (no behavior).
5857
pub(super) const LEXICAL_DECODE_WINDOW_FILES_PER_WORKER_V1: usize = 4;
5958

6059
type PersistedSealedLexicalCursorFields = (

crates/tracedecay-code-index/tests/code_index_suite/chunk_incremental.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,37 @@ fn duplicate_file_occurrences_are_rejected_before_manifest_flattening() {
353353
)))
354354
);
355355
}
356+
357+
#[test]
358+
fn standalone_pool_failure_is_parallelism_not_identity_validation() {
359+
struct ClearForce;
360+
impl Drop for ClearForce {
361+
fn drop(&mut self) {
362+
tracedecay_code_index::parallelism::force_install_failure_for_test(false);
363+
}
364+
}
365+
let _clear = ClearForce;
366+
tracedecay_code_index::parallelism::force_install_failure_for_test(true);
367+
368+
let expected_generation = generation(2);
369+
let file = baseline_file(&expected_generation, "file.ok", "src/lib.rs");
370+
let err = GenerationChunkManifestV1::new(expected_generation, vec![file]).unwrap_err();
371+
372+
match err {
373+
ChunkIncrementErrorV1::Parallelism(
374+
tracedecay_code_index::parallelism::CodeIndexParallelismErrorV1::PoolBuild { message },
375+
) => {
376+
assert!(
377+
message.contains("forced"),
378+
"expected forced pool failure, got {message}"
379+
);
380+
}
381+
ChunkIncrementErrorV1::NonCanonical(cause) => {
382+
panic!(
383+
"operational pool failure must not be NonCanonical ({})",
384+
cause.reason_code()
385+
);
386+
}
387+
other => panic!("unexpected increment error: {other}"),
388+
}
389+
}

crates/tracedecay-hooks/src/delivery_spool.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ pub struct HookDeliveryReceiptSpoolV1 {
120120
_lock: File,
121121
}
122122

123+
impl Drop for HookDeliveryReceiptSpoolV1 {
124+
fn drop(&mut self) {
125+
if let Err(error) = self._lock.unlock() {
126+
tracing::warn!(error = %error, "hook delivery receipt spool lock could not be released");
127+
}
128+
}
129+
}
130+
123131
impl HookDeliveryReceiptSpoolV1 {
124132
/// Opens the spool without waiting for a held writer lock. Native callbacks
125133
/// use `open_within` with one synchronous budget for the lock wait.

crates/tracedecay-hooks/src/spool/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ impl HookSpoolV1 {
187187
hotpath::measure_block!("hooks.spool.fsync.directory", {
188188
shared_sync_directory(&root, DIRECTORY_POLICY).map_err(|_| HookSpoolError::Io)
189189
})?;
190-
drop(lease_file);
190+
lease_file.unlock().map_err(|_| HookSpoolError::Io)?;
191191
Ok(())
192192
}
193193

crates/tracedecay-query/src/retrieval/lexical/projection/artifact/builder.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use tracedecay_code_index::production::{
2424
VerifiedSealedLexicalSourceReceiptV1, VerifiedSealedLexicalSymbolDisplayV1,
2525
};
2626
use tracedecay_domain::{
27-
CodeSearchChunkAnchorV1, CodeSearchChunkV1, ExactTechnicalTermV1, ManifestDigest,
27+
CodeSearchChunkAnchorV1, CodeSearchChunkV1, ExactTechnicalTermV1, FileOccurrenceId,
28+
ManifestDigest,
2829
};
2930
use tracedecay_private_fs::framed_log::{DirectorySyncPolicy, sync_parent_directory};
3031
use tracedecay_private_fs::{create_private_file_retained, open_private_file};
@@ -2455,8 +2456,7 @@ fn validated_fixed_ledger_charge(
24552456
let serialized_bytes = metadata_serialized_upper_bound(metadata);
24562457
let fixed = ARTIFACT_SQLITE_CACHE_BYTES
24572458
.checked_add(
2458-
metadata
2459-
.retained_owned_bytes()
2459+
metadata_retained_bytes(metadata)
24602460
.checked_mul(2)
24612461
.ok_or_else(|| {
24622462
CodeLexicalArtifactErrorV1::Contract(
@@ -2490,13 +2490,50 @@ fn metadata_serialized_upper_bound(metadata: &CodeLexicalProjectionMetadataV1) -
24902490
.saturating_add(path.len())
24912491
.saturating_add(32)
24922492
});
2493-
metadata
2494-
.retained_owned_bytes()
2493+
metadata_retained_bytes(metadata)
24952494
.saturating_add(path_bytes)
24962495
.saturating_mul(6)
24972496
.saturating_add(512)
24982497
}
24992498

2499+
/// Owned bytes one projection metadata structure retains: logical paths at
2500+
/// capacity with per-entry b-tree node overhead, and every scalar identity
2501+
/// string charged as its `String` header plus payload length.
2502+
fn metadata_retained_bytes(metadata: &CodeLexicalProjectionMetadataV1) -> usize {
2503+
let path_bytes = metadata.logical_paths.iter().fold(
2504+
metadata.logical_paths.len().saturating_mul(
2505+
std::mem::size_of::<(FileOccurrenceId, String)>()
2506+
.saturating_add(BTREE_MAP_ENTRY_OVERHEAD_BYTES),
2507+
),
2508+
|bytes, (file, path)| {
2509+
bytes
2510+
.saturating_add(file.as_str().len())
2511+
.saturating_add(path.capacity())
2512+
},
2513+
);
2514+
let scalar_identities = [
2515+
Some(metadata.generation.as_str()),
2516+
metadata
2517+
.repository_id
2518+
.as_ref()
2519+
.map(|repository| repository.as_str()),
2520+
Some(metadata.freshness.source_namespace.as_str()),
2521+
Some(metadata.freshness.source_instance.as_str()),
2522+
Some(metadata.freshness.policy_revision.as_str()),
2523+
Some(metadata.exact_retriever_revision.as_str()),
2524+
Some(metadata.lexical_retriever_revision.as_str()),
2525+
Some(metadata.exact_score_domain.as_str()),
2526+
];
2527+
scalar_identities
2528+
.into_iter()
2529+
.flatten()
2530+
.fold(path_bytes, |bytes, identity| {
2531+
bytes
2532+
.saturating_add(std::mem::size_of::<String>())
2533+
.saturating_add(identity.len())
2534+
})
2535+
}
2536+
25002537
fn page_batch_ledger_charge_bytes(
25012538
metadata: &CodeLexicalProjectionMetadataV1,
25022539
pages: &[VerifiedSealedLexicalPageV1],
@@ -3802,7 +3839,6 @@ fn verify_clone_payload_digests<'body>(
38023839
Ok(())
38033840
}
38043841

3805-
/// CI retrigger marker (no behavior).
38063842
fn append_prepared_clone_bodies(
38073843
transaction: &Transaction<'_>,
38083844
pages: &[PreparedCodeLexicalArtifactPageV1],

0 commit comments

Comments
 (0)