tensorfs 0.3.37__tar.gz → 0.3.38__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.37 → tensorfs-0.3.38}/Cargo.lock +32 -2
- {tensorfs-0.3.37 → tensorfs-0.3.38}/Cargo.toml +1 -1
- {tensorfs-0.3.37 → tensorfs-0.3.38}/PKG-INFO +1 -1
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/Cargo.toml +1 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/repo_cache.rs +347 -62
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/repo_cache.rs +88 -4
- {tensorfs-0.3.37 → tensorfs-0.3.38}/pyproject.toml +1 -1
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-py/src/store.rs +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/python/tensorfs/_ext.pyi +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.37 → tensorfs-0.3.38}/python/tensorfs/py.typed +0 -0
|
@@ -48,6 +48,16 @@ version = "0.5.8"
|
|
|
48
48
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
49
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
|
50
50
|
|
|
51
|
+
[[package]]
|
|
52
|
+
name = "errno"
|
|
53
|
+
version = "0.3.14"
|
|
54
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
55
|
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
56
|
+
dependencies = [
|
|
57
|
+
"libc",
|
|
58
|
+
"windows-sys",
|
|
59
|
+
]
|
|
60
|
+
|
|
51
61
|
[[package]]
|
|
52
62
|
name = "fallible-iterator"
|
|
53
63
|
version = "0.3.0"
|
|
@@ -128,6 +138,12 @@ dependencies = [
|
|
|
128
138
|
"vcpkg",
|
|
129
139
|
]
|
|
130
140
|
|
|
141
|
+
[[package]]
|
|
142
|
+
name = "linux-raw-sys"
|
|
143
|
+
version = "0.12.1"
|
|
144
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
145
|
+
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|
146
|
+
|
|
131
147
|
[[package]]
|
|
132
148
|
name = "minicbor"
|
|
133
149
|
version = "2.3.0"
|
|
@@ -290,6 +306,19 @@ dependencies = [
|
|
|
290
306
|
"smallvec",
|
|
291
307
|
]
|
|
292
308
|
|
|
309
|
+
[[package]]
|
|
310
|
+
name = "rustix"
|
|
311
|
+
version = "1.1.4"
|
|
312
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
313
|
+
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
|
314
|
+
dependencies = [
|
|
315
|
+
"bitflags",
|
|
316
|
+
"errno",
|
|
317
|
+
"libc",
|
|
318
|
+
"linux-raw-sys",
|
|
319
|
+
"windows-sys",
|
|
320
|
+
]
|
|
321
|
+
|
|
293
322
|
[[package]]
|
|
294
323
|
name = "rustls"
|
|
295
324
|
version = "0.23.43"
|
|
@@ -392,19 +421,20 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
392
421
|
|
|
393
422
|
[[package]]
|
|
394
423
|
name = "tensorfs-core"
|
|
395
|
-
version = "0.3.
|
|
424
|
+
version = "0.3.38"
|
|
396
425
|
dependencies = [
|
|
397
426
|
"fs2",
|
|
398
427
|
"minicbor",
|
|
399
428
|
"rcgen",
|
|
400
429
|
"rusqlite",
|
|
430
|
+
"rustix",
|
|
401
431
|
"rustls",
|
|
402
432
|
"webpki-roots 0.26.11",
|
|
403
433
|
]
|
|
404
434
|
|
|
405
435
|
[[package]]
|
|
406
436
|
name = "tensorfs-py"
|
|
407
|
-
version = "0.3.
|
|
437
|
+
version = "0.3.38"
|
|
408
438
|
dependencies = [
|
|
409
439
|
"pyo3",
|
|
410
440
|
"tensorfs-core",
|
|
@@ -11,6 +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
15
|
# tfs-049: the ONE transport (src/transport/). rustls is the one TLS stack, used only
|
|
15
16
|
# there; scripts/fence.py refuses it anywhere else so a second mover cannot grow.
|
|
16
17
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
//! the caller's exact digest and length while bytes move into an ordinary local Store.
|
|
6
6
|
//! Cache failure is weather, never authority.
|
|
7
7
|
|
|
8
|
+
use std::ffi::OsStr;
|
|
8
9
|
use std::fs::{self, File, OpenOptions};
|
|
9
10
|
use std::io::{Read, Write};
|
|
10
|
-
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
|
11
|
+
use std::os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt};
|
|
12
|
+
|
|
11
13
|
use std::path::{Path, PathBuf};
|
|
12
14
|
|
|
15
|
+
use rustix::fs::{self as unix, AtFlags, Mode, OFlags};
|
|
16
|
+
|
|
13
17
|
use crate::err::{Code, Result};
|
|
14
18
|
use crate::ids::{hex64, Doc, ObjectRef};
|
|
15
19
|
use crate::limits;
|
|
@@ -241,27 +245,49 @@ impl RepoObjectCache {
|
|
|
241
245
|
progress: Option<&dyn store::Progress>,
|
|
242
246
|
) -> Result<CacheWrite> {
|
|
243
247
|
let destination = self.path(kind, &want.sha256)?;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
let directory = match cache_parent(&self.root, &destination) {
|
|
249
|
+
Ok(directory) => directory,
|
|
250
|
+
Err(_) => return Ok(CacheWrite::Unavailable),
|
|
251
|
+
};
|
|
252
|
+
let name = destination.file_name().expect("digest path has a filename");
|
|
253
|
+
let repair = match open_at(&directory, name) {
|
|
254
|
+
Ok(file) => {
|
|
255
|
+
let metadata = match file.metadata() {
|
|
256
|
+
Ok(metadata) if metadata.is_file() => metadata,
|
|
257
|
+
_ => return Ok(CacheWrite::Unavailable),
|
|
258
|
+
};
|
|
259
|
+
match verify_file(file, kind, want, progress) {
|
|
260
|
+
Ok(()) => return Ok(CacheWrite::Present),
|
|
261
|
+
Err(SoftFailure::Corrupt) => Some(metadata),
|
|
262
|
+
Err(_) => return Ok(CacheWrite::Unavailable),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
Err(SoftFailure::Missing) => None,
|
|
266
|
+
Err(_) => return Ok(CacheWrite::Unavailable),
|
|
267
|
+
};
|
|
250
268
|
let result = match kind {
|
|
251
269
|
CacheKind::Blob => match source.open_verified(&want.sha256) {
|
|
252
270
|
Ok(mut file) if file.len() == want.length => {
|
|
253
|
-
publish_stream(&mut file, &
|
|
271
|
+
publish_stream(&mut file, &directory, name, want, progress, repair.as_ref())
|
|
254
272
|
}
|
|
255
273
|
Ok(_) | Err(_) => Err(SoftFailure::Unavailable),
|
|
256
274
|
},
|
|
257
275
|
CacheKind::Manifest => match source.read_manifest(want) {
|
|
258
|
-
Ok(manifest) => publish_bytes(
|
|
276
|
+
Ok(manifest) => publish_bytes(
|
|
277
|
+
&manifest.canonical_bytes(),
|
|
278
|
+
&directory,
|
|
279
|
+
name,
|
|
280
|
+
progress,
|
|
281
|
+
repair.as_ref(),
|
|
282
|
+
),
|
|
259
283
|
Err(_) => Err(SoftFailure::Unavailable),
|
|
260
284
|
},
|
|
261
285
|
};
|
|
262
286
|
Ok(match result {
|
|
263
287
|
Ok(true) => CacheWrite::Stored,
|
|
264
|
-
Ok(false) => match
|
|
288
|
+
Ok(false) => match open_at(&directory, name)
|
|
289
|
+
.and_then(|file| verify_file(file, kind, want, progress))
|
|
290
|
+
{
|
|
265
291
|
Ok(()) => CacheWrite::Present,
|
|
266
292
|
Err(_) => CacheWrite::Unavailable,
|
|
267
293
|
},
|
|
@@ -451,80 +477,181 @@ fn read_manifest(path: &Path, want: &ObjectRef) -> std::result::Result<Manifest,
|
|
|
451
477
|
Manifest::parse(&bytes).map_err(|_| SoftFailure::Corrupt)
|
|
452
478
|
}
|
|
453
479
|
|
|
480
|
+
#[cfg(test)]
|
|
454
481
|
fn verify_path(
|
|
455
482
|
path: &Path,
|
|
456
483
|
kind: CacheKind,
|
|
457
484
|
want: &ObjectRef,
|
|
458
485
|
progress: Option<&dyn store::Progress>,
|
|
459
486
|
) -> std::result::Result<(), SoftFailure> {
|
|
487
|
+
verify_file(open_exact(path, want.length)?, kind, want, progress)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
fn verify_file(
|
|
491
|
+
mut file: File,
|
|
492
|
+
kind: CacheKind,
|
|
493
|
+
want: &ObjectRef,
|
|
494
|
+
progress: Option<&dyn store::Progress>,
|
|
495
|
+
) -> std::result::Result<(), SoftFailure> {
|
|
496
|
+
let metadata = file.metadata().map_err(|_| SoftFailure::Unavailable)?;
|
|
497
|
+
if !metadata.is_file() {
|
|
498
|
+
return Err(SoftFailure::Unavailable);
|
|
499
|
+
}
|
|
500
|
+
if metadata.len() != want.length {
|
|
501
|
+
return Err(SoftFailure::Corrupt);
|
|
502
|
+
}
|
|
460
503
|
match kind {
|
|
461
|
-
CacheKind::Blob =>
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
504
|
+
CacheKind::Blob => stream_exact(&mut file, &mut std::io::sink(), want, progress),
|
|
505
|
+
CacheKind::Manifest => {
|
|
506
|
+
if want.length > Manifest::MAX_BYTES as u64 {
|
|
507
|
+
return Err(SoftFailure::Corrupt);
|
|
508
|
+
}
|
|
509
|
+
let mut bytes = Vec::with_capacity(want.length as usize);
|
|
510
|
+
file.take(want.length + 1)
|
|
511
|
+
.read_to_end(&mut bytes)
|
|
512
|
+
.map_err(|_| SoftFailure::Unavailable)?;
|
|
513
|
+
if bytes.len() as u64 != want.length || sha256::hex_digest(&bytes) != want.sha256 {
|
|
514
|
+
return Err(SoftFailure::Corrupt);
|
|
515
|
+
}
|
|
516
|
+
Manifest::parse(&bytes)
|
|
517
|
+
.map(|_| ())
|
|
518
|
+
.map_err(|_| SoftFailure::Corrupt)
|
|
465
519
|
}
|
|
466
|
-
CacheKind::Manifest => read_manifest(path, want).map(|_| ()),
|
|
467
520
|
}
|
|
468
521
|
}
|
|
469
522
|
|
|
523
|
+
// The configured root is the caller's location authority. Pin it once, then refuse
|
|
524
|
+
// symlinks in every cache-owned fanout directory and publish relative to the pinned fd.
|
|
525
|
+
fn cache_parent(root: &Path, destination: &Path) -> std::result::Result<File, SoftFailure> {
|
|
526
|
+
fs::create_dir_all(root).map_err(|_| SoftFailure::Unavailable)?;
|
|
527
|
+
let mut directory = File::from(
|
|
528
|
+
unix::open(
|
|
529
|
+
root,
|
|
530
|
+
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
|
|
531
|
+
Mode::empty(),
|
|
532
|
+
)
|
|
533
|
+
.map_err(|_| SoftFailure::Unavailable)?,
|
|
534
|
+
);
|
|
535
|
+
let relative = destination
|
|
536
|
+
.parent()
|
|
537
|
+
.and_then(|parent| parent.strip_prefix(root).ok())
|
|
538
|
+
.ok_or(SoftFailure::Unavailable)?;
|
|
539
|
+
for component in relative.components() {
|
|
540
|
+
let std::path::Component::Normal(name) = component else {
|
|
541
|
+
return Err(SoftFailure::Unavailable);
|
|
542
|
+
};
|
|
543
|
+
match unix::mkdirat(&directory, name, Mode::from_raw_mode(0o755)) {
|
|
544
|
+
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
|
545
|
+
Err(_) => return Err(SoftFailure::Unavailable),
|
|
546
|
+
}
|
|
547
|
+
directory = File::from(
|
|
548
|
+
unix::openat(
|
|
549
|
+
&directory,
|
|
550
|
+
name,
|
|
551
|
+
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
|
|
552
|
+
Mode::empty(),
|
|
553
|
+
)
|
|
554
|
+
.map_err(|_| SoftFailure::Unavailable)?,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
Ok(directory)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
fn open_at(directory: &File, name: &OsStr) -> std::result::Result<File, SoftFailure> {
|
|
561
|
+
unix::openat(
|
|
562
|
+
directory,
|
|
563
|
+
name,
|
|
564
|
+
OFlags::RDONLY | OFlags::NOFOLLOW | OFlags::NONBLOCK | OFlags::CLOEXEC,
|
|
565
|
+
Mode::empty(),
|
|
566
|
+
)
|
|
567
|
+
.map(File::from)
|
|
568
|
+
.map_err(|error| {
|
|
569
|
+
if error == rustix::io::Errno::NOENT {
|
|
570
|
+
SoftFailure::Missing
|
|
571
|
+
} else {
|
|
572
|
+
SoftFailure::Unavailable
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
|
|
470
577
|
fn publish_bytes(
|
|
471
578
|
bytes: &[u8],
|
|
472
|
-
|
|
579
|
+
directory: &File,
|
|
580
|
+
name: &OsStr,
|
|
473
581
|
progress: Option<&dyn store::Progress>,
|
|
582
|
+
repair: Option<&fs::Metadata>,
|
|
474
583
|
) -> std::result::Result<bool, SoftFailure> {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
584
|
+
publish_stream(
|
|
585
|
+
&mut &bytes[..],
|
|
586
|
+
directory,
|
|
587
|
+
name,
|
|
588
|
+
&ObjectRef::of(bytes),
|
|
589
|
+
progress,
|
|
590
|
+
repair,
|
|
591
|
+
)
|
|
478
592
|
}
|
|
479
593
|
|
|
480
594
|
fn publish_stream(
|
|
481
595
|
source: &mut impl Read,
|
|
482
|
-
|
|
596
|
+
directory: &File,
|
|
597
|
+
name: &OsStr,
|
|
483
598
|
want: &ObjectRef,
|
|
484
599
|
progress: Option<&dyn store::Progress>,
|
|
600
|
+
repair: Option<&fs::Metadata>,
|
|
485
601
|
) -> std::result::Result<bool, SoftFailure> {
|
|
486
|
-
let
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
fs::create_dir_all(directory).map_err(|_| SoftFailure::Unavailable)?;
|
|
490
|
-
let name = destination
|
|
491
|
-
.file_name()
|
|
492
|
-
.and_then(|value| value.to_str())
|
|
493
|
-
.unwrap_or("object");
|
|
494
|
-
let temporary = directory.join(format!(
|
|
495
|
-
".{name}.{}.{}.tmp",
|
|
602
|
+
let temporary = format!(
|
|
603
|
+
".{}.{}.{}.tmp",
|
|
604
|
+
name.to_string_lossy(),
|
|
496
605
|
std::process::id(),
|
|
497
606
|
store::now_nanos_unique()
|
|
498
|
-
)
|
|
499
|
-
let mut output =
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
607
|
+
);
|
|
608
|
+
let mut output = File::from(
|
|
609
|
+
unix::openat(
|
|
610
|
+
directory,
|
|
611
|
+
temporary.as_str(),
|
|
612
|
+
OFlags::WRONLY | OFlags::CREATE | OFlags::EXCL | OFlags::NOFOLLOW | OFlags::CLOEXEC,
|
|
613
|
+
Mode::from_raw_mode(0o600),
|
|
614
|
+
)
|
|
615
|
+
.map_err(|_| SoftFailure::Unavailable)?,
|
|
616
|
+
);
|
|
617
|
+
let publish = (|| {
|
|
618
|
+
stream_exact(source, &mut output, want, progress)?;
|
|
619
|
+
output.sync_all().map_err(|_| SoftFailure::Unavailable)?;
|
|
620
|
+
output
|
|
621
|
+
.set_permissions(fs::Permissions::from_mode(0o444))
|
|
622
|
+
.map_err(|_| SoftFailure::Unavailable)?;
|
|
623
|
+
let admitted = if let Some(previous) = repair {
|
|
624
|
+
let current = unix::statat(directory, name, AtFlags::SYMLINK_NOFOLLOW)
|
|
625
|
+
.map_err(|_| SoftFailure::Unavailable)?;
|
|
626
|
+
if current.st_dev != previous.dev() || current.st_ino != previous.ino() {
|
|
627
|
+
false
|
|
628
|
+
} else {
|
|
629
|
+
// Both racing successful repairs publish exactly the same verified object.
|
|
630
|
+
// renameat replaces the directory entry, never follows a changed symlink.
|
|
631
|
+
unix::renameat(directory, temporary.as_str(), directory, name)
|
|
632
|
+
.map_err(|_| SoftFailure::Unavailable)?;
|
|
633
|
+
true
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
match unix::linkat(
|
|
637
|
+
directory,
|
|
638
|
+
temporary.as_str(),
|
|
639
|
+
directory,
|
|
640
|
+
name,
|
|
641
|
+
AtFlags::empty(),
|
|
642
|
+
) {
|
|
643
|
+
Ok(()) => true,
|
|
644
|
+
Err(rustix::io::Errno::EXIST) => false,
|
|
645
|
+
Err(_) => return Err(SoftFailure::Unavailable),
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
if admitted {
|
|
649
|
+
directory.sync_all().map_err(|_| SoftFailure::Unavailable)?;
|
|
521
650
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
Ok(admitted)
|
|
651
|
+
Ok(admitted)
|
|
652
|
+
})();
|
|
653
|
+
let _ = unix::unlinkat(directory, temporary.as_str(), AtFlags::empty());
|
|
654
|
+
publish
|
|
528
655
|
}
|
|
529
656
|
|
|
530
657
|
fn stream_exact(
|
|
@@ -1040,7 +1167,14 @@ mod backfill_tests {
|
|
|
1040
1167
|
length: object.length,
|
|
1041
1168
|
};
|
|
1042
1169
|
assert_eq!(
|
|
1043
|
-
publish_stream(
|
|
1170
|
+
publish_stream(
|
|
1171
|
+
&mut input,
|
|
1172
|
+
&cache_parent(&root, &destination).unwrap(),
|
|
1173
|
+
destination.file_name().unwrap(),
|
|
1174
|
+
&object,
|
|
1175
|
+
None,
|
|
1176
|
+
None
|
|
1177
|
+
),
|
|
1044
1178
|
Ok(true)
|
|
1045
1179
|
);
|
|
1046
1180
|
assert!(!source.contains(&object.sha256));
|
|
@@ -1077,7 +1211,14 @@ mod backfill_tests {
|
|
|
1077
1211
|
}
|
|
1078
1212
|
}
|
|
1079
1213
|
assert_eq!(
|
|
1080
|
-
publish_stream(
|
|
1214
|
+
publish_stream(
|
|
1215
|
+
&mut Fails(false),
|
|
1216
|
+
&cache_parent(&root, &destination).unwrap(),
|
|
1217
|
+
destination.file_name().unwrap(),
|
|
1218
|
+
&object,
|
|
1219
|
+
None,
|
|
1220
|
+
None
|
|
1221
|
+
),
|
|
1081
1222
|
Err(SoftFailure::Unavailable)
|
|
1082
1223
|
);
|
|
1083
1224
|
assert!(!destination.exists());
|
|
@@ -1153,7 +1294,14 @@ mod exit_wait_tests {
|
|
|
1153
1294
|
let wanted = object.clone();
|
|
1154
1295
|
let (finish, finishing) = mpsc::channel();
|
|
1155
1296
|
let copy = std::thread::spawn(move || {
|
|
1156
|
-
let result = publish_stream(
|
|
1297
|
+
let result = publish_stream(
|
|
1298
|
+
&mut Paced(&data),
|
|
1299
|
+
&cache_parent(target.parent().unwrap(), &target).unwrap(),
|
|
1300
|
+
target.file_name().unwrap(),
|
|
1301
|
+
&wanted,
|
|
1302
|
+
Some(&observed),
|
|
1303
|
+
None,
|
|
1304
|
+
);
|
|
1157
1305
|
finishing.recv().unwrap();
|
|
1158
1306
|
observed.activity.finished();
|
|
1159
1307
|
result
|
|
@@ -1185,3 +1333,140 @@ mod exit_wait_tests {
|
|
|
1185
1333
|
fs::remove_dir_all(root).unwrap();
|
|
1186
1334
|
}
|
|
1187
1335
|
}
|
|
1336
|
+
|
|
1337
|
+
#[cfg(test)]
|
|
1338
|
+
mod repair_tests {
|
|
1339
|
+
use super::*;
|
|
1340
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
1341
|
+
|
|
1342
|
+
struct Swap {
|
|
1343
|
+
once: AtomicBool,
|
|
1344
|
+
path: PathBuf,
|
|
1345
|
+
replacement: PathBuf,
|
|
1346
|
+
outside: Option<PathBuf>,
|
|
1347
|
+
}
|
|
1348
|
+
impl store::Progress for Swap {
|
|
1349
|
+
fn admitted(&self) {}
|
|
1350
|
+
fn moved(&self, _: u64) {
|
|
1351
|
+
if self.once.swap(true, Ordering::SeqCst) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if let Some(outside) = &self.outside {
|
|
1355
|
+
fs::rename(&self.path, &self.replacement).unwrap();
|
|
1356
|
+
std::os::unix::fs::symlink(outside, &self.path).unwrap();
|
|
1357
|
+
} else {
|
|
1358
|
+
fs::rename(&self.replacement, &self.path).unwrap();
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
#[test]
|
|
1364
|
+
fn publication_stays_in_its_open_directory_after_a_fanout_swap() {
|
|
1365
|
+
let root = std::env::temp_dir().join(format!(
|
|
1366
|
+
"tfs-parent-swap-{}-{}",
|
|
1367
|
+
std::process::id(),
|
|
1368
|
+
store::now_nanos_unique()
|
|
1369
|
+
));
|
|
1370
|
+
let source = Store::init(&root.join("source")).unwrap();
|
|
1371
|
+
let data = vec![9; 4096];
|
|
1372
|
+
let object = source
|
|
1373
|
+
.put_stream(&mut data.as_slice(), None, &Fault::default())
|
|
1374
|
+
.unwrap()
|
|
1375
|
+
.obj;
|
|
1376
|
+
let cache = RepoObjectCache::new(root.join("cache"));
|
|
1377
|
+
let path = cache.path(CacheKind::Blob, &object.sha256).unwrap();
|
|
1378
|
+
let outside = root.join("outside");
|
|
1379
|
+
fs::create_dir_all(&outside).unwrap();
|
|
1380
|
+
let parked = root.join("parked");
|
|
1381
|
+
let swap = Swap {
|
|
1382
|
+
once: AtomicBool::new(false),
|
|
1383
|
+
path: path.parent().unwrap().to_path_buf(),
|
|
1384
|
+
replacement: parked.clone(),
|
|
1385
|
+
outside: Some(outside.clone()),
|
|
1386
|
+
};
|
|
1387
|
+
assert_eq!(
|
|
1388
|
+
cache
|
|
1389
|
+
.backfill_observed(&source, CacheKind::Blob, &object, Some(&swap))
|
|
1390
|
+
.unwrap(),
|
|
1391
|
+
CacheWrite::Stored
|
|
1392
|
+
);
|
|
1393
|
+
assert_eq!(fs::read_dir(outside).unwrap().count(), 0);
|
|
1394
|
+
assert_eq!(
|
|
1395
|
+
fs::read(parked.join(path.file_name().unwrap())).unwrap(),
|
|
1396
|
+
data
|
|
1397
|
+
);
|
|
1398
|
+
fs::remove_dir_all(root).unwrap();
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
#[test]
|
|
1402
|
+
fn repair_adopts_a_competing_verified_final_instead_of_replacing_it() {
|
|
1403
|
+
let root = std::env::temp_dir().join(format!(
|
|
1404
|
+
"tfs-repair-winner-{}-{}",
|
|
1405
|
+
std::process::id(),
|
|
1406
|
+
store::now_nanos_unique()
|
|
1407
|
+
));
|
|
1408
|
+
let source = Store::init(&root.join("source")).unwrap();
|
|
1409
|
+
let data = vec![17; 4096];
|
|
1410
|
+
let object = source
|
|
1411
|
+
.put_stream(&mut data.as_slice(), None, &Fault::default())
|
|
1412
|
+
.unwrap()
|
|
1413
|
+
.obj;
|
|
1414
|
+
let cache = RepoObjectCache::new(root.join("cache"));
|
|
1415
|
+
let path = cache.path(CacheKind::Blob, &object.sha256).unwrap();
|
|
1416
|
+
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
1417
|
+
fs::write(&path, b"bad").unwrap();
|
|
1418
|
+
let winner = root.join("winner");
|
|
1419
|
+
fs::write(&winner, &data).unwrap();
|
|
1420
|
+
let winner_inode = fs::metadata(&winner).unwrap().ino();
|
|
1421
|
+
let swap = Swap {
|
|
1422
|
+
once: AtomicBool::new(false),
|
|
1423
|
+
path: path.clone(),
|
|
1424
|
+
replacement: winner,
|
|
1425
|
+
outside: None,
|
|
1426
|
+
};
|
|
1427
|
+
assert_eq!(
|
|
1428
|
+
cache
|
|
1429
|
+
.backfill_observed(&source, CacheKind::Blob, &object, Some(&swap))
|
|
1430
|
+
.unwrap(),
|
|
1431
|
+
CacheWrite::Present
|
|
1432
|
+
);
|
|
1433
|
+
assert_eq!(fs::metadata(&path).unwrap().ino(), winner_inode);
|
|
1434
|
+
assert_eq!(fs::read(path).unwrap(), data);
|
|
1435
|
+
fs::remove_dir_all(root).unwrap();
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
#[test]
|
|
1439
|
+
fn interrupted_repair_keeps_the_previous_final_and_removes_its_temporary() {
|
|
1440
|
+
struct Broken;
|
|
1441
|
+
impl Read for Broken {
|
|
1442
|
+
fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
|
|
1443
|
+
Err(std::io::Error::other("copy interrupted"))
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
let root = std::env::temp_dir().join(format!(
|
|
1447
|
+
"tfs-repair-interrupted-{}-{}",
|
|
1448
|
+
std::process::id(),
|
|
1449
|
+
store::now_nanos_unique()
|
|
1450
|
+
));
|
|
1451
|
+
fs::create_dir_all(&root).unwrap();
|
|
1452
|
+
let object = ObjectRef::of(b"right");
|
|
1453
|
+
let path = root.join(&object.sha256);
|
|
1454
|
+
fs::write(&path, b"wrong").unwrap();
|
|
1455
|
+
let previous = fs::metadata(&path).unwrap();
|
|
1456
|
+
let directory = cache_parent(&root, &path).unwrap();
|
|
1457
|
+
assert_eq!(
|
|
1458
|
+
publish_stream(
|
|
1459
|
+
&mut Broken,
|
|
1460
|
+
&directory,
|
|
1461
|
+
path.file_name().unwrap(),
|
|
1462
|
+
&object,
|
|
1463
|
+
None,
|
|
1464
|
+
Some(&previous)
|
|
1465
|
+
),
|
|
1466
|
+
Err(SoftFailure::Unavailable)
|
|
1467
|
+
);
|
|
1468
|
+
assert_eq!(fs::read(&path).unwrap(), b"wrong");
|
|
1469
|
+
assert_eq!(fs::read_dir(&root).unwrap().count(), 1);
|
|
1470
|
+
fs::remove_dir_all(root).unwrap();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
@@ -5,7 +5,7 @@ use std::sync::{Arc, Barrier};
|
|
|
5
5
|
use std::thread;
|
|
6
6
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
7
7
|
|
|
8
|
-
use tensorfs_core::ids::ObjectRef;
|
|
8
|
+
use tensorfs_core::ids::{Doc, ObjectRef};
|
|
9
9
|
use tensorfs_core::manifest::Manifest;
|
|
10
10
|
use tensorfs_core::repo_cache::{CacheKind, CacheRead, CacheWrite, RepoObjectCache};
|
|
11
11
|
use tensorfs_core::store::{Fault, Store};
|
|
@@ -105,6 +105,27 @@ fn blobs_and_manifests_round_trip_through_normal_store_admission() {
|
|
|
105
105
|
);
|
|
106
106
|
assert_eq!(destination.read_manifest(&manifest_ref).unwrap(), manifest);
|
|
107
107
|
|
|
108
|
+
// Manifest repairs obey the same complete-object publication rule as blobs.
|
|
109
|
+
let manifest_path = cache
|
|
110
|
+
.path(CacheKind::Manifest, &manifest_ref.sha256)
|
|
111
|
+
.unwrap();
|
|
112
|
+
fs::set_permissions(
|
|
113
|
+
&manifest_path,
|
|
114
|
+
std::os::unix::fs::PermissionsExt::from_mode(0o600),
|
|
115
|
+
)
|
|
116
|
+
.unwrap();
|
|
117
|
+
fs::write(&manifest_path, b"damaged manifest").unwrap();
|
|
118
|
+
assert_eq!(
|
|
119
|
+
cache
|
|
120
|
+
.backfill_store(&source, CacheKind::Manifest, &manifest_ref)
|
|
121
|
+
.unwrap(),
|
|
122
|
+
CacheWrite::Stored
|
|
123
|
+
);
|
|
124
|
+
assert_eq!(
|
|
125
|
+
fs::read(&manifest_path).unwrap(),
|
|
126
|
+
manifest.canonical_bytes()
|
|
127
|
+
);
|
|
128
|
+
|
|
108
129
|
let cache_root = cache.root();
|
|
109
130
|
assert!(!cache_root.join("tensorfs.sqlite").exists());
|
|
110
131
|
assert!(!cache_root.join("repos").exists());
|
|
@@ -203,10 +224,10 @@ fn missing_and_corrupt_cache_objects_fall_through_without_local_admission() {
|
|
|
203
224
|
cache
|
|
204
225
|
.backfill_store(&source, CacheKind::Blob, &wanted)
|
|
205
226
|
.unwrap(),
|
|
206
|
-
CacheWrite::
|
|
207
|
-
"
|
|
227
|
+
CacheWrite::Stored,
|
|
228
|
+
"verified local bytes atomically repair a corrupt regular cache entry"
|
|
208
229
|
);
|
|
209
|
-
assert_eq!(fs::read(&cache_path).unwrap(), b"
|
|
230
|
+
assert_eq!(fs::read(&cache_path).unwrap(), b"right");
|
|
210
231
|
fs::remove_file(&cache_path).unwrap();
|
|
211
232
|
assert_eq!(
|
|
212
233
|
cache
|
|
@@ -568,3 +589,66 @@ fn concurrent_offers_keep_the_offer_list_and_the_sequence_in_step() {
|
|
|
568
589
|
assert_eq!(mirror.report().durable.objects, objects.len() as u64);
|
|
569
590
|
let _ = fs::remove_dir_all(root);
|
|
570
591
|
}
|
|
592
|
+
|
|
593
|
+
#[test]
|
|
594
|
+
fn a_symlinked_fanout_never_redirects_cache_publication() {
|
|
595
|
+
use std::os::unix::fs::symlink;
|
|
596
|
+
let root = temporary("fanout-symlink");
|
|
597
|
+
let source = Store::init(&root.join("source")).unwrap();
|
|
598
|
+
let object = put_blob(&source, b"verified");
|
|
599
|
+
let cache = RepoObjectCache::new(root.join("cache"));
|
|
600
|
+
let outside = root.join("outside");
|
|
601
|
+
fs::create_dir_all(&outside).unwrap();
|
|
602
|
+
fs::create_dir_all(cache.root()).unwrap();
|
|
603
|
+
symlink(&outside, cache.root().join("blobs")).unwrap();
|
|
604
|
+
assert_eq!(
|
|
605
|
+
cache
|
|
606
|
+
.backfill_store(&source, CacheKind::Blob, &object)
|
|
607
|
+
.unwrap(),
|
|
608
|
+
CacheWrite::Unavailable
|
|
609
|
+
);
|
|
610
|
+
assert_eq!(fs::read_dir(outside).unwrap().count(), 0);
|
|
611
|
+
fs::remove_dir_all(root).unwrap();
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
#[test]
|
|
615
|
+
fn concurrent_repairs_publish_only_verified_complete_bytes() {
|
|
616
|
+
let root = temporary("repairs");
|
|
617
|
+
let source = Arc::new(Store::init(&root.join("source")).unwrap());
|
|
618
|
+
let data = vec![73; 2 << 20];
|
|
619
|
+
let object = put_blob(&source, &data);
|
|
620
|
+
let cache = Arc::new(RepoObjectCache::new(root.join("cache")));
|
|
621
|
+
let path = cache.path(CacheKind::Blob, &object.sha256).unwrap();
|
|
622
|
+
write(&path, &vec![0; data.len()]);
|
|
623
|
+
let barrier = Arc::new(Barrier::new(4));
|
|
624
|
+
let workers = (0..4)
|
|
625
|
+
.map(|_| {
|
|
626
|
+
let (source, cache, object, barrier) = (
|
|
627
|
+
source.clone(),
|
|
628
|
+
cache.clone(),
|
|
629
|
+
object.clone(),
|
|
630
|
+
barrier.clone(),
|
|
631
|
+
);
|
|
632
|
+
thread::spawn(move || {
|
|
633
|
+
barrier.wait();
|
|
634
|
+
cache
|
|
635
|
+
.backfill_store(&source, CacheKind::Blob, &object)
|
|
636
|
+
.unwrap()
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
.collect::<Vec<_>>();
|
|
640
|
+
for worker in workers {
|
|
641
|
+
assert!(matches!(
|
|
642
|
+
worker.join().unwrap(),
|
|
643
|
+
CacheWrite::Stored | CacheWrite::Present
|
|
644
|
+
));
|
|
645
|
+
}
|
|
646
|
+
assert_eq!(fs::read(path).unwrap(), data);
|
|
647
|
+
assert_eq!(
|
|
648
|
+
cache
|
|
649
|
+
.backfill_store(&source, CacheKind::Blob, &object)
|
|
650
|
+
.unwrap(),
|
|
651
|
+
CacheWrite::Present
|
|
652
|
+
);
|
|
653
|
+
fs::remove_dir_all(root).unwrap();
|
|
654
|
+
}
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/optional-cache-backfill.md
RENAMED
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/examples/resident_conversion_profile.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor
RENAMED
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json
RENAMED
|
File without changes
|
{tensorfs-0.3.37 → tensorfs-0.3.38}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|