tensorfs 0.3.35__tar.gz → 0.3.37__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tensorfs-0.3.35 → tensorfs-0.3.37}/Cargo.lock +2 -2
- {tensorfs-0.3.35 → tensorfs-0.3.37}/Cargo.toml +1 -1
- {tensorfs-0.3.35 → tensorfs-0.3.37}/PKG-INFO +1 -1
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/adoption.rs +140 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/reclaim.rs +34 -106
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/repo_cache.rs +9 -58
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/tests.rs +64 -194
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/reclaim_run.rs +65 -28
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/repo_cache.rs +67 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/pyproject.toml +1 -1
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/crates/tensorfs-py/src/store.rs +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/python/tensorfs/_ext.pyi +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.35 → tensorfs-0.3.37}/python/tensorfs/py.typed +0 -0
|
@@ -392,7 +392,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
392
392
|
|
|
393
393
|
[[package]]
|
|
394
394
|
name = "tensorfs-core"
|
|
395
|
-
version = "0.3.
|
|
395
|
+
version = "0.3.37"
|
|
396
396
|
dependencies = [
|
|
397
397
|
"fs2",
|
|
398
398
|
"minicbor",
|
|
@@ -404,7 +404,7 @@ dependencies = [
|
|
|
404
404
|
|
|
405
405
|
[[package]]
|
|
406
406
|
name = "tensorfs-py"
|
|
407
|
-
version = "0.3.
|
|
407
|
+
version = "0.3.37"
|
|
408
408
|
dependencies = [
|
|
409
409
|
"pyo3",
|
|
410
410
|
"tensorfs-core",
|
|
@@ -250,6 +250,146 @@ fn export(
|
|
|
250
250
|
#[cfg(test)]
|
|
251
251
|
mod tests {
|
|
252
252
|
use super::*;
|
|
253
|
+
use std::collections::BTreeSet;
|
|
254
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
255
|
+
use std::sync::Arc;
|
|
256
|
+
|
|
257
|
+
#[derive(Default)]
|
|
258
|
+
struct MeasuredIO {
|
|
259
|
+
moved: AtomicU64,
|
|
260
|
+
admitted: AtomicU64,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
impl crate::store::Progress for MeasuredIO {
|
|
264
|
+
fn moved(&self, bytes: u64) {
|
|
265
|
+
self.moved.fetch_add(bytes, Ordering::Relaxed);
|
|
266
|
+
}
|
|
267
|
+
fn admitted(&self) {
|
|
268
|
+
self.admitted.fetch_add(1, Ordering::Relaxed);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
impl MeasuredIO {
|
|
273
|
+
fn take(&self) -> (u64, u64) {
|
|
274
|
+
(
|
|
275
|
+
self.moved.swap(0, Ordering::Relaxed),
|
|
276
|
+
self.admitted.swap(0, Ordering::Relaxed),
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#[test]
|
|
282
|
+
fn multiple_checkpoint_groups_separate_rehash_from_adoption_metadata_io() {
|
|
283
|
+
let (store, meta, mut declaration, source, target) = fixture();
|
|
284
|
+
let mut third = declaration.components[0].add[0].clone();
|
|
285
|
+
third.key = "third".into();
|
|
286
|
+
declaration.components[0].add.push(third);
|
|
287
|
+
declaration.order.push(("model".into(), "third".into()));
|
|
288
|
+
declaration.max_new_bytes += 2048;
|
|
289
|
+
let raw = declaration.canonical_work_bytes().unwrap();
|
|
290
|
+
let writer = begin(&store, &meta, &source, 1, declaration.clone(), None).unwrap();
|
|
291
|
+
let first = write(&store, &meta, &source, "weight", 3);
|
|
292
|
+
checkpoint_progress(&store, &meta, &source, 1, "source", "model", None).unwrap();
|
|
293
|
+
let second = write(&store, &meta, &source, "next", 4);
|
|
294
|
+
let (_, checkpoint) =
|
|
295
|
+
checkpoint_progress(&store, &meta, &source, 1, "source", "model", None).unwrap();
|
|
296
|
+
fence(&meta, &source, 1).unwrap();
|
|
297
|
+
release_guards(&meta, Some(writer.writer_hold), writer.source_leases);
|
|
298
|
+
|
|
299
|
+
let progress = Arc::new(MeasuredIO::default());
|
|
300
|
+
let store = store.observed(progress.clone());
|
|
301
|
+
eprintln!("CR108_IO_ROOT {}", store.root().display());
|
|
302
|
+
eprintln!("CR108_IO_PAYLOAD {} {}", first.sha256, second.sha256);
|
|
303
|
+
// Lose only the verification receipts in this fixture, not the actual
|
|
304
|
+
// bytes. The real verifier must rehash them before the warm adoption arm.
|
|
305
|
+
let catalog = crate::catalog::Catalog::open(store.root()).unwrap();
|
|
306
|
+
catalog.drop_verification(&first.sha256);
|
|
307
|
+
catalog.drop_verification(&second.sha256);
|
|
308
|
+
drop(catalog);
|
|
309
|
+
eprintln!("CR108_IO_PHASE verification");
|
|
310
|
+
assert!(matches!(
|
|
311
|
+
store.verify(&first.sha256).unwrap(),
|
|
312
|
+
crate::store::Verdict::Invalidated { .. }
|
|
313
|
+
));
|
|
314
|
+
assert!(matches!(
|
|
315
|
+
store.verify(&second.sha256).unwrap(),
|
|
316
|
+
crate::store::Verdict::Invalidated { .. }
|
|
317
|
+
));
|
|
318
|
+
let verification = progress.take();
|
|
319
|
+
assert_eq!(verification, (4096, 0));
|
|
320
|
+
|
|
321
|
+
eprintln!("CR108_IO_PHASE inspect_before");
|
|
322
|
+
let before: BTreeSet<_> = store.objects().unwrap().into_iter().collect();
|
|
323
|
+
let before_payloads: Vec<_> = [&first, &second]
|
|
324
|
+
.iter()
|
|
325
|
+
.map(|object| {
|
|
326
|
+
let path = store.blob_path(&object.sha256);
|
|
327
|
+
(
|
|
328
|
+
std::fs::read(&path).unwrap(),
|
|
329
|
+
std::fs::metadata(path).unwrap().modified().unwrap(),
|
|
330
|
+
)
|
|
331
|
+
})
|
|
332
|
+
.collect();
|
|
333
|
+
eprintln!("CR108_IO_PHASE adoption");
|
|
334
|
+
let (_, adopted) = adopt_checkpoint(
|
|
335
|
+
&store,
|
|
336
|
+
&meta,
|
|
337
|
+
&target,
|
|
338
|
+
&source,
|
|
339
|
+
&raw,
|
|
340
|
+
checkpoint.head.as_ref().unwrap(),
|
|
341
|
+
"target",
|
|
342
|
+
"model",
|
|
343
|
+
)
|
|
344
|
+
.unwrap();
|
|
345
|
+
let adoption = progress.take();
|
|
346
|
+
eprintln!("CR108_IO_PHASE inspect_after");
|
|
347
|
+
let fresh: Vec<_> = store
|
|
348
|
+
.objects()
|
|
349
|
+
.unwrap()
|
|
350
|
+
.into_iter()
|
|
351
|
+
.filter(|id| !before.contains(id))
|
|
352
|
+
.collect();
|
|
353
|
+
let metadata_bytes: u64 = fresh
|
|
354
|
+
.iter()
|
|
355
|
+
.map(|id| std::fs::metadata(store.blob_path(id)).unwrap().len())
|
|
356
|
+
.sum();
|
|
357
|
+
assert!(metadata_bytes > 0);
|
|
358
|
+
assert_eq!(adoption, (metadata_bytes, fresh.len() as u64));
|
|
359
|
+
for (object, prior) in [&first, &second].iter().zip(before_payloads) {
|
|
360
|
+
let path = store.blob_path(&object.sha256);
|
|
361
|
+
assert_eq!(std::fs::read(&path).unwrap(), prior.0);
|
|
362
|
+
assert_eq!(
|
|
363
|
+
std::fs::metadata(path).unwrap().modified().unwrap(),
|
|
364
|
+
prior.1
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
eprintln!("CR108_IO_PHASE restore");
|
|
368
|
+
let next = begin(
|
|
369
|
+
&store,
|
|
370
|
+
&meta,
|
|
371
|
+
&target,
|
|
372
|
+
1,
|
|
373
|
+
declaration,
|
|
374
|
+
adopted.head.as_ref(),
|
|
375
|
+
)
|
|
376
|
+
.unwrap();
|
|
377
|
+
assert_eq!(
|
|
378
|
+
completed(&meta, &target, 1).unwrap().0,
|
|
379
|
+
vec![
|
|
380
|
+
("model".into(), "next".into(), "value".into()),
|
|
381
|
+
("model".into(), "weight".into(), "value".into())
|
|
382
|
+
]
|
|
383
|
+
);
|
|
384
|
+
let restore = progress.take();
|
|
385
|
+
assert_eq!(restore, (0, 0));
|
|
386
|
+
eprintln!("CR108_IO_PHASE finish");
|
|
387
|
+
write(&store, &meta, &target, "third", 5);
|
|
388
|
+
commit(&store, &meta, &target, 1).unwrap();
|
|
389
|
+
release_guards(&meta, Some(next.writer_hold), next.source_leases);
|
|
390
|
+
eprintln!("CR108_IO_PHASE done");
|
|
391
|
+
eprintln!("CR108_IO_COUNTS verification={verification:?} adoption={adoption:?} restore={restore:?} adoption_metadata_bytes={metadata_bytes}");
|
|
392
|
+
}
|
|
253
393
|
|
|
254
394
|
fn fixture() -> (Store, Meta, Declaration, String, String) {
|
|
255
395
|
let path = std::env::temp_dir().join(format!(
|
|
@@ -498,22 +498,11 @@ pub(crate) fn plan_managed(
|
|
|
498
498
|
|
|
499
499
|
// ------------------------------------------------------------------ the act (tfs-066)
|
|
500
500
|
|
|
501
|
-
/// How a plan's bytes are proven to be gettable again before they are let go.
|
|
502
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
503
|
-
pub enum Recovery {
|
|
504
|
-
/// The object is already on the bound repo cache at its exact length.
|
|
505
|
-
Cache,
|
|
506
|
-
/// This run put it there.
|
|
507
|
-
Demoted,
|
|
508
|
-
/// The caller declared the roots recoverable from the hub. Their word, like free space.
|
|
509
|
-
Declared,
|
|
510
|
-
}
|
|
511
|
-
|
|
512
501
|
/// What `run` was asked to do beyond the plan itself.
|
|
513
502
|
#[derive(Debug, Clone, Copy, Default)]
|
|
514
503
|
pub struct RunOptions {
|
|
515
|
-
///
|
|
516
|
-
///
|
|
504
|
+
/// Best-effort cache warming before reclaim. This never authorizes deletion;
|
|
505
|
+
/// assume_upstream is still required because the cache is disposable.
|
|
517
506
|
pub demote: bool,
|
|
518
507
|
/// The caller declares these roots recoverable from the hub. Same class of caller-input
|
|
519
508
|
/// as free space (decisions.md 241): TensorFS cannot see the hub, the caller can.
|
|
@@ -573,87 +562,23 @@ impl RunReport {
|
|
|
573
562
|
}
|
|
574
563
|
}
|
|
575
564
|
|
|
576
|
-
///
|
|
577
|
-
///
|
|
578
|
-
///
|
|
579
|
-
///
|
|
580
|
-
/// pressure-evicted"* — with the falsifier *"a pressure sweep evicting a local-only
|
|
581
|
-
/// artifact"*. tfs-020 then deleted the `hub_published` boolean along with the pressure
|
|
582
|
-
/// plane it gated, so the invariant has had no mechanism since.
|
|
583
|
-
///
|
|
584
|
-
/// It comes back CONSTRUCTIVELY rather than as a flag someone must remember to set: the
|
|
585
|
-
/// cache is checked, or this run puts the bytes there, or the caller says so. Demotion is
|
|
586
|
-
/// the interesting one, because it does not FIND the invariant true — it MAKES it true, and
|
|
587
|
-
/// it makes it true against the `/workspace` volume, which measurably survives the
|
|
588
|
-
/// container-disk reset that destroys the Store (th-152, 2026-09-04).
|
|
589
|
-
fn recovery(
|
|
590
|
-
store: &Store,
|
|
591
|
-
row: &crate::storage::GcDelete,
|
|
592
|
-
options: RunOptions,
|
|
593
|
-
report: &mut RunReport,
|
|
594
|
-
) -> Option<Recovery> {
|
|
595
|
-
if options.assume_upstream {
|
|
596
|
-
return Some(Recovery::Declared);
|
|
597
|
-
}
|
|
598
|
-
let cache = store.repo_cache()?;
|
|
599
|
-
let kind = match row.kind {
|
|
600
|
-
"manifest" => CacheKind::Manifest,
|
|
601
|
-
_ => CacheKind::Blob,
|
|
602
|
-
};
|
|
603
|
-
let want = ObjectRef {
|
|
604
|
-
sha256: row.sha256.clone(),
|
|
605
|
-
length: row.length,
|
|
606
|
-
};
|
|
607
|
-
// Present at the exact length is the cheap check and the honest one: the cache's own
|
|
608
|
-
// admission path re-verifies the digest on the way back in, so a byte-level check here
|
|
609
|
-
// would pay for a promise something else already keeps.
|
|
610
|
-
if let Ok(path) = cache.path(kind, &row.sha256) {
|
|
611
|
-
if std::fs::metadata(&path).is_ok_and(|meta| meta.is_file() && meta.len() == row.length) {
|
|
612
|
-
report.already_cached_objects += 1;
|
|
613
|
-
return Some(Recovery::Cache);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
if !options.demote {
|
|
617
|
-
return None;
|
|
618
|
-
}
|
|
619
|
-
match cache.backfill_store(store, kind, &want) {
|
|
620
|
-
Ok(CacheWrite::Stored) => {
|
|
621
|
-
report.demoted_objects += 1;
|
|
622
|
-
report.demoted_bytes = report.demoted_bytes.saturating_add(row.length);
|
|
623
|
-
Some(Recovery::Demoted)
|
|
624
|
-
}
|
|
625
|
-
Ok(CacheWrite::Present) => {
|
|
626
|
-
report.already_cached_objects += 1;
|
|
627
|
-
Some(Recovery::Cache)
|
|
628
|
-
}
|
|
629
|
-
// A cache that cannot take the bytes is weather everywhere else in this crate and
|
|
630
|
-
// stays weather here — but for an EVICTION the consequence is different. On a fetch
|
|
631
|
-
// the authority is still R2; on an eviction this pod may hold the last copy. So a
|
|
632
|
-
// failed demote does not become a silent delete, it becomes a refusal.
|
|
633
|
-
Ok(CacheWrite::Unavailable) | Err(_) => None,
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/// Demote, tombstone, collect — in that order, and the order is the argument.
|
|
638
|
-
///
|
|
639
|
-
/// 1. **Demote** while the objects are still live and still reachable. Nothing is deleted.
|
|
640
|
-
/// 2. **Tombstone** — `DeleteRepository` per victim, through `apply_repository`'s
|
|
641
|
-
/// compare-and-set. This is the *explicit act* decisions.md 1135 requires: bytes become
|
|
642
|
-
/// collectable because somebody removed the thing that named them, never because a sweep
|
|
643
|
-
/// inferred they were garbage.
|
|
644
|
-
/// 3. **Collect** — the existing `gc::collect`, unchanged. It recomputes reachability from
|
|
645
|
-
/// `repos/`, `manifests/` and `blobs/` alone.
|
|
646
|
-
///
|
|
647
|
-
/// **Step 3 is why a wrong plan cannot corrupt the Store.** The plan chooses which ROOTS
|
|
648
|
-
/// stop being named; the census decides which OBJECTS that actually frees. A caller that
|
|
649
|
-
/// names every root still cannot delete an object a hold protects or a retained manifest
|
|
650
|
-
/// needs.
|
|
565
|
+
/// Reclaim named roots only after the caller confirms durable upstream custody.
|
|
566
|
+
/// Optional cache warming precedes repository tombstones and ordinary GC; cache
|
|
567
|
+
/// presence never authorizes either step. Retained roots and active holds still
|
|
568
|
+
/// protect their objects through the existing reachability census.
|
|
651
569
|
pub fn run(store: &Store, drop: &[RepositoryName], options: RunOptions) -> Result<RunReport> {
|
|
652
570
|
permitted(store)?;
|
|
653
571
|
let mut report = RunReport::default();
|
|
654
572
|
if drop.is_empty() {
|
|
655
573
|
return Ok(report);
|
|
656
574
|
}
|
|
575
|
+
if !options.assume_upstream {
|
|
576
|
+
return refuse(
|
|
577
|
+
Code::DURABILITY_UNPROVEN,
|
|
578
|
+
"named local roots require --assume-upstream after confirming durable upstream \
|
|
579
|
+
custody; an optional cache or --demote cannot preserve the only copy",
|
|
580
|
+
);
|
|
581
|
+
}
|
|
657
582
|
|
|
658
583
|
// EXCLUSIVITY IS PROVED BEFORE ANYTHING IS DONE, not discovered at the end.
|
|
659
584
|
//
|
|
@@ -698,26 +623,29 @@ pub fn run(store: &Store, drop: &[RepositoryName], options: RunOptions) -> Resul
|
|
|
698
623
|
.filter(|row| !already.contains(&row.key))
|
|
699
624
|
.collect();
|
|
700
625
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
626
|
+
if options.demote {
|
|
627
|
+
if let Some(cache) = store.repo_cache() {
|
|
628
|
+
for row in &condemned {
|
|
629
|
+
let kind = if row.kind == "manifest" {
|
|
630
|
+
CacheKind::Manifest
|
|
631
|
+
} else {
|
|
632
|
+
CacheKind::Blob
|
|
633
|
+
};
|
|
634
|
+
let object = ObjectRef {
|
|
635
|
+
sha256: row.sha256.clone(),
|
|
636
|
+
length: row.length,
|
|
637
|
+
};
|
|
638
|
+
match cache.backfill_store(store, kind, &object) {
|
|
639
|
+
Ok(CacheWrite::Stored) => {
|
|
640
|
+
report.demoted_objects += 1;
|
|
641
|
+
report.demoted_bytes = report.demoted_bytes.saturating_add(row.length);
|
|
642
|
+
}
|
|
643
|
+
Ok(CacheWrite::Present) => report.already_cached_objects += 1,
|
|
644
|
+
Ok(CacheWrite::Unavailable) | Err(_) => {}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
705
647
|
}
|
|
706
648
|
}
|
|
707
|
-
if !unrecoverable.is_empty() {
|
|
708
|
-
let shown: Vec<&str> = unrecoverable.iter().take(3).map(String::as_str).collect();
|
|
709
|
-
return refuse(
|
|
710
|
-
Code::DURABILITY_UNPROVEN,
|
|
711
|
-
format!(
|
|
712
|
-
"{} of {} objects cannot be got back if they go — {} — so nothing was \
|
|
713
|
-
dropped; bind a repo cache and pass --demote, or declare the roots \
|
|
714
|
-
recoverable with --assume-upstream",
|
|
715
|
-
unrecoverable.len(),
|
|
716
|
-
condemned.len(),
|
|
717
|
-
shown.join(", "),
|
|
718
|
-
),
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
649
|
|
|
722
650
|
let fault = crate::store::Fault::default();
|
|
723
651
|
for repo in drop {
|
|
@@ -83,6 +83,11 @@ enum SoftFailure {
|
|
|
83
83
|
Unavailable,
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
#[cfg(target_os = "linux")]
|
|
87
|
+
const O_NONBLOCK: i32 = 0o4000;
|
|
88
|
+
#[cfg(target_os = "macos")]
|
|
89
|
+
const O_NONBLOCK: i32 = 0x0004;
|
|
90
|
+
|
|
86
91
|
impl RepoObjectCache {
|
|
87
92
|
/// Name an optional cache. Construction performs no I/O and creates no metadata.
|
|
88
93
|
pub fn new(root: impl Into<PathBuf>) -> Self {
|
|
@@ -409,7 +414,10 @@ fn read_status(error: SoftFailure) -> CacheRead {
|
|
|
409
414
|
fn open_exact(path: &Path, length: u64) -> std::result::Result<File, SoftFailure> {
|
|
410
415
|
let file = OpenOptions::new()
|
|
411
416
|
.read(true)
|
|
412
|
-
.
|
|
417
|
+
// A FIFO open waits for a writer before metadata can reject it. Nonblocking
|
|
418
|
+
// open lets every nonregular entry reach the check below; regular files are
|
|
419
|
+
// unaffected. This does not impose a deadline on hard-mounted NFS I/O.
|
|
420
|
+
.custom_flags(store::O_NOFOLLOW | O_NONBLOCK)
|
|
413
421
|
.open(path)
|
|
414
422
|
.map_err(|error| {
|
|
415
423
|
if error.kind() == std::io::ErrorKind::NotFound {
|
|
@@ -1176,61 +1184,4 @@ mod exit_wait_tests {
|
|
|
1176
1184
|
);
|
|
1177
1185
|
fs::remove_dir_all(root).unwrap();
|
|
1178
1186
|
}
|
|
1179
|
-
|
|
1180
|
-
#[test]
|
|
1181
|
-
fn stalled_optional_copy_stops_waiting_without_joining_its_worker() {
|
|
1182
|
-
let root = std::env::temp_dir().join(format!(
|
|
1183
|
-
"tensorfs-exit-stalled-{}-{}",
|
|
1184
|
-
std::process::id(),
|
|
1185
|
-
store::now_nanos_unique()
|
|
1186
|
-
));
|
|
1187
|
-
let source = Store::init(&root.join("source")).unwrap();
|
|
1188
|
-
let object = source
|
|
1189
|
-
.put_stream(&mut b"still local".as_slice(), None, &Fault::default())
|
|
1190
|
-
.unwrap()
|
|
1191
|
-
.obj;
|
|
1192
|
-
let cache = RepoObjectCache::new(root.join("cache"));
|
|
1193
|
-
let fifo = cache.path(CacheKind::Blob, &object.sha256).unwrap();
|
|
1194
|
-
fs::create_dir_all(fifo.parent().unwrap()).unwrap();
|
|
1195
|
-
assert!(std::process::Command::new("mkfifo")
|
|
1196
|
-
.arg(&fifo)
|
|
1197
|
-
.status()
|
|
1198
|
-
.unwrap()
|
|
1199
|
-
.success());
|
|
1200
|
-
let activity = Arc::new(BackfillActivity::new(Ledger::with_resolution(
|
|
1201
|
-
Duration::from_millis(10),
|
|
1202
|
-
)));
|
|
1203
|
-
activity.offered();
|
|
1204
|
-
let observed = activity.clone();
|
|
1205
|
-
let wanted = object.clone();
|
|
1206
|
-
let store = source.clone();
|
|
1207
|
-
let worker = std::thread::spawn(move || {
|
|
1208
|
-
let result =
|
|
1209
|
-
cache.backfill_observed(&store, CacheKind::Blob, &wanted, Some(observed.as_ref()));
|
|
1210
|
-
observed.finished();
|
|
1211
|
-
result
|
|
1212
|
-
});
|
|
1213
|
-
assert!(
|
|
1214
|
-
!activity.wait(),
|
|
1215
|
-
"blocked optional cache never produced a no-progress verdict"
|
|
1216
|
-
);
|
|
1217
|
-
assert!(
|
|
1218
|
-
!worker.is_finished(),
|
|
1219
|
-
"the wait returned by joining the stalled cache worker"
|
|
1220
|
-
);
|
|
1221
|
-
assert!(
|
|
1222
|
-
source.record_valid(&object.sha256).is_ok(),
|
|
1223
|
-
"cache stall invalidated completed local bytes"
|
|
1224
|
-
);
|
|
1225
|
-
let unblock = OpenOptions::new()
|
|
1226
|
-
.read(true)
|
|
1227
|
-
.write(true)
|
|
1228
|
-
.custom_flags(0x800)
|
|
1229
|
-
.open(&fifo)
|
|
1230
|
-
.unwrap();
|
|
1231
|
-
assert_eq!(worker.join().unwrap().unwrap(), CacheWrite::Unavailable);
|
|
1232
|
-
drop(unblock);
|
|
1233
|
-
assert!(activity.wait());
|
|
1234
|
-
fs::remove_dir_all(root).unwrap();
|
|
1235
|
-
}
|
|
1236
1187
|
}
|