tensorfs 0.3.59__tar.gz → 0.3.61__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.59 → tensorfs-0.3.61}/Cargo.lock +2 -2
- {tensorfs-0.3.59 → tensorfs-0.3.61}/Cargo.toml +1 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/PKG-INFO +1 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/fetch.rs +32 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/main.rs +13 -3
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/catalog.rs +183 -4
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/checkpoint.rs +22 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/resume_tests.rs +23 -16
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived.rs +45 -7
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/durability.rs +17 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/gc.rs +5 -1
- tensorfs-0.3.61/crates/tensorfs-core/src/ingest/custody.rs +249 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/fingerprint.rs +24 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/journal.rs +13 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/mod.rs +1 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/source.rs +481 -104
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/transaction.rs +38 -7
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/stats.rs +21 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/storage.rs +20 -10
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/store.rs +72 -41
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/mod.rs +4 -1
- tensorfs-0.3.61/crates/tensorfs-core/src/transport/pace.rs +236 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/pull.rs +177 -100
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/speculate.rs +11 -3
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/tests.rs +254 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/write_through.rs +15 -14
- tensorfs-0.3.61/crates/tensorfs-py/src/ingest.rs +223 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-py/src/lib.rs +8 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-py/src/store.rs +19 -12
- {tensorfs-0.3.59 → tensorfs-0.3.61}/pyproject.toml +1 -1
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/__init__.py +8 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/_ext.pyi +28 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/asset_update.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/memory.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_download.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/trust_index.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/_derived_channel.py +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/_source_channel.py +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.59 → tensorfs-0.3.61}/python/tensorfs/py.typed +0 -0
|
@@ -439,7 +439,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
439
439
|
|
|
440
440
|
[[package]]
|
|
441
441
|
name = "tensorfs-core"
|
|
442
|
-
version = "0.3.
|
|
442
|
+
version = "0.3.61"
|
|
443
443
|
dependencies = [
|
|
444
444
|
"fs2",
|
|
445
445
|
"libc",
|
|
@@ -454,7 +454,7 @@ dependencies = [
|
|
|
454
454
|
|
|
455
455
|
[[package]]
|
|
456
456
|
name = "tensorfs-py"
|
|
457
|
-
version = "0.3.
|
|
457
|
+
version = "0.3.61"
|
|
458
458
|
dependencies = [
|
|
459
459
|
"pyo3",
|
|
460
460
|
"tensorfs-core",
|
|
@@ -273,7 +273,8 @@ fn pull_with_budget(
|
|
|
273
273
|
let mut request = PullRequest::new(&store, base, refspec, &credential, &policy);
|
|
274
274
|
request.lane = flag(flags, "lane").unwrap_or("");
|
|
275
275
|
request.session = flag(flags, "session").unwrap_or("");
|
|
276
|
-
request.streams = crate::flag_num(flags, "streams", transport::
|
|
276
|
+
request.streams = crate::flag_num(flags, "streams", transport::PULL_STREAMS);
|
|
277
|
+
request.streams_start = crate::flag_num(flags, "streams-start", transport::STREAMS);
|
|
277
278
|
request.deadline = deadline_of(flags);
|
|
278
279
|
request.download_budget = budget;
|
|
279
280
|
// THE WALK REPORTS WHILE IT WALKS.
|
|
@@ -300,7 +301,11 @@ fn pull_with_budget(
|
|
|
300
301
|
let observe = |_object: &ObjectRef, moved: u64, door: ObjectSource| progress.saw(moved, door);
|
|
301
302
|
request.on_plan = Some(&planned);
|
|
302
303
|
request.on_object = Some(&observe);
|
|
304
|
+
let before = tensorfs_core::stats::snapshot();
|
|
305
|
+
let started = std::time::Instant::now();
|
|
303
306
|
let report = ok!(transport::pull(&request));
|
|
307
|
+
let seconds = started.elapsed().as_secs_f64();
|
|
308
|
+
let after = tensorfs_core::stats::snapshot();
|
|
304
309
|
progress.settle();
|
|
305
310
|
if !tensorfs_core::repo_cache::finish_optional_backfills() {
|
|
306
311
|
eprintln!("repo cache replication incomplete; local fetch remains complete");
|
|
@@ -313,6 +318,32 @@ fn pull_with_budget(
|
|
|
313
318
|
quoted(&report.model), quoted(&report.manifest), quoted(&report.scope),
|
|
314
319
|
report.cache_writes.bytes(), report.bytes_cached, report.bytes_total,
|
|
315
320
|
);
|
|
321
|
+
// What each stream level bought, so a slow pull says whether more streams helped
|
|
322
|
+
// (one GET was the limit) or not (the link, disk or origin was).
|
|
323
|
+
let levels: Vec<String> = report
|
|
324
|
+
.levels
|
|
325
|
+
.iter()
|
|
326
|
+
.map(|level| {
|
|
327
|
+
format!(
|
|
328
|
+
"[{},{}]",
|
|
329
|
+
level.streams,
|
|
330
|
+
level.bytes_per_second.round() as u64
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
.collect();
|
|
334
|
+
// And what admitting them cost: a pull whose streams stop growing while its batches
|
|
335
|
+
// take most of the wall time is held by the disk, not the link.
|
|
336
|
+
eprintln!(
|
|
337
|
+
"{{\"event\":\"pull.streams\",\"model\":{},\"seconds\":{:.3},\"bytes_moved\":{},\"streams\":{},\"levels\":[{}],\"admission\":{{\"objects\":{},\"batches\":{},\"seconds\":{:.3}}}}}",
|
|
338
|
+
quoted(&report.model),
|
|
339
|
+
seconds,
|
|
340
|
+
report.bytes_moved,
|
|
341
|
+
report.streams,
|
|
342
|
+
levels.join(","),
|
|
343
|
+
after.admitted_objects - before.admitted_objects,
|
|
344
|
+
after.admission_batches - before.admission_batches,
|
|
345
|
+
(after.admission_nanos - before.admission_nanos) as f64 / 1e9,
|
|
346
|
+
);
|
|
316
347
|
println!(
|
|
317
348
|
"model {}@{} lane {}",
|
|
318
349
|
report.model, report.release, report.lane
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
//! The ONE transport (tfs-048/tfs-049/tfs-050). The credential is `--credential-file` or
|
|
43
43
|
//! TFS_CREDENTIAL (`bearer <token>`, or empty for anonymous), never bare argv; a
|
|
44
44
|
//! URL of `-` arrives on stdin. Flags: --hub <base> --lane --session --streams <n>
|
|
45
|
-
//! --timeout <s> --allow-hosts a,b,.c --allow-local --max-redirects <n>.
|
|
45
|
+
//! --streams-start <n> --timeout <s> --allow-hosts a,b,.c --allow-local --max-redirects <n>.
|
|
46
|
+
//! `--streams` is the most objects in flight (default 128, memory-bounded); the walk starts
|
|
47
|
+
//! at `--streams-start` (default 16) and doubles while measured goodput follows.
|
|
46
48
|
//! `tfs fetch` reads and backfills the Store's bound repo cache between the local Store
|
|
47
49
|
//! and the origin. It is bound by `tfs store ensure --repo-cache` and never by a flag here.
|
|
48
50
|
//!
|
|
@@ -505,7 +507,7 @@ fn cmd_repo_cache_restore(store: &Path, object: &str, length: &str, flags: &Flag
|
|
|
505
507
|
/// The chain head names links that live on the cache; each is read at its exact digest, and
|
|
506
508
|
/// the chain is DISCARDED WHOLE unless every link was written under the plan `--plan` names
|
|
507
509
|
/// — the plan a resuming run re-derives from the source headers alone, for zero tensor
|
|
508
|
-
/// bytes.
|
|
510
|
+
/// bytes. A discarded chain restores nothing and succeeds: the run converts afresh.
|
|
509
511
|
///
|
|
510
512
|
/// The objects come back exactly as a checkpoint's closure does. The conversion journal the
|
|
511
513
|
/// newest link points at is installed beside them under `--session`, so the next
|
|
@@ -548,7 +550,15 @@ fn cmd_repo_cache_resume(store: &Path, object: &str, length: &str, flags: &Flags
|
|
|
548
550
|
};
|
|
549
551
|
let set = match durability::fold(&chain, plan) {
|
|
550
552
|
Ok(set) => set,
|
|
551
|
-
Err(error) => return bail(error),
|
|
553
|
+
Err(error) if error.code == tensorfs_core::err::Code::IO_FAILED => return bail(error),
|
|
554
|
+
Err(error) => {
|
|
555
|
+
println!(
|
|
556
|
+
"chain DISCARDED {}: {} — nothing restored; the conversion starts a new chain",
|
|
557
|
+
error.code.as_str(),
|
|
558
|
+
error.detail
|
|
559
|
+
);
|
|
560
|
+
return ExitCode::SUCCESS;
|
|
561
|
+
}
|
|
552
562
|
};
|
|
553
563
|
println!(
|
|
554
564
|
"chain {} link(s) over operation {} — {} B declared durable",
|
|
@@ -903,8 +903,9 @@ impl Catalog {
|
|
|
903
903
|
hold_on(&self.connection()?, operation, key, length)
|
|
904
904
|
}
|
|
905
905
|
|
|
906
|
-
///
|
|
907
|
-
///
|
|
906
|
+
/// A HELD admission: the hold is written before the object is published, so an
|
|
907
|
+
/// operation that cannot hold it publishes nothing. Unheld admissions, which is every
|
|
908
|
+
/// pull, take `record_grouped` instead.
|
|
908
909
|
pub(crate) fn admit_blob(
|
|
909
910
|
&self,
|
|
910
911
|
operation: Option<&str>,
|
|
@@ -914,7 +915,7 @@ impl Catalog {
|
|
|
914
915
|
// One admission at a time per catalog in this process, holding the write lock from
|
|
915
916
|
// before publication: streams landing objects together queue here instead of in
|
|
916
917
|
// SQLite's busy timeout, where a lost race refused bytes already verified.
|
|
917
|
-
let turn =
|
|
918
|
+
let turn = held_admissions(&self.path);
|
|
918
919
|
let _turn = turn.lock().unwrap_or_else(|error| error.into_inner());
|
|
919
920
|
let mut connection = self.connection()?;
|
|
920
921
|
let transaction = connection
|
|
@@ -941,6 +942,117 @@ impl Catalog {
|
|
|
941
942
|
Ok(verified.is_some())
|
|
942
943
|
}
|
|
943
944
|
|
|
945
|
+
/// The object's existing hold and verification become durable together. Filesystem
|
|
946
|
+
/// publication has already happened, on the caller's thread, and precedes this record,
|
|
947
|
+
/// which is written with FULL synchronization.
|
|
948
|
+
///
|
|
949
|
+
/// Records queue per catalog in this process, and whichever arrives while no batch is
|
|
950
|
+
/// committing leads the next one: one immediate transaction and one commit make every
|
|
951
|
+
/// waiting record durable. One object alone costs what it always did. Before this,
|
|
952
|
+
/// directory creation, link, directory fsync and commit ran for one object at a time
|
|
953
|
+
/// under one lock, several fsyncs each, and on a disk busy flushing those same objects'
|
|
954
|
+
/// bodies that queue, not the network, set a pull's rate.
|
|
955
|
+
pub(crate) fn record_grouped(&self, recording: Recording) -> Result<()> {
|
|
956
|
+
let group = admission_group(&self.path);
|
|
957
|
+
let mut state = group
|
|
958
|
+
.state
|
|
959
|
+
.lock()
|
|
960
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
961
|
+
let ticket = state.next;
|
|
962
|
+
state.next += 1;
|
|
963
|
+
state.queue.push((ticket, recording));
|
|
964
|
+
loop {
|
|
965
|
+
if let Some(result) = state.done.remove(&ticket) {
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
if state.leading {
|
|
969
|
+
state = group
|
|
970
|
+
.turned
|
|
971
|
+
.wait(state)
|
|
972
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
state.leading = true;
|
|
976
|
+
let batch = std::mem::take(&mut state.queue);
|
|
977
|
+
let mut lead = Lead {
|
|
978
|
+
group: &group,
|
|
979
|
+
tickets: batch.iter().map(|(ticket, _)| *ticket).collect(),
|
|
980
|
+
results: Vec::new(),
|
|
981
|
+
};
|
|
982
|
+
drop(state);
|
|
983
|
+
let started = std::time::Instant::now();
|
|
984
|
+
let objects = batch.len();
|
|
985
|
+
lead.results = self.record_batch(batch);
|
|
986
|
+
crate::stats::admission_batch(objects, started.elapsed());
|
|
987
|
+
drop(lead);
|
|
988
|
+
state = group
|
|
989
|
+
.state
|
|
990
|
+
.lock()
|
|
991
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
fn record_batch(&self, batch: Vec<(u64, Recording)>) -> Vec<(u64, Result<()>)> {
|
|
996
|
+
let fail_all = |batch: Vec<(u64, Recording)>, error: Refusal| {
|
|
997
|
+
batch
|
|
998
|
+
.into_iter()
|
|
999
|
+
.map(|(ticket, _)| (ticket, Err(error.clone())))
|
|
1000
|
+
.collect::<Vec<_>>()
|
|
1001
|
+
};
|
|
1002
|
+
let mut connection = match self.connection() {
|
|
1003
|
+
Ok(connection) => connection,
|
|
1004
|
+
Err(error) => return fail_all(batch, error),
|
|
1005
|
+
};
|
|
1006
|
+
let mut transaction =
|
|
1007
|
+
match connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate) {
|
|
1008
|
+
Ok(transaction) => transaction,
|
|
1009
|
+
Err(error) => return fail_all(batch, sql("begin blob admission", error)),
|
|
1010
|
+
};
|
|
1011
|
+
let mut results = Vec::with_capacity(batch.len());
|
|
1012
|
+
let mut written = Vec::with_capacity(batch.len());
|
|
1013
|
+
// Each object's row sits behind its own savepoint: one refused row refuses that
|
|
1014
|
+
// object, as it did when every object had a transaction of its own.
|
|
1015
|
+
for (ticket, recording) in batch {
|
|
1016
|
+
let outcome = transaction
|
|
1017
|
+
.savepoint()
|
|
1018
|
+
.map_err(|error| sql("begin object admission", error))
|
|
1019
|
+
.and_then(|savepoint| {
|
|
1020
|
+
if let Some(row) = &recording.record {
|
|
1021
|
+
write_verification_on(&savepoint, row)?;
|
|
1022
|
+
}
|
|
1023
|
+
savepoint
|
|
1024
|
+
.commit()
|
|
1025
|
+
.map_err(|error| sql("record object admission", error))
|
|
1026
|
+
});
|
|
1027
|
+
match outcome {
|
|
1028
|
+
Ok(()) => written.push((ticket, recording.record)),
|
|
1029
|
+
Err(error) => results.push((ticket, Err(error))),
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
match transaction
|
|
1033
|
+
.commit()
|
|
1034
|
+
.map_err(|error| sql("commit blob admission", error))
|
|
1035
|
+
{
|
|
1036
|
+
Ok(()) => {
|
|
1037
|
+
let trust = Trust::live(&self.path);
|
|
1038
|
+
for (ticket, record) in written {
|
|
1039
|
+
if let (Some(row), Some(trust)) = (&record, &trust) {
|
|
1040
|
+
trust.remember(row);
|
|
1041
|
+
}
|
|
1042
|
+
results.push((ticket, Ok(())));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
Err(error) => {
|
|
1046
|
+
results.extend(
|
|
1047
|
+
written
|
|
1048
|
+
.into_iter()
|
|
1049
|
+
.map(|(ticket, _)| (ticket, Err(error.clone()))),
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
results
|
|
1054
|
+
}
|
|
1055
|
+
|
|
944
1056
|
pub fn held_keys(&self) -> Result<Vec<crate::storage::HeldKey>> {
|
|
945
1057
|
let connection = self.connection()?;
|
|
946
1058
|
let mut statement = connection
|
|
@@ -1084,7 +1196,7 @@ impl Catalog {
|
|
|
1084
1196
|
}
|
|
1085
1197
|
}
|
|
1086
1198
|
|
|
1087
|
-
fn
|
|
1199
|
+
fn held_admissions(path: &Path) -> Arc<Mutex<()>> {
|
|
1088
1200
|
static TURNS: OnceLock<Mutex<HashMap<PathBuf, Arc<Mutex<()>>>>> = OnceLock::new();
|
|
1089
1201
|
TURNS
|
|
1090
1202
|
.get_or_init(Default::default)
|
|
@@ -1095,6 +1207,73 @@ fn admissions(path: &Path) -> Arc<Mutex<()>> {
|
|
|
1095
1207
|
.clone()
|
|
1096
1208
|
}
|
|
1097
1209
|
|
|
1210
|
+
/// One published object's catalog rows, handed to the admission group.
|
|
1211
|
+
pub(crate) struct Recording {
|
|
1212
|
+
/// The verification row, when this call installed the object.
|
|
1213
|
+
pub record: Option<crate::store::VRecord>,
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
#[derive(Default)]
|
|
1217
|
+
struct AdmissionGroup {
|
|
1218
|
+
state: Mutex<GroupState>,
|
|
1219
|
+
turned: std::sync::Condvar,
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
#[derive(Default)]
|
|
1223
|
+
struct GroupState {
|
|
1224
|
+
next: u64,
|
|
1225
|
+
leading: bool,
|
|
1226
|
+
queue: Vec<(u64, Recording)>,
|
|
1227
|
+
done: HashMap<u64, Result<()>>,
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
fn admission_group(path: &Path) -> Arc<AdmissionGroup> {
|
|
1231
|
+
static GROUPS: OnceLock<Mutex<HashMap<PathBuf, Arc<AdmissionGroup>>>> = OnceLock::new();
|
|
1232
|
+
GROUPS
|
|
1233
|
+
.get_or_init(Default::default)
|
|
1234
|
+
.lock()
|
|
1235
|
+
.unwrap_or_else(|error| error.into_inner())
|
|
1236
|
+
.entry(path.to_path_buf())
|
|
1237
|
+
.or_default()
|
|
1238
|
+
.clone()
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/// A leader hands its batch's results back however it leaves, so a panic inside one batch
|
|
1242
|
+
/// cannot strand the admissions queued behind it.
|
|
1243
|
+
struct Lead<'a> {
|
|
1244
|
+
group: &'a AdmissionGroup,
|
|
1245
|
+
tickets: Vec<u64>,
|
|
1246
|
+
results: Vec<(u64, Result<()>)>,
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
impl Drop for Lead<'_> {
|
|
1250
|
+
fn drop(&mut self) {
|
|
1251
|
+
let mut state = self
|
|
1252
|
+
.group
|
|
1253
|
+
.state
|
|
1254
|
+
.lock()
|
|
1255
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
1256
|
+
state.leading = false;
|
|
1257
|
+
let mut answered = std::collections::HashSet::new();
|
|
1258
|
+
for (ticket, result) in self.results.drain(..) {
|
|
1259
|
+
answered.insert(ticket);
|
|
1260
|
+
state.done.insert(ticket, result);
|
|
1261
|
+
}
|
|
1262
|
+
for ticket in &self.tickets {
|
|
1263
|
+
if !answered.contains(ticket) {
|
|
1264
|
+
state.done.insert(
|
|
1265
|
+
*ticket,
|
|
1266
|
+
Err(Refusal {
|
|
1267
|
+
code: Code::IO_FAILED,
|
|
1268
|
+
detail: "the admission batch holding this object did not finish".into(),
|
|
1269
|
+
}),
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
self.group.turned.notify_all();
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1098
1277
|
fn hold_on(connection: &Connection, operation: &str, key: &str, length: u64) -> Result<()> {
|
|
1099
1278
|
let kind = if key.starts_with("blobs/") {
|
|
1100
1279
|
"blob"
|
|
@@ -418,6 +418,28 @@ impl Walk {
|
|
|
418
418
|
}
|
|
419
419
|
Ok(())
|
|
420
420
|
}
|
|
421
|
+
|
|
422
|
+
/// Every reached object present, or held by a live source operation's publication
|
|
423
|
+
/// custodian. A complete checkpoint for publication, never a readable one.
|
|
424
|
+
pub fn require_held(
|
|
425
|
+
&self,
|
|
426
|
+
store: &Store,
|
|
427
|
+
custodied: &std::collections::BTreeSet<String>,
|
|
428
|
+
) -> Result<()> {
|
|
429
|
+
for r in self.distinct() {
|
|
430
|
+
if !store.contains(&r.sha256) && !custodied.contains(&r.sha256) {
|
|
431
|
+
return refuse(
|
|
432
|
+
Code::OBJECT_ABSENT,
|
|
433
|
+
format!(
|
|
434
|
+
"{} is committed by this manifest but neither resident nor held by \
|
|
435
|
+
a live publication custodian",
|
|
436
|
+
r.id()
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
Ok(())
|
|
442
|
+
}
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
/// Manifest → CozyTensorsHeader → contained tensor rules → storage-part object runs.
|
|
@@ -351,7 +351,7 @@ fn missing_corrupt_and_changed_work_cannot_be_reused() {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
#[test]
|
|
354
|
-
fn
|
|
354
|
+
fn recovery_discards_old_writer_authority_and_malformed_part_geometry() {
|
|
355
355
|
let (store, meta, id, declaration) = fixture("authority");
|
|
356
356
|
let writer = begin(&store, &meta, &id, 9, declaration.clone(), None).unwrap();
|
|
357
357
|
data(&store, &meta, &id, 9, 0x31);
|
|
@@ -359,7 +359,8 @@ fn recovery_refuses_old_writer_authority_and_malformed_part_geometry() {
|
|
|
359
359
|
let plan = progress_plan(&row);
|
|
360
360
|
let mut geometry = progress_row(&row);
|
|
361
361
|
geometry.added_parts[0].part.shape = vec![16, 31];
|
|
362
|
-
|
|
362
|
+
let mut writers = vec![writer];
|
|
363
|
+
for (epoch, snapshot) in [(10, row), (11, geometry)] {
|
|
363
364
|
let bytes = crate::canon::write(&snapshot.to_value());
|
|
364
365
|
let head = crate::durability::checkpoint_bytes(
|
|
365
366
|
&store,
|
|
@@ -378,11 +379,20 @@ fn recovery_refuses_old_writer_authority_and_malformed_part_geometry() {
|
|
|
378
379
|
.head
|
|
379
380
|
.head
|
|
380
381
|
.unwrap();
|
|
381
|
-
|
|
382
|
-
|
|
382
|
+
// The checkpoint cannot be continued: it is discarded, nothing of it is imported,
|
|
383
|
+
// and the new writer proceeds from the work this Store already holds.
|
|
384
|
+
writers.push(begin(&store, &meta, &id, epoch, declaration.clone(), Some(&head)).unwrap());
|
|
385
|
+
check_writer(&meta, &id, epoch).unwrap();
|
|
386
|
+
let current = meta.derived_rows().unwrap().remove(0);
|
|
387
|
+
assert!(current
|
|
388
|
+
.added_parts
|
|
389
|
+
.iter()
|
|
390
|
+
.all(|added| added.part.shape != vec![16, 31]));
|
|
391
|
+
}
|
|
392
|
+
fence(&meta, &id, 11).unwrap();
|
|
393
|
+
for writer in writers {
|
|
394
|
+
release(&meta, writer);
|
|
383
395
|
}
|
|
384
|
-
fence(&meta, &id, 9).unwrap();
|
|
385
|
-
release(&meta, writer);
|
|
386
396
|
abandon(&store, &meta, &id).unwrap();
|
|
387
397
|
std::fs::remove_dir_all(store.root()).unwrap();
|
|
388
398
|
}
|
|
@@ -489,7 +499,7 @@ fn source_identity_is_locked_and_source_closure_stays_rooted_after_writer_death(
|
|
|
489
499
|
}
|
|
490
500
|
|
|
491
501
|
#[test]
|
|
492
|
-
fn
|
|
502
|
+
fn wrong_document_lengths_refuse_to_validate_and_are_discarded_on_resume() {
|
|
493
503
|
let (store, meta, id, declaration) = fixture("lengths");
|
|
494
504
|
let writer = begin(&store, &meta, &id, 1, declaration.clone(), None).unwrap();
|
|
495
505
|
data(&store, &meta, &id, 1, 0x31);
|
|
@@ -528,16 +538,13 @@ fn wrong_document_lengths_refuse_before_parsing_or_importing_work() {
|
|
|
528
538
|
.code,
|
|
529
539
|
Code::LENGTH_MISMATCH
|
|
530
540
|
);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
Code::LENGTH_MISMATCH
|
|
537
|
-
);
|
|
538
|
-
check_writer(&meta, &id, 1).unwrap();
|
|
539
|
-
fence(&meta, &id, 1).unwrap();
|
|
541
|
+
// A forged checkpoint is discarded, not imported; this begin then meets the live writer.
|
|
542
|
+
// Resuming from it discards it instead of refusing: the next writer proceeds.
|
|
543
|
+
let next = begin(&store, &meta, &id, 2, declaration, Some(&forged)).unwrap();
|
|
544
|
+
check_writer(&meta, &id, 2).unwrap();
|
|
545
|
+
fence(&meta, &id, 2).unwrap();
|
|
540
546
|
release(&meta, writer);
|
|
547
|
+
release(&meta, next);
|
|
541
548
|
abandon(&store, &meta, &id).unwrap();
|
|
542
549
|
std::fs::remove_dir_all(store.root()).unwrap();
|
|
543
550
|
}
|
|
@@ -1601,6 +1601,9 @@ fn require_source_roots(store: &Store, declaration: &Declaration) -> Result<()>
|
|
|
1601
1601
|
}
|
|
1602
1602
|
|
|
1603
1603
|
fn load_sources(store: &Store, declaration: &Declaration) -> Result<Vec<SourceData>> {
|
|
1604
|
+
// A prepared model whose output a live source operation's publication custodian holds
|
|
1605
|
+
// derives by reference without its bytes; the result is publishable, never readable.
|
|
1606
|
+
let custodied = crate::ingest::custody::live(store.root())?;
|
|
1604
1607
|
let mut out = Vec::new();
|
|
1605
1608
|
for declared in &declaration.sources {
|
|
1606
1609
|
let manifest = checkpoint::load_manifest(store, &declared.manifest)?;
|
|
@@ -1625,11 +1628,13 @@ fn load_sources(store: &Store, declaration: &Declaration) -> Result<Vec<SourceDa
|
|
|
1625
1628
|
let closure = checkpoint::load_closure(store, &header)?;
|
|
1626
1629
|
header.validate(&closure)?;
|
|
1627
1630
|
let walk = checkpoint::walk_cozytensors(store, &manifest)?;
|
|
1628
|
-
walk.
|
|
1631
|
+
walk.require_held(store, &custodied)?;
|
|
1629
1632
|
// This is the zero-read/zero-hash inherit gate: the verification record proves the
|
|
1630
1633
|
// exact installed inode/length/generation without opening or hashing payload bytes.
|
|
1631
1634
|
for reached in &walk.objects {
|
|
1632
|
-
if reached.kind == "part" || reached.kind == "config"
|
|
1635
|
+
if (reached.kind == "part" || reached.kind == "config")
|
|
1636
|
+
&& !custodied.contains(&reached.obj.sha256)
|
|
1637
|
+
{
|
|
1633
1638
|
require_payload_record(store, &reached.obj, reached.kind)?;
|
|
1634
1639
|
}
|
|
1635
1640
|
}
|
|
@@ -2308,9 +2313,35 @@ pub fn begin(
|
|
|
2308
2313
|
release_guards(meta, Some(writer_hold), Vec::new());
|
|
2309
2314
|
return Err(error);
|
|
2310
2315
|
}
|
|
2316
|
+
// Pin every resident source object. A custodied object has no local bytes to pin: a
|
|
2317
|
+
// derivation over it carries it by reference and never reads it.
|
|
2318
|
+
let custodied = match crate::ingest::custody::live(store.root()) {
|
|
2319
|
+
Ok(custodied) => custodied,
|
|
2320
|
+
Err(error) => {
|
|
2321
|
+
release_guards(meta, Some(writer_hold), Vec::new());
|
|
2322
|
+
return Err(error);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2311
2325
|
let mut source_leases = Vec::new();
|
|
2312
2326
|
for source in &sources {
|
|
2313
|
-
|
|
2327
|
+
let pinned = if custodied.is_empty() {
|
|
2328
|
+
read::acquire_cozytensors(store, meta, &source.manifest)
|
|
2329
|
+
} else {
|
|
2330
|
+
checkpoint::walk_cozytensors(store, &source.manifest).and_then(|walk| {
|
|
2331
|
+
let id = source.manifest.manifest_id();
|
|
2332
|
+
read::acquire(
|
|
2333
|
+
store,
|
|
2334
|
+
meta,
|
|
2335
|
+
id.strip_prefix("sha256:").unwrap_or(&id),
|
|
2336
|
+
walk.distinct()
|
|
2337
|
+
.into_iter()
|
|
2338
|
+
.filter(|object| !custodied.contains(&object.sha256))
|
|
2339
|
+
.cloned()
|
|
2340
|
+
.collect(),
|
|
2341
|
+
)
|
|
2342
|
+
})
|
|
2343
|
+
};
|
|
2344
|
+
match pinned {
|
|
2314
2345
|
Ok((lease, receipts)) if receipts.0.is_empty() => {
|
|
2315
2346
|
source_leases.push((source.fact.alias.clone(), lease));
|
|
2316
2347
|
}
|
|
@@ -2342,15 +2373,22 @@ pub fn begin(
|
|
|
2342
2373
|
})
|
|
2343
2374
|
.collect();
|
|
2344
2375
|
|
|
2376
|
+
// A checkpoint this work cannot continue (another plan or work context, or no longer
|
|
2377
|
+
// whole) is discarded: the work re-derives what it lost instead of refusing.
|
|
2378
|
+
let mut checkpoint = checkpoint;
|
|
2345
2379
|
let incoming = match checkpoint
|
|
2346
2380
|
.map(|head| read_progress(store, head, &transaction, &declaration_text, &source_facts))
|
|
2347
|
-
.transpose()
|
|
2348
2381
|
{
|
|
2349
|
-
|
|
2350
|
-
|
|
2382
|
+
None => None,
|
|
2383
|
+
Some(Ok(row)) => Some(row),
|
|
2384
|
+
Some(Err(error)) if error.code == Code::IO_FAILED => {
|
|
2351
2385
|
release_guards(meta, Some(writer_hold), source_leases);
|
|
2352
2386
|
return Err(error);
|
|
2353
2387
|
}
|
|
2388
|
+
Some(Err(_)) => {
|
|
2389
|
+
checkpoint = None;
|
|
2390
|
+
None
|
|
2391
|
+
}
|
|
2354
2392
|
};
|
|
2355
2393
|
// Byte verification can update the verification catalog. Keep it outside the SQL
|
|
2356
2394
|
// writer transaction, then compare the observed row before assigning this epoch.
|
|
@@ -3269,7 +3307,7 @@ pub fn commit(
|
|
|
3269
3307
|
let manifest_ref = store.put_manifest(&manifest)?.obj;
|
|
3270
3308
|
store.read_manifest(&manifest_ref)?;
|
|
3271
3309
|
let walk = checkpoint::walk(store, &manifest)?;
|
|
3272
|
-
walk.
|
|
3310
|
+
walk.require_held(store, &crate::ingest::custody::live(store.root())?)?;
|
|
3273
3311
|
let (inherited_payload_objects, inherited_payload_bytes) =
|
|
3274
3312
|
inherit_observation(&header, &row_before)?;
|
|
3275
3313
|
|
|
@@ -289,6 +289,23 @@ pub fn local_chain(store: &Store, head: &ObjectRef, plan: &str) -> Result<Set> {
|
|
|
289
289
|
fold(&links, plan)
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
/// A supplied chain this conversion can continue, or `None` when it was written under another
|
|
293
|
+
/// plan or slot or is no longer whole in this Store. Such a chain is discarded and the work
|
|
294
|
+
/// reconverts into a new chain: losing a checkpoint costs time, never a refusal.
|
|
295
|
+
pub fn continuable(
|
|
296
|
+
store: &Store,
|
|
297
|
+
head: &ObjectRef,
|
|
298
|
+
plan: &str,
|
|
299
|
+
chain: &str,
|
|
300
|
+
) -> Result<Option<Set>> {
|
|
301
|
+
match local_chain(store, head, plan) {
|
|
302
|
+
Ok(set) if set.operation == chain => Ok(Some(set)),
|
|
303
|
+
Ok(_) => Ok(None),
|
|
304
|
+
Err(error) if error.code == Code::IO_FAILED => Err(error),
|
|
305
|
+
Err(_) => Ok(None),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
292
309
|
/// Export locally complete progress. This does not assert remote durability: the RecordOwner
|
|
293
310
|
/// publishes the bounded chain and advances its durable pointer only after upload acceptance.
|
|
294
311
|
/// NFS is not consulted and cannot delay this checkpoint.
|
|
@@ -108,8 +108,12 @@ pub fn session_holds(store: &Store) -> Result<(Vec<HeldKey>, Vec<String>)> {
|
|
|
108
108
|
let mut sessions = Vec::new();
|
|
109
109
|
for session in tx::sessions(store) {
|
|
110
110
|
let root = tx::read_session(store.root(), &session)?;
|
|
111
|
-
|
|
111
|
+
// An object the operation's publication custodian already holds is not in flight:
|
|
112
|
+
// the journal stops naming it, so the local copy is ordinary unreferenced bytes.
|
|
113
|
+
let custody = crate::ingest::custody::read_session(store.root(), &session)?;
|
|
114
|
+
let mut journalled =
|
|
112
115
|
crate::ingest::journal::session_objects(&tx::candidate_dir(store.root(), &session))?;
|
|
116
|
+
journalled.retain(|object| !custody.contains(object));
|
|
113
117
|
if root.candidates.is_empty() && journalled.is_empty() {
|
|
114
118
|
continue;
|
|
115
119
|
}
|