tensorfs 0.3.45__tar.gz → 0.3.47__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.45 → tensorfs-0.3.47}/Cargo.lock +2 -2
- {tensorfs-0.3.45 → tensorfs-0.3.47}/Cargo.toml +1 -1
- {tensorfs-0.3.45 → tensorfs-0.3.47}/PKG-INFO +1 -1
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/Cargo.toml +1 -1
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/asset_update.rs +111 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/http.rs +84 -14
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/ledger.rs +20 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/pull.rs +8 -2
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/tests.rs +331 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-py/src/store.rs +32 -3
- {tensorfs-0.3.45 → tensorfs-0.3.47}/pyproject.toml +1 -1
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/_ext.pyi +9 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/_derived_channel.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/_source_channel.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.45 → tensorfs-0.3.47}/python/tensorfs/py.typed +0 -0
|
@@ -421,7 +421,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
421
421
|
|
|
422
422
|
[[package]]
|
|
423
423
|
name = "tensorfs-core"
|
|
424
|
-
version = "0.3.
|
|
424
|
+
version = "0.3.47"
|
|
425
425
|
dependencies = [
|
|
426
426
|
"fs2",
|
|
427
427
|
"libc",
|
|
@@ -435,7 +435,7 @@ dependencies = [
|
|
|
435
435
|
|
|
436
436
|
[[package]]
|
|
437
437
|
name = "tensorfs-py"
|
|
438
|
-
version = "0.3.
|
|
438
|
+
version = "0.3.47"
|
|
439
439
|
dependencies = [
|
|
440
440
|
"pyo3",
|
|
441
441
|
"tensorfs-core",
|
|
@@ -11,7 +11,7 @@ path = "src/bin/tfs/main.rs"
|
|
|
11
11
|
fs2 = "0.4"
|
|
12
12
|
minicbor = { version = "2.3.0", default-features = false, features = ["alloc"] }
|
|
13
13
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
|
14
|
-
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std"] }
|
|
14
|
+
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std", "net", "event"] }
|
|
15
15
|
# tfs-049: the ONE transport (src/transport/). rustls is the one TLS stack, used only
|
|
16
16
|
# there; scripts/fence.py refuses it anywhere else so a second mover cannot grow.
|
|
17
17
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
|
@@ -23,6 +23,9 @@ pub enum Edit {
|
|
|
23
23
|
source_path: String,
|
|
24
24
|
media_type: String,
|
|
25
25
|
},
|
|
26
|
+
/// Inherit one already-committed asset from the source checkpoint header.
|
|
27
|
+
/// The asset declaration and segment ObjectRefs are copied verbatim; no payload is read.
|
|
28
|
+
Inherit { name: String, source_name: String },
|
|
26
29
|
/// Remove an asset by its exact header name. Missing names are harmless, making
|
|
27
30
|
/// retries deterministic.
|
|
28
31
|
Drop { name: String },
|
|
@@ -72,6 +75,7 @@ pub fn update(
|
|
|
72
75
|
detail: "asset update source has no CozyTensors header".into(),
|
|
73
76
|
})?;
|
|
74
77
|
let mut header = checkpoint::load_header(store, header_ref)?;
|
|
78
|
+
let source_assets: BTreeMap<String, Asset> = header.assets.iter().cloned().collect();
|
|
75
79
|
let closure = checkpoint::load_closure(store, &header)?;
|
|
76
80
|
|
|
77
81
|
let mut assets: BTreeMap<String, Asset> = header.assets.drain(..).collect();
|
|
@@ -80,6 +84,13 @@ pub fn update(
|
|
|
80
84
|
Edit::Drop { name } => {
|
|
81
85
|
assets.remove(name);
|
|
82
86
|
}
|
|
87
|
+
Edit::Inherit { name, source_name } => {
|
|
88
|
+
let asset = source_assets.get(source_name).ok_or_else(|| Refusal {
|
|
89
|
+
code: Code::OBJECT_ABSENT,
|
|
90
|
+
detail: format!("source asset {source_name:?} is absent"),
|
|
91
|
+
})?;
|
|
92
|
+
assets.insert(name.clone(), asset.clone());
|
|
93
|
+
}
|
|
83
94
|
Edit::Copy {
|
|
84
95
|
name,
|
|
85
96
|
source_path,
|
|
@@ -129,6 +140,47 @@ pub fn update(
|
|
|
129
140
|
})
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
/// Inherit selected runtime assets from one exact source checkpoint into another
|
|
144
|
+
/// metadata-only derived checkpoint. Tensor and asset payloads are never opened.
|
|
145
|
+
pub fn inherit(
|
|
146
|
+
store: &Store,
|
|
147
|
+
target_manifest: &ObjectRef,
|
|
148
|
+
source_manifest: &ObjectRef,
|
|
149
|
+
names: &[String],
|
|
150
|
+
) -> Result<ResultSnapshot> {
|
|
151
|
+
let target = checkpoint::load_manifest(store, target_manifest)?;
|
|
152
|
+
let target_header_ref = target.header().ok_or_else(|| Refusal {
|
|
153
|
+
code: Code::ATTACHMENT_CARDINALITY,
|
|
154
|
+
detail: "asset inheritance target has no CozyTensors header".into(),
|
|
155
|
+
})?;
|
|
156
|
+
let source = checkpoint::load_manifest(store, source_manifest)?;
|
|
157
|
+
let source_header_ref = source.header().ok_or_else(|| Refusal {
|
|
158
|
+
code: Code::ATTACHMENT_CARDINALITY,
|
|
159
|
+
detail: "asset inheritance source has no CozyTensors header".into(),
|
|
160
|
+
})?;
|
|
161
|
+
let mut header = checkpoint::load_header(store, target_header_ref)?;
|
|
162
|
+
let source_header = checkpoint::load_header(store, source_header_ref)?;
|
|
163
|
+
let target_closure = checkpoint::load_closure(store, &header)?;
|
|
164
|
+
let source_assets: BTreeMap<String, Asset> = source_header.assets.into_iter().collect();
|
|
165
|
+
let mut assets: BTreeMap<String, Asset> = header.assets.drain(..).collect();
|
|
166
|
+
for name in names {
|
|
167
|
+
let asset = source_assets.get(name).ok_or_else(|| Refusal {
|
|
168
|
+
code: Code::OBJECT_ABSENT,
|
|
169
|
+
detail: format!("source asset {name:?} is absent"),
|
|
170
|
+
})?;
|
|
171
|
+
assets.insert(name.clone(), asset.clone());
|
|
172
|
+
}
|
|
173
|
+
header.assets = assets.into_iter().collect();
|
|
174
|
+
header.validate(&target_closure)?;
|
|
175
|
+
let header_object = checkpoint::put_doc(store, &header)?;
|
|
176
|
+
let manifest = checkpoint::build_manifest(&header, &header_object, &target_closure)?;
|
|
177
|
+
let manifest_object = store.put_manifest(&manifest)?.obj;
|
|
178
|
+
Ok(ResultSnapshot {
|
|
179
|
+
header: header_object,
|
|
180
|
+
manifest: manifest_object,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
132
184
|
#[cfg(test)]
|
|
133
185
|
mod tests {
|
|
134
186
|
use super::*;
|
|
@@ -290,4 +342,63 @@ mod tests {
|
|
|
290
342
|
assert_eq!(one.manifest, two.manifest);
|
|
291
343
|
std::fs::remove_dir_all(root).unwrap();
|
|
292
344
|
}
|
|
345
|
+
|
|
346
|
+
#[test]
|
|
347
|
+
fn inherits_existing_asset_refs_without_payload_reads() {
|
|
348
|
+
let root = temp();
|
|
349
|
+
let store = Store::init(&root).unwrap();
|
|
350
|
+
let spec = registry::seeds().into_iter().next().unwrap().spec;
|
|
351
|
+
let payload = put(&store, b"tokenizer");
|
|
352
|
+
let header = Header {
|
|
353
|
+
configs: vec![],
|
|
354
|
+
assets: vec![(
|
|
355
|
+
"tokenizer/vocab.json".into(),
|
|
356
|
+
Asset {
|
|
357
|
+
logical_sha256: payload.sha256.clone(),
|
|
358
|
+
logical_length: payload.length,
|
|
359
|
+
media_type: "application/json".into(),
|
|
360
|
+
segments: vec![payload.clone()],
|
|
361
|
+
},
|
|
362
|
+
)],
|
|
363
|
+
encodings: vec![spec.clone()],
|
|
364
|
+
components: vec![(
|
|
365
|
+
"model".into(),
|
|
366
|
+
vec![(
|
|
367
|
+
"w".into(),
|
|
368
|
+
Tensor {
|
|
369
|
+
dtype: Dtype::U8,
|
|
370
|
+
shape: vec![1],
|
|
371
|
+
encoding: spec.object_id(),
|
|
372
|
+
parts: vec![(
|
|
373
|
+
"value".into(),
|
|
374
|
+
Part {
|
|
375
|
+
dtype: Dtype::U8,
|
|
376
|
+
shape: vec![1],
|
|
377
|
+
body: Body::Inline(vec![0]),
|
|
378
|
+
},
|
|
379
|
+
)],
|
|
380
|
+
},
|
|
381
|
+
)],
|
|
382
|
+
)],
|
|
383
|
+
};
|
|
384
|
+
let href = put(&store, &header.canonical_bytes().unwrap());
|
|
385
|
+
let source = Draft {
|
|
386
|
+
entries: vec![("model.cozytensors".into(), Entry::CozyTensors(href))],
|
|
387
|
+
}
|
|
388
|
+
.seal()
|
|
389
|
+
.unwrap();
|
|
390
|
+
let source_ref = store.put_manifest(&source).unwrap().obj;
|
|
391
|
+
let result = update(
|
|
392
|
+
&store,
|
|
393
|
+
&source_ref,
|
|
394
|
+
&[Edit::Inherit {
|
|
395
|
+
name: "tokenizer/vocab.json".into(),
|
|
396
|
+
source_name: "tokenizer/vocab.json".into(),
|
|
397
|
+
}],
|
|
398
|
+
)
|
|
399
|
+
.unwrap();
|
|
400
|
+
let output = checkpoint::load_header(&store, &result.header).unwrap();
|
|
401
|
+
assert_eq!(output.assets[0].1.segments, vec![payload]);
|
|
402
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
403
|
+
}
|
|
293
404
|
}
|
|
@@ -82,6 +82,7 @@ fn transfer_verdict(
|
|
|
82
82
|
since: Instant,
|
|
83
83
|
waiting_for: &str,
|
|
84
84
|
) -> Result<()> {
|
|
85
|
+
ledger.check_running()?;
|
|
85
86
|
if judge != Judge::Transfer {
|
|
86
87
|
return Ok(());
|
|
87
88
|
}
|
|
@@ -131,6 +132,7 @@ fn transfer_verdict(
|
|
|
131
132
|
/// its own window and advances only when transport bytes actually move; slow
|
|
132
133
|
/// progress may span arbitrarily many windows without exhausting a wall budget.
|
|
133
134
|
fn stream_verdict(judge: Judge, ledger: &Ledger, advanced: Instant, phase: &str) -> Result<()> {
|
|
135
|
+
ledger.check_running()?;
|
|
134
136
|
if judge != Judge::Stream || advanced.elapsed() <= ledger.patience() {
|
|
135
137
|
return Ok(());
|
|
136
138
|
}
|
|
@@ -271,27 +273,86 @@ impl Wire {
|
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
|
|
274
|
-
fn connect(
|
|
276
|
+
fn connect(
|
|
277
|
+
checked: &CheckedUrl,
|
|
278
|
+
deadline: Deadline,
|
|
279
|
+
ledger: &Ledger,
|
|
280
|
+
judge: Judge,
|
|
281
|
+
fail: Code,
|
|
282
|
+
) -> Result<TcpStream> {
|
|
283
|
+
use rustix::event::{poll, PollFd, PollFlags, Timespec};
|
|
284
|
+
use rustix::net::{self, AddressFamily, SocketFlags, SocketType};
|
|
275
285
|
let mut last: Option<std::io::Error> = None;
|
|
276
|
-
for addr in &checked.addrs {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
'addresses: for addr in &checked.addrs {
|
|
287
|
+
ledger.check_running()?;
|
|
288
|
+
let family = if addr.is_ipv4() {
|
|
289
|
+
AddressFamily::INET
|
|
290
|
+
} else {
|
|
291
|
+
AddressFamily::INET6
|
|
280
292
|
};
|
|
281
|
-
match
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
293
|
+
let socket = match net::socket_with(
|
|
294
|
+
family,
|
|
295
|
+
SocketType::STREAM,
|
|
296
|
+
SocketFlags::NONBLOCK | SocketFlags::CLOEXEC,
|
|
297
|
+
Some(net::ipproto::TCP),
|
|
298
|
+
) {
|
|
299
|
+
Ok(socket) => socket,
|
|
300
|
+
Err(error) => {
|
|
301
|
+
last = Some(error.into());
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
let since = Instant::now();
|
|
306
|
+
match net::connect(&socket, addr) {
|
|
307
|
+
Ok(()) => {}
|
|
308
|
+
Err(rustix::io::Errno::INPROGRESS) => loop {
|
|
309
|
+
// Poll one pending connection instead of restarting it at each
|
|
310
|
+
// sample. Cancellation and existing silence rules remain active
|
|
311
|
+
// without imposing a new total connection deadline.
|
|
312
|
+
transfer_verdict(judge, ledger, since, "a TCP connection")?;
|
|
313
|
+
stream_verdict(judge, ledger, since, "TCP connect")?;
|
|
314
|
+
let tick = deadline
|
|
315
|
+
.remaining()?
|
|
316
|
+
.map_or(ledger.sample(), |left| left.min(ledger.sample()));
|
|
317
|
+
let timeout = Timespec {
|
|
318
|
+
tv_sec: tick.as_secs() as _,
|
|
319
|
+
tv_nsec: tick.subsec_nanos() as _,
|
|
320
|
+
};
|
|
321
|
+
let mut descriptors = [PollFd::new(&socket, PollFlags::OUT)];
|
|
322
|
+
match poll(&mut descriptors, Some(&timeout)) {
|
|
323
|
+
Ok(0) | Err(rustix::io::Errno::INTR) => continue,
|
|
324
|
+
Ok(_) => match net::sockopt::socket_error(&socket) {
|
|
325
|
+
Ok(Ok(())) => break,
|
|
326
|
+
Ok(Err(error)) | Err(error) => {
|
|
327
|
+
last = Some(error.into());
|
|
328
|
+
continue 'addresses;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
Err(error) => {
|
|
332
|
+
last = Some(error.into());
|
|
333
|
+
continue 'addresses;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
Err(error) => {
|
|
338
|
+
last = Some(error.into());
|
|
339
|
+
continue;
|
|
285
340
|
}
|
|
286
|
-
Err(error) => last = Some(error),
|
|
287
341
|
}
|
|
342
|
+
let socket: TcpStream = socket.into();
|
|
343
|
+
socket.set_nonblocking(false).map_err(|error| Refusal {
|
|
344
|
+
code: fail,
|
|
345
|
+
detail: format!("configure connected socket: {error}"),
|
|
346
|
+
})?;
|
|
347
|
+
let _ = socket.set_nodelay(true);
|
|
348
|
+
return Ok(socket);
|
|
288
349
|
}
|
|
289
350
|
refuse(
|
|
290
351
|
fail,
|
|
291
352
|
format!(
|
|
292
353
|
"{}: no checked address accepted a connection ({})",
|
|
293
354
|
checked.host,
|
|
294
|
-
last.map(|
|
|
355
|
+
last.map(|error| error.to_string()).unwrap_or_default()
|
|
295
356
|
),
|
|
296
357
|
)
|
|
297
358
|
}
|
|
@@ -425,7 +486,11 @@ impl BodyReader<'_> {
|
|
|
425
486
|
/// One raw read off the wire under the sampling rule: the socket timeout is the
|
|
426
487
|
/// RESOLUTION; the deadline and (when judged) the measured patience carry the verdict.
|
|
427
488
|
fn step(&mut self, buf: &mut [u8]) -> Result<usize> {
|
|
489
|
+
// Time spent by the consumer hashing or writing the prior chunk is not
|
|
490
|
+
// network silence. This wait starts when it asks for another chunk.
|
|
491
|
+
self.advanced = Instant::now();
|
|
428
492
|
loop {
|
|
493
|
+
self.ledger.check_running()?;
|
|
429
494
|
let tick = match self.deadline.remaining()? {
|
|
430
495
|
Some(left) => left.min(self.ledger.sample()),
|
|
431
496
|
None => self.ledger.sample(),
|
|
@@ -435,7 +500,9 @@ impl BodyReader<'_> {
|
|
|
435
500
|
Ok(n) => {
|
|
436
501
|
if n > 0 {
|
|
437
502
|
let now = Instant::now();
|
|
438
|
-
self.
|
|
503
|
+
if self.judge == Judge::Stream {
|
|
504
|
+
self.ledger.taught(now - self.advanced);
|
|
505
|
+
}
|
|
439
506
|
self.advanced = now;
|
|
440
507
|
}
|
|
441
508
|
return Ok(n);
|
|
@@ -645,8 +712,9 @@ pub fn request<'l>(
|
|
|
645
712
|
write_silence: Option<Duration>,
|
|
646
713
|
fail: Code,
|
|
647
714
|
) -> Result<Response<'l>> {
|
|
715
|
+
ledger.check_running()?;
|
|
648
716
|
let opened = Instant::now();
|
|
649
|
-
let sock = connect(checked, deadline, fail)?;
|
|
717
|
+
let sock = connect(checked, deadline, ledger, judge, fail)?;
|
|
650
718
|
let mut wire = if checked.https {
|
|
651
719
|
let name = ServerName::try_from(checked.host.clone()).map_err(|_| Refusal {
|
|
652
720
|
code: Code::SOURCE_NOT_ALLOWED,
|
|
@@ -697,7 +765,9 @@ pub fn request<'l>(
|
|
|
697
765
|
|
|
698
766
|
let (status, response_headers, spill) =
|
|
699
767
|
read_head(&mut wire, deadline, ledger, judge, opened, fail)?;
|
|
700
|
-
|
|
768
|
+
if judge == Judge::Stream {
|
|
769
|
+
ledger.taught(opened.elapsed());
|
|
770
|
+
}
|
|
701
771
|
// A HEAD THAT ARRIVED IS PROGRESS. Without this, the second page of a paginated closure
|
|
702
772
|
// would be judged from the start of the first, and a pull whose hub is merely slow
|
|
703
773
|
// would condemn its own second call for the first call's honest latency.
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
//! paid H100s sat at exactly zero for fifteen minutes with neither plane complaining.
|
|
32
32
|
|
|
33
33
|
use crate::err::{refuse, Code, Result};
|
|
34
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
34
35
|
use std::sync::Mutex;
|
|
35
36
|
use std::time::{Duration, Instant};
|
|
36
37
|
|
|
@@ -134,6 +135,7 @@ pub struct Still {
|
|
|
134
135
|
|
|
135
136
|
/// The one liveness ledger a pull shares across its streams.
|
|
136
137
|
pub struct Ledger {
|
|
138
|
+
cancelled: AtomicBool,
|
|
137
139
|
sample: Duration,
|
|
138
140
|
worst_gap: Mutex<Duration>,
|
|
139
141
|
progress: Mutex<Progress>,
|
|
@@ -150,6 +152,7 @@ impl Ledger {
|
|
|
150
152
|
/// in milliseconds and judge by exactly the production rule.
|
|
151
153
|
pub fn with_resolution(sample: Duration) -> Ledger {
|
|
152
154
|
Ledger {
|
|
155
|
+
cancelled: AtomicBool::new(false),
|
|
153
156
|
sample: sample.max(Duration::from_millis(1)),
|
|
154
157
|
worst_gap: Mutex::new(Duration::ZERO),
|
|
155
158
|
progress: Mutex::new(Progress {
|
|
@@ -181,6 +184,23 @@ impl Ledger {
|
|
|
181
184
|
Instant::now()
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
/// A terminal object failure stops the whole pull, including already-open
|
|
188
|
+
/// HTTP streams. Completed objects remain admitted for the next pull.
|
|
189
|
+
pub(crate) fn cancel(&self) {
|
|
190
|
+
self.cancelled.store(true, Ordering::Release);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pub(crate) fn check_running(&self) -> Result<()> {
|
|
194
|
+
if self.cancelled.load(Ordering::Acquire) {
|
|
195
|
+
refuse(
|
|
196
|
+
Code::TRANSFER_FAILED,
|
|
197
|
+
"the pull stopped after another object failed",
|
|
198
|
+
)
|
|
199
|
+
} else {
|
|
200
|
+
Ok(())
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
184
204
|
pub fn sample(&self) -> Duration {
|
|
185
205
|
self.sample
|
|
186
206
|
}
|
|
@@ -207,6 +207,7 @@ pub fn fetch_authorized(
|
|
|
207
207
|
// declared life, which is the only place that question can be answered.
|
|
208
208
|
let mut stale: Option<u16> = None;
|
|
209
209
|
for attempt in 1..=FETCH_ATTEMPTS {
|
|
210
|
+
ledger.check_running()?;
|
|
210
211
|
let last = attempt == FETCH_ATTEMPTS;
|
|
211
212
|
// A refused re-authorization must still name the origin's answer. It is the whole
|
|
212
213
|
// diagnosis: "403, and re-minting was not honest" and "403, and the hub would not
|
|
@@ -604,6 +605,7 @@ pub(super) struct Leases<'a> {
|
|
|
604
605
|
|
|
605
606
|
impl ObjectUrls for Leases<'_> {
|
|
606
607
|
fn url_for(&self, at: usize, object: &ObjectRef, stale: bool) -> Result<String> {
|
|
608
|
+
self.ledger.check_running()?;
|
|
607
609
|
if stale && !self.retire(&object.sha256)? {
|
|
608
610
|
// The refused lease was NOT spent: the hub's own declared life still had more
|
|
609
611
|
// than half of it left, so the origin's refusal is not about time and asking to
|
|
@@ -623,6 +625,7 @@ impl ObjectUrls for Leases<'_> {
|
|
|
623
625
|
return Ok(url);
|
|
624
626
|
}
|
|
625
627
|
let _minting = self.minting.lock().unwrap();
|
|
628
|
+
self.ledger.check_running()?;
|
|
626
629
|
// Another stream may have minted this window while this one waited for the right
|
|
627
630
|
// to call. Asking again before calling is what makes one window cost one call.
|
|
628
631
|
if let Some(url) = self.fresh(&object.sha256) {
|
|
@@ -773,7 +776,7 @@ pub struct WalkOutcome {
|
|
|
773
776
|
|
|
774
777
|
/// Walk `plan.wanted` with `streams` objects in flight. One is the serial walk and it
|
|
775
778
|
/// stays reachable, because a link where concurrency does not help is a real link. The
|
|
776
|
-
/// first refusal wins
|
|
779
|
+
/// first refusal wins and cancels in-flight network waits. Verified objects remain local.
|
|
777
780
|
///
|
|
778
781
|
/// **`cache` is the source order, and it is the whole reason a second pod is cheap.** The
|
|
779
782
|
/// order is local Store, then the optional mounted cache, then the origin — the Store half
|
|
@@ -885,11 +888,14 @@ pub fn fetch_wanted(
|
|
|
885
888
|
}
|
|
886
889
|
}
|
|
887
890
|
Err(refusal) => {
|
|
888
|
-
stop.store(true, Ordering::Relaxed);
|
|
889
891
|
let mut slot = first.lock().unwrap();
|
|
890
892
|
if slot.is_none() {
|
|
893
|
+
// Keep the actual failure before waking peers, so their
|
|
894
|
+
// cancellation errors cannot replace its diagnosis.
|
|
891
895
|
*slot = Some(refusal);
|
|
896
|
+
ledger.cancel();
|
|
892
897
|
}
|
|
898
|
+
stop.store(true, Ordering::Relaxed);
|
|
893
899
|
return;
|
|
894
900
|
}
|
|
895
901
|
}
|