tensorfs 0.3.46__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.46 → tensorfs-0.3.47}/Cargo.lock +2 -2
- {tensorfs-0.3.46 → tensorfs-0.3.47}/Cargo.toml +1 -1
- {tensorfs-0.3.46 → tensorfs-0.3.47}/PKG-INFO +1 -1
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/Cargo.toml +1 -1
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/http.rs +84 -14
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/ledger.rs +20 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/pull.rs +8 -2
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/tests.rs +331 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/pyproject.toml +1 -1
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/asset_update.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-py/src/store.rs +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/_derived_channel.py +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/_ext.pyi +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/_source_channel.py +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.46 → tensorfs-0.3.47}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.46 → 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"] }
|
|
@@ -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
|
}
|
|
@@ -4094,3 +4094,334 @@ fn downloaded_cache_root_is_protected_then_evicted_and_refetched() {
|
|
|
4094
4094
|
assert!(store.repository_path(&repository).exists());
|
|
4095
4095
|
let _ = std::fs::remove_dir_all(root);
|
|
4096
4096
|
}
|
|
4097
|
+
|
|
4098
|
+
#[test]
|
|
4099
|
+
fn failed_object_cancels_stalled_siblings_and_preserves_completed_bytes() {
|
|
4100
|
+
use std::collections::BTreeMap;
|
|
4101
|
+
let root = temporary("failed-sibling");
|
|
4102
|
+
let store = Store::init(&root).unwrap();
|
|
4103
|
+
let good_bytes = vec![b'g'; 4096];
|
|
4104
|
+
let good = ObjectRef::of(&good_bytes);
|
|
4105
|
+
let failed = ObjectRef::of(b"missing object");
|
|
4106
|
+
let stalled = ObjectRef::of(b"unfinished object");
|
|
4107
|
+
store
|
|
4108
|
+
.put_stream(
|
|
4109
|
+
&mut good_bytes.as_slice(),
|
|
4110
|
+
Some(&good),
|
|
4111
|
+
&crate::store::Fault::default(),
|
|
4112
|
+
)
|
|
4113
|
+
.unwrap();
|
|
4114
|
+
let ledger = Arc::new(Ledger::with_resolution(Duration::from_millis(20)));
|
|
4115
|
+
ledger.taught(Duration::from_secs(5));
|
|
4116
|
+
let progress = Arc::clone(&ledger);
|
|
4117
|
+
let failed_path = format!("/{}", failed.sha256);
|
|
4118
|
+
let peer = origin(Arc::new(move |request| {
|
|
4119
|
+
if request.path == failed_path {
|
|
4120
|
+
let until = std::time::Instant::now() + Duration::from_secs(2);
|
|
4121
|
+
while progress.still().moved == 0 {
|
|
4122
|
+
assert!(
|
|
4123
|
+
std::time::Instant::now() < until,
|
|
4124
|
+
"peer fixture never started"
|
|
4125
|
+
);
|
|
4126
|
+
std::thread::sleep(Duration::from_millis(1));
|
|
4127
|
+
}
|
|
4128
|
+
return status(404, b"missing object");
|
|
4129
|
+
}
|
|
4130
|
+
Serve::Stall {
|
|
4131
|
+
total: 17,
|
|
4132
|
+
prefix: b"unfinished".to_vec(),
|
|
4133
|
+
}
|
|
4134
|
+
}));
|
|
4135
|
+
let mut objects = vec![good.clone(), failed.clone(), stalled.clone()];
|
|
4136
|
+
objects.sort_by(|a, b| a.sha256.cmp(&b.sha256));
|
|
4137
|
+
let (plan, _) = FetchPlan::of_objects(&store, "failed-sibling", &objects).unwrap();
|
|
4138
|
+
let urls: BTreeMap<String, String> = objects
|
|
4139
|
+
.iter()
|
|
4140
|
+
.map(|object| {
|
|
4141
|
+
(
|
|
4142
|
+
object.sha256.clone(),
|
|
4143
|
+
format!("{}/{}", peer.base, object.sha256),
|
|
4144
|
+
)
|
|
4145
|
+
})
|
|
4146
|
+
.collect();
|
|
4147
|
+
let started = std::time::Instant::now();
|
|
4148
|
+
let refusal = super::pull::fetch_wanted(
|
|
4149
|
+
&store,
|
|
4150
|
+
&plan,
|
|
4151
|
+
&urls,
|
|
4152
|
+
&local_policy(),
|
|
4153
|
+
&Anonymous,
|
|
4154
|
+
Deadline::after_seconds(Some(3.0)),
|
|
4155
|
+
&ledger,
|
|
4156
|
+
2,
|
|
4157
|
+
None,
|
|
4158
|
+
)
|
|
4159
|
+
.unwrap_err();
|
|
4160
|
+
assert!(
|
|
4161
|
+
refusal.detail.contains("404"),
|
|
4162
|
+
"original refusal lost: {refusal}"
|
|
4163
|
+
);
|
|
4164
|
+
assert!(
|
|
4165
|
+
started.elapsed() < Duration::from_secs(1),
|
|
4166
|
+
"failed pull waited on its stalled sibling: {:?}",
|
|
4167
|
+
started.elapsed()
|
|
4168
|
+
);
|
|
4169
|
+
assert!(store.record_valid(&good.sha256).is_ok());
|
|
4170
|
+
assert!(store.record_valid(&stalled.sha256).is_err());
|
|
4171
|
+
let (retry, _) = FetchPlan::of_objects(&store, "retry-failed-sibling", &objects).unwrap();
|
|
4172
|
+
assert_eq!(retry.held, vec![good]);
|
|
4173
|
+
assert_eq!(retry.wanted.len(), 2);
|
|
4174
|
+
let _ = std::fs::remove_dir_all(root);
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
#[test]
|
|
4178
|
+
fn complete_content_length_body_does_not_wait_for_connection_close() {
|
|
4179
|
+
let root = temporary("complete-keepalive");
|
|
4180
|
+
let store = Store::init(&root).unwrap();
|
|
4181
|
+
let bytes = b"complete bounded body";
|
|
4182
|
+
let object = ObjectRef::of(bytes);
|
|
4183
|
+
let (plan, _) =
|
|
4184
|
+
FetchPlan::of_objects(&store, "keepalive", std::slice::from_ref(&object)).unwrap();
|
|
4185
|
+
let grant = DeliveryGrant::mint(&plan, &object).unwrap();
|
|
4186
|
+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
4187
|
+
let address = listener.local_addr().unwrap();
|
|
4188
|
+
let (close_peer, hold_open) = std::sync::mpsc::channel();
|
|
4189
|
+
let peer = std::thread::spawn(move || {
|
|
4190
|
+
let (mut socket, _) = listener.accept().unwrap();
|
|
4191
|
+
read_request(&mut socket).unwrap();
|
|
4192
|
+
write!(
|
|
4193
|
+
socket,
|
|
4194
|
+
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: keep-alive\r\n\r\n",
|
|
4195
|
+
bytes.len()
|
|
4196
|
+
)
|
|
4197
|
+
.unwrap();
|
|
4198
|
+
socket.write_all(bytes).unwrap();
|
|
4199
|
+
// Keep TCP open until the complete Store admission returns. Disk fsync
|
|
4200
|
+
// time is unrelated to HTTP framing and may exceed a test socket timer.
|
|
4201
|
+
let _ = hold_open.recv();
|
|
4202
|
+
});
|
|
4203
|
+
let ledger = Ledger::with_resolution(Duration::from_millis(20));
|
|
4204
|
+
let result = fetch_url(
|
|
4205
|
+
&store,
|
|
4206
|
+
&grant,
|
|
4207
|
+
&format!("http://{address}/body"),
|
|
4208
|
+
&local_policy(),
|
|
4209
|
+
&Anonymous,
|
|
4210
|
+
Deadline::after_seconds(Some(1.0)),
|
|
4211
|
+
&ledger,
|
|
4212
|
+
None,
|
|
4213
|
+
);
|
|
4214
|
+
close_peer.send(()).unwrap();
|
|
4215
|
+
peer.join().unwrap();
|
|
4216
|
+
let result = result.unwrap();
|
|
4217
|
+
assert_eq!(result.admitted.object, object);
|
|
4218
|
+
assert!(store.record_valid(&object.sha256).is_ok());
|
|
4219
|
+
let _ = std::fs::remove_dir_all(root);
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
#[test]
|
|
4223
|
+
fn slow_control_answer_does_not_teach_object_stream_patience() {
|
|
4224
|
+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
4225
|
+
let address = listener.local_addr().unwrap();
|
|
4226
|
+
let peer = std::thread::spawn(move || {
|
|
4227
|
+
let (mut socket, _) = listener.accept().unwrap();
|
|
4228
|
+
read_request(&mut socket).unwrap();
|
|
4229
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
4230
|
+
socket
|
|
4231
|
+
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\n{}")
|
|
4232
|
+
.unwrap();
|
|
4233
|
+
});
|
|
4234
|
+
let ledger = Ledger::with_resolution(Duration::from_millis(10));
|
|
4235
|
+
let checked = local_policy()
|
|
4236
|
+
.check(&format!("http://{address}/presign"))
|
|
4237
|
+
.unwrap();
|
|
4238
|
+
let response = super::http::request(
|
|
4239
|
+
&super::http::Client::new(),
|
|
4240
|
+
&checked,
|
|
4241
|
+
"GET",
|
|
4242
|
+
&[],
|
|
4243
|
+
None,
|
|
4244
|
+
Deadline::after_seconds(Some(1.0)),
|
|
4245
|
+
&ledger,
|
|
4246
|
+
super::http::Judge::Transfer,
|
|
4247
|
+
None,
|
|
4248
|
+
Code::HUB_UNREACHABLE,
|
|
4249
|
+
)
|
|
4250
|
+
.unwrap();
|
|
4251
|
+
assert_eq!(response.read_capped(2).unwrap(), b"{}");
|
|
4252
|
+
peer.join().unwrap();
|
|
4253
|
+
assert_eq!(
|
|
4254
|
+
ledger.patience(),
|
|
4255
|
+
ledger.floor(),
|
|
4256
|
+
"Hub latency inflated the unrelated object-stream timeout"
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
#[test]
|
|
4261
|
+
fn consumer_pause_does_not_teach_object_stream_patience() {
|
|
4262
|
+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
4263
|
+
let address = listener.local_addr().unwrap();
|
|
4264
|
+
let (resume, paused) = std::sync::mpsc::channel();
|
|
4265
|
+
let peer = std::thread::spawn(move || {
|
|
4266
|
+
let (mut socket, _) = listener.accept().unwrap();
|
|
4267
|
+
read_request(&mut socket).unwrap();
|
|
4268
|
+
socket
|
|
4269
|
+
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\na")
|
|
4270
|
+
.unwrap();
|
|
4271
|
+
paused.recv().unwrap();
|
|
4272
|
+
socket.write_all(b"b").unwrap();
|
|
4273
|
+
});
|
|
4274
|
+
let ledger = Ledger::with_resolution(Duration::from_millis(10));
|
|
4275
|
+
let checked = local_policy()
|
|
4276
|
+
.check(&format!("http://{address}/object"))
|
|
4277
|
+
.unwrap();
|
|
4278
|
+
let mut body = super::http::request(
|
|
4279
|
+
&super::http::Client::new(),
|
|
4280
|
+
&checked,
|
|
4281
|
+
"GET",
|
|
4282
|
+
&[],
|
|
4283
|
+
None,
|
|
4284
|
+
Deadline::after_seconds(Some(1.0)),
|
|
4285
|
+
&ledger,
|
|
4286
|
+
super::http::Judge::Stream,
|
|
4287
|
+
None,
|
|
4288
|
+
Code::TRANSFER_FAILED,
|
|
4289
|
+
)
|
|
4290
|
+
.unwrap()
|
|
4291
|
+
.into_body();
|
|
4292
|
+
let mut byte = [0];
|
|
4293
|
+
body.read_exact(&mut byte).unwrap();
|
|
4294
|
+
assert_eq!(&byte, b"a");
|
|
4295
|
+
// Simulate time spent hashing or writing between successive body reads.
|
|
4296
|
+
std::thread::sleep(Duration::from_millis(200));
|
|
4297
|
+
resume.send(()).unwrap();
|
|
4298
|
+
body.read_exact(&mut byte).unwrap();
|
|
4299
|
+
assert_eq!(&byte, b"b");
|
|
4300
|
+
peer.join().unwrap();
|
|
4301
|
+
assert!(
|
|
4302
|
+
ledger.patience() < Duration::from_millis(200),
|
|
4303
|
+
"consumer work inflated the unrelated object-stream timeout"
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
#[test]
|
|
4308
|
+
fn pull_cancellation_interrupts_tls_headers_and_partial_body_waits() {
|
|
4309
|
+
for phase in ["tls", "headers", "body"] {
|
|
4310
|
+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
4311
|
+
let address = listener.local_addr().unwrap();
|
|
4312
|
+
let (entered, waiting) = std::sync::mpsc::channel();
|
|
4313
|
+
let peer = std::thread::spawn(move || {
|
|
4314
|
+
let (mut socket, _) = listener.accept().unwrap();
|
|
4315
|
+
socket
|
|
4316
|
+
.set_read_timeout(Some(Duration::from_secs(2)))
|
|
4317
|
+
.unwrap();
|
|
4318
|
+
let mut input = [0u8; 8192];
|
|
4319
|
+
if phase == "tls" {
|
|
4320
|
+
assert!(socket.read(&mut input).unwrap() > 0);
|
|
4321
|
+
} else {
|
|
4322
|
+
read_request(&mut socket).unwrap();
|
|
4323
|
+
if phase == "body" {
|
|
4324
|
+
socket
|
|
4325
|
+
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 99\r\n\r\nx")
|
|
4326
|
+
.unwrap();
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
entered.send(()).unwrap();
|
|
4330
|
+
while matches!(socket.read(&mut input),Ok(n) if n>0) {}
|
|
4331
|
+
});
|
|
4332
|
+
let ledger = Arc::new(Ledger::with_resolution(Duration::from_millis(20)));
|
|
4333
|
+
ledger.taught(Duration::from_secs(5));
|
|
4334
|
+
let abort = Arc::clone(&ledger);
|
|
4335
|
+
let cancelling = std::thread::spawn(move || {
|
|
4336
|
+
waiting.recv_timeout(Duration::from_secs(1)).unwrap();
|
|
4337
|
+
abort.cancel();
|
|
4338
|
+
});
|
|
4339
|
+
let scheme = if phase == "tls" { "https" } else { "http" };
|
|
4340
|
+
let checked = local_policy()
|
|
4341
|
+
.check(&format!("{scheme}://{address}/object"))
|
|
4342
|
+
.unwrap();
|
|
4343
|
+
let started = std::time::Instant::now();
|
|
4344
|
+
let result = super::http::request(
|
|
4345
|
+
&super::http::Client::new(),
|
|
4346
|
+
&checked,
|
|
4347
|
+
"GET",
|
|
4348
|
+
&[],
|
|
4349
|
+
None,
|
|
4350
|
+
Deadline::after_seconds(Some(1.0)),
|
|
4351
|
+
&ledger,
|
|
4352
|
+
super::http::Judge::Stream,
|
|
4353
|
+
None,
|
|
4354
|
+
Code::TRANSFER_FAILED,
|
|
4355
|
+
);
|
|
4356
|
+
let refusal = match result {
|
|
4357
|
+
Ok(response) => response.read_capped(99).unwrap_err(),
|
|
4358
|
+
Err(refusal) => refusal,
|
|
4359
|
+
};
|
|
4360
|
+
assert!(
|
|
4361
|
+
refusal.detail.contains("another object failed"),
|
|
4362
|
+
"{phase}: {refusal}"
|
|
4363
|
+
);
|
|
4364
|
+
assert!(
|
|
4365
|
+
started.elapsed() < Duration::from_millis(500),
|
|
4366
|
+
"{phase} ignored pull cancellation"
|
|
4367
|
+
);
|
|
4368
|
+
cancelling.join().unwrap();
|
|
4369
|
+
peer.join().unwrap();
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
#[test]
|
|
4374
|
+
#[cfg(target_os = "linux")]
|
|
4375
|
+
fn pull_cancellation_interrupts_a_pending_tcp_connection() {
|
|
4376
|
+
use rustix::net::{self, AddressFamily, SocketFlags, SocketType};
|
|
4377
|
+
let socket = net::socket_with(
|
|
4378
|
+
AddressFamily::INET,
|
|
4379
|
+
SocketType::STREAM,
|
|
4380
|
+
SocketFlags::CLOEXEC,
|
|
4381
|
+
Some(net::ipproto::TCP),
|
|
4382
|
+
)
|
|
4383
|
+
.unwrap();
|
|
4384
|
+
net::bind(
|
|
4385
|
+
&socket,
|
|
4386
|
+
&std::net::SocketAddrV4::new(std::net::Ipv4Addr::LOCALHOST, 0),
|
|
4387
|
+
)
|
|
4388
|
+
.unwrap();
|
|
4389
|
+
net::listen(&socket, 0).unwrap();
|
|
4390
|
+
let listener: TcpListener = socket.into();
|
|
4391
|
+
let address = listener.local_addr().unwrap();
|
|
4392
|
+
// Fill Linux's one-entry backlog and deliberately never accept it. A second
|
|
4393
|
+
// connection must keep its same nonblocking socket while polling cancellation.
|
|
4394
|
+
let _occupied = TcpStream::connect(address).unwrap();
|
|
4395
|
+
let ledger = Arc::new(Ledger::with_resolution(Duration::from_millis(20)));
|
|
4396
|
+
ledger.taught(Duration::from_secs(5));
|
|
4397
|
+
let abort = Arc::clone(&ledger);
|
|
4398
|
+
let cancelling = std::thread::spawn(move || {
|
|
4399
|
+
std::thread::sleep(Duration::from_millis(50));
|
|
4400
|
+
abort.cancel();
|
|
4401
|
+
});
|
|
4402
|
+
let checked = local_policy()
|
|
4403
|
+
.check(&format!("http://{address}/object"))
|
|
4404
|
+
.unwrap();
|
|
4405
|
+
let started = std::time::Instant::now();
|
|
4406
|
+
let refusal = match super::http::request(
|
|
4407
|
+
&super::http::Client::new(),
|
|
4408
|
+
&checked,
|
|
4409
|
+
"GET",
|
|
4410
|
+
&[],
|
|
4411
|
+
None,
|
|
4412
|
+
Deadline::after_seconds(Some(1.0)),
|
|
4413
|
+
&ledger,
|
|
4414
|
+
super::http::Judge::Stream,
|
|
4415
|
+
None,
|
|
4416
|
+
Code::TRANSFER_FAILED,
|
|
4417
|
+
) {
|
|
4418
|
+
Err(refusal) => refusal,
|
|
4419
|
+
Ok(_) => panic!("full listener unexpectedly answered"),
|
|
4420
|
+
};
|
|
4421
|
+
assert!(
|
|
4422
|
+
refusal.detail.contains("another object failed"),
|
|
4423
|
+
"{refusal}"
|
|
4424
|
+
);
|
|
4425
|
+
assert!(started.elapsed() < Duration::from_millis(500));
|
|
4426
|
+
cancelling.join().unwrap();
|
|
4427
|
+
}
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/examples/optional-cache-backfill.md
RENAMED
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.46 → tensorfs-0.3.47}/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
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.46 → tensorfs-0.3.47}/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.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor
RENAMED
|
File without changes
|
{tensorfs-0.3.46 → tensorfs-0.3.47}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json
RENAMED
|
File without changes
|
{tensorfs-0.3.46 → tensorfs-0.3.47}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|