tensorfs 0.3.38__tar.gz → 0.3.40__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.38 → tensorfs-0.3.40}/Cargo.lock +2 -2
- {tensorfs-0.3.38 → tensorfs-0.3.40}/Cargo.toml +1 -1
- {tensorfs-0.3.38 → tensorfs-0.3.40}/PKG-INFO +1 -1
- tensorfs-0.3.40/crates/tensorfs-core/src/derived/estimate.rs +303 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/resume_tests.rs +150 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived.rs +189 -134
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/meta.rs +74 -5
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-py/src/store.rs +71 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/pyproject.toml +1 -1
- {tensorfs-0.3.38 → tensorfs-0.3.40}/python/tensorfs/__init__.py +6 -0
- tensorfs-0.3.40/python/tensorfs/_derived_channel.py +458 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/python/tensorfs/_ext.pyi +20 -0
- tensorfs-0.3.40/python/tensorfs/derived.py +208 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.38 → tensorfs-0.3.40}/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.40"
|
|
425
425
|
dependencies = [
|
|
426
426
|
"fs2",
|
|
427
427
|
"minicbor",
|
|
@@ -434,7 +434,7 @@ dependencies = [
|
|
|
434
434
|
|
|
435
435
|
[[package]]
|
|
436
436
|
name = "tensorfs-py"
|
|
437
|
-
version = "0.3.
|
|
437
|
+
version = "0.3.40"
|
|
438
438
|
dependencies = [
|
|
439
439
|
"pyo3",
|
|
440
440
|
"tensorfs-core",
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
//! Remaining added-role geometry without opening or fencing a native writer.
|
|
2
|
+
|
|
3
|
+
use super::*;
|
|
4
|
+
|
|
5
|
+
#[derive(Debug, Default, PartialEq, Eq)]
|
|
6
|
+
pub struct WriteEstimate {
|
|
7
|
+
pub payload_bytes: u64,
|
|
8
|
+
pub largest_part_bytes: u64,
|
|
9
|
+
pub parts: u64,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// Inspect exactly the progress that begin would adopt. The observation is not a
|
|
13
|
+
/// reservation or receipt: begin revalidates it, and per-role admission still
|
|
14
|
+
/// guards every actual write. All inspection holds are released before return.
|
|
15
|
+
pub fn estimate_write(
|
|
16
|
+
store: &Store,
|
|
17
|
+
meta: &Meta,
|
|
18
|
+
transaction: &str,
|
|
19
|
+
writer_session: u64,
|
|
20
|
+
mut declaration: Declaration,
|
|
21
|
+
checkpoint: Option<&ObjectRef>,
|
|
22
|
+
) -> Result<WriteEstimate> {
|
|
23
|
+
let transaction = transaction_id(transaction)?;
|
|
24
|
+
if writer_session == 0 || writer_session > limits::INT_MAX as u64 {
|
|
25
|
+
return refuse(
|
|
26
|
+
Code::NUMBER_RANGE,
|
|
27
|
+
"WriterSessionId must be positive and inside the canonical integer range",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
let text =
|
|
31
|
+
String::from_utf8(declaration.canonical_work_bytes()?).expect("canonical JSON is UTF-8");
|
|
32
|
+
let hold = meta.acquire_hold("derived-write-estimate")?;
|
|
33
|
+
let estimate = (|| {
|
|
34
|
+
require_source_roots(store, &declaration)?;
|
|
35
|
+
let sources = load_sources(store, &declaration)?;
|
|
36
|
+
let facts: Vec<_> = sources.iter().map(|source| source.fact.clone()).collect();
|
|
37
|
+
let incoming = checkpoint
|
|
38
|
+
.map(|head| read_progress(store, head, &transaction, &text, &facts))
|
|
39
|
+
.transpose()?;
|
|
40
|
+
let initial = meta
|
|
41
|
+
.derived_rows()?
|
|
42
|
+
.into_iter()
|
|
43
|
+
.find(|row| row.id == transaction);
|
|
44
|
+
let mut row = planned_row(
|
|
45
|
+
initial.as_ref(),
|
|
46
|
+
&transaction,
|
|
47
|
+
writer_session,
|
|
48
|
+
&text,
|
|
49
|
+
&facts,
|
|
50
|
+
)?;
|
|
51
|
+
merge_progress(store, &mut row, incoming.as_ref())?;
|
|
52
|
+
build_header(
|
|
53
|
+
&declaration,
|
|
54
|
+
&sources,
|
|
55
|
+
&row.added_parts,
|
|
56
|
+
&row.added_configs,
|
|
57
|
+
true,
|
|
58
|
+
)?;
|
|
59
|
+
if roots::read(store, &transaction)?
|
|
60
|
+
.and_then(|root| root.restore_epoch)
|
|
61
|
+
.is_some_and(|epoch| epoch > writer_session)
|
|
62
|
+
{
|
|
63
|
+
return refuse(
|
|
64
|
+
Code::WRITER_FENCED,
|
|
65
|
+
"writer is behind the retained restore epoch",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
let completed: HashSet<_> = row
|
|
69
|
+
.added_parts
|
|
70
|
+
.iter()
|
|
71
|
+
.map(|part| {
|
|
72
|
+
(
|
|
73
|
+
part.component.as_str(),
|
|
74
|
+
part.key.as_str(),
|
|
75
|
+
part.role.as_str(),
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
.collect();
|
|
79
|
+
let mut result = WriteEstimate::default();
|
|
80
|
+
for component in &declaration.components {
|
|
81
|
+
for tensor in &component.add {
|
|
82
|
+
for part in &tensor.parts {
|
|
83
|
+
if part.source.is_some()
|
|
84
|
+
|| completed.contains(&(
|
|
85
|
+
component.target.as_str(),
|
|
86
|
+
tensor.key.as_str(),
|
|
87
|
+
part.role.as_str(),
|
|
88
|
+
))
|
|
89
|
+
{
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
let length = checked_bytes("remaining derived part", &part.shape, part.dtype)?;
|
|
93
|
+
result.payload_bytes = result
|
|
94
|
+
.payload_bytes
|
|
95
|
+
.checked_add(length)
|
|
96
|
+
.ok_or_else(|| arithmetic("remaining derived payload overflows"))?;
|
|
97
|
+
result.largest_part_bytes = result.largest_part_bytes.max(length);
|
|
98
|
+
result.parts += 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
Ok(result)
|
|
103
|
+
})();
|
|
104
|
+
let released = hold.release(meta);
|
|
105
|
+
estimate.and_then(|value| released.map(|()| value))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[cfg(test)]
|
|
109
|
+
mod tests {
|
|
110
|
+
use super::*;
|
|
111
|
+
|
|
112
|
+
fn fixture(label: &str) -> (Store, Meta, String, Declaration) {
|
|
113
|
+
let path = std::env::temp_dir().join(format!(
|
|
114
|
+
"tensorfs-derived-estimate-{label}-{}",
|
|
115
|
+
crate::meta::now_nanos_unique()
|
|
116
|
+
));
|
|
117
|
+
let store = Store::init(&path).unwrap();
|
|
118
|
+
let meta = Meta::open(&store).unwrap();
|
|
119
|
+
(
|
|
120
|
+
store,
|
|
121
|
+
meta,
|
|
122
|
+
format!("sha256:{}", "78".repeat(32)),
|
|
123
|
+
super::super::tests::created_declaration(),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn release(meta: &Meta, begun: Begin) {
|
|
128
|
+
release_guards(meta, Some(begun.writer_hold), begun.source_leases);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn fresh_estimate_does_not_claim_writer_or_block_collection() {
|
|
133
|
+
let (store, meta, id, declaration) = fixture("fresh");
|
|
134
|
+
let before = meta.derived_rows().unwrap();
|
|
135
|
+
assert_eq!(
|
|
136
|
+
estimate_write(&store, &meta, &id, 1, declaration.clone(), None).unwrap(),
|
|
137
|
+
WriteEstimate {
|
|
138
|
+
payload_bytes: 2048,
|
|
139
|
+
largest_part_bytes: 2048,
|
|
140
|
+
parts: 1
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
assert_eq!(meta.derived_rows().unwrap(), before);
|
|
144
|
+
assert!(matches!(
|
|
145
|
+
lookup(&store, &meta, &id).unwrap(),
|
|
146
|
+
Lookup::Absent
|
|
147
|
+
));
|
|
148
|
+
crate::gc::collect(store.root(), false).unwrap();
|
|
149
|
+
let writer = begin(&store, &meta, &id, 1, declaration, None).unwrap();
|
|
150
|
+
fence(&meta, &id, 1).unwrap();
|
|
151
|
+
release(&meta, writer);
|
|
152
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[test]
|
|
156
|
+
fn interrupted_large_writer_only_charges_its_small_remaining_tail() {
|
|
157
|
+
let (store, meta, id, mut declaration) = fixture("large-partial");
|
|
158
|
+
const LARGE: u64 = 64 << 20;
|
|
159
|
+
let mut tail = declaration.components[0].add[0].clone();
|
|
160
|
+
tail.key = "tail".into();
|
|
161
|
+
tail.shape = vec![1];
|
|
162
|
+
tail.parts[0].shape = vec![1];
|
|
163
|
+
declaration.components[0].add[0].shape = vec![LARGE / 4];
|
|
164
|
+
declaration.components[0].add[0].parts[0].shape = vec![LARGE / 4];
|
|
165
|
+
declaration.components[0].add.push(tail);
|
|
166
|
+
declaration.order.push(("model".into(), "tail".into()));
|
|
167
|
+
declaration.max_new_bytes = LARGE + 4;
|
|
168
|
+
let writer = begin(&store, &meta, &id, 1, declaration.clone(), None).unwrap();
|
|
169
|
+
let written = add_part(
|
|
170
|
+
&store,
|
|
171
|
+
&meta,
|
|
172
|
+
&id,
|
|
173
|
+
1,
|
|
174
|
+
("model", "weight", "value"),
|
|
175
|
+
&mut std::io::repeat(0x31).take(LARGE),
|
|
176
|
+
)
|
|
177
|
+
.unwrap();
|
|
178
|
+
let (_, checkpoint) =
|
|
179
|
+
checkpoint_progress(&store, &meta, &id, 1, "request", "model", None).unwrap();
|
|
180
|
+
assert!(checkpoint.head.is_some());
|
|
181
|
+
fence(&meta, &id, 1).unwrap();
|
|
182
|
+
release(&meta, writer);
|
|
183
|
+
let before = meta.derived_rows().unwrap();
|
|
184
|
+
assert_eq!(
|
|
185
|
+
estimate_write(
|
|
186
|
+
&store,
|
|
187
|
+
&meta,
|
|
188
|
+
&id,
|
|
189
|
+
1,
|
|
190
|
+
declaration.clone(),
|
|
191
|
+
checkpoint.head.as_ref()
|
|
192
|
+
)
|
|
193
|
+
.unwrap_err()
|
|
194
|
+
.code,
|
|
195
|
+
Code::WRITER_FENCED
|
|
196
|
+
);
|
|
197
|
+
assert_eq!(
|
|
198
|
+
estimate_write(
|
|
199
|
+
&store,
|
|
200
|
+
&meta,
|
|
201
|
+
&id,
|
|
202
|
+
2,
|
|
203
|
+
declaration.clone(),
|
|
204
|
+
checkpoint.head.as_ref()
|
|
205
|
+
)
|
|
206
|
+
.unwrap(),
|
|
207
|
+
WriteEstimate {
|
|
208
|
+
payload_bytes: 4,
|
|
209
|
+
largest_part_bytes: 4,
|
|
210
|
+
parts: 1
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
assert_eq!(meta.derived_rows().unwrap(), before);
|
|
214
|
+
crate::gc::collect(store.root(), false).unwrap();
|
|
215
|
+
assert!(written
|
|
216
|
+
.part
|
|
217
|
+
.segments()
|
|
218
|
+
.iter()
|
|
219
|
+
.all(|part| store.contains(&part.sha256)));
|
|
220
|
+
let resumed = begin(&store, &meta, &id, 2, declaration, checkpoint.head.as_ref()).unwrap();
|
|
221
|
+
assert_eq!(
|
|
222
|
+
completed(&meta, &id, 2).unwrap().0,
|
|
223
|
+
vec![("model".into(), "weight".into(), "value".into())]
|
|
224
|
+
);
|
|
225
|
+
add_part(
|
|
226
|
+
&store,
|
|
227
|
+
&meta,
|
|
228
|
+
&id,
|
|
229
|
+
2,
|
|
230
|
+
("model", "tail", "value"),
|
|
231
|
+
&mut [0u8; 4].as_slice(),
|
|
232
|
+
)
|
|
233
|
+
.unwrap();
|
|
234
|
+
commit(&store, &meta, &id, 2).unwrap();
|
|
235
|
+
release(&meta, resumed);
|
|
236
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[test]
|
|
240
|
+
fn grafts_charge_no_new_payload_and_preserve_source_custody() {
|
|
241
|
+
let (store, meta, id, mut declaration) = fixture("graft");
|
|
242
|
+
let writer = begin(&store, &meta, &id, 1, declaration.clone(), None).unwrap();
|
|
243
|
+
add_part(
|
|
244
|
+
&store,
|
|
245
|
+
&meta,
|
|
246
|
+
&id,
|
|
247
|
+
1,
|
|
248
|
+
("model", "weight", "value"),
|
|
249
|
+
&mut [0x31u8; 2048].as_slice(),
|
|
250
|
+
)
|
|
251
|
+
.unwrap();
|
|
252
|
+
let original = commit(&store, &meta, &id, 1).unwrap();
|
|
253
|
+
release(&meta, writer);
|
|
254
|
+
declaration.sources.push(Source {
|
|
255
|
+
alias: "original".into(),
|
|
256
|
+
manifest: original.manifest.clone(),
|
|
257
|
+
});
|
|
258
|
+
declaration.components[0].add[0].parts[0].source = Some(PartSource {
|
|
259
|
+
source: "original".into(),
|
|
260
|
+
component: "model".into(),
|
|
261
|
+
tensor: "weight".into(),
|
|
262
|
+
role: "value".into(),
|
|
263
|
+
});
|
|
264
|
+
declaration.max_new_bytes = 0;
|
|
265
|
+
let target = format!("sha256:{}", "79".repeat(32));
|
|
266
|
+
let before = meta.derived_rows().unwrap();
|
|
267
|
+
assert_eq!(
|
|
268
|
+
estimate_write(&store, &meta, &target, 1, declaration.clone(), None).unwrap(),
|
|
269
|
+
WriteEstimate::default()
|
|
270
|
+
);
|
|
271
|
+
assert_eq!(meta.derived_rows().unwrap(), before);
|
|
272
|
+
crate::gc::collect(store.root(), false).unwrap();
|
|
273
|
+
let target_writer = begin(&store, &meta, &target, 1, declaration, None).unwrap();
|
|
274
|
+
assert_eq!(
|
|
275
|
+
commit(&store, &meta, &target, 1).unwrap().manifest,
|
|
276
|
+
original.manifest
|
|
277
|
+
);
|
|
278
|
+
release(&meta, target_writer);
|
|
279
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#[test]
|
|
283
|
+
fn changed_work_and_missing_checkpoint_refuse_without_mutating_progress() {
|
|
284
|
+
let (store, meta, id, declaration) = fixture("refuse");
|
|
285
|
+
let writer = begin(&store, &meta, &id, 1, declaration.clone(), None).unwrap();
|
|
286
|
+
fence(&meta, &id, 1).unwrap();
|
|
287
|
+
release(&meta, writer);
|
|
288
|
+
let before = meta.derived_rows().unwrap();
|
|
289
|
+
let mut changed = declaration.clone();
|
|
290
|
+
changed.work_fingerprint = Some(format!("sha256:{}", "65".repeat(32)));
|
|
291
|
+
assert_eq!(
|
|
292
|
+
estimate_write(&store, &meta, &id, 2, changed, None)
|
|
293
|
+
.unwrap_err()
|
|
294
|
+
.code,
|
|
295
|
+
Code::TRANSACTION_CONFLICT
|
|
296
|
+
);
|
|
297
|
+
let absent = ObjectRef::of(b"not a checkpoint");
|
|
298
|
+
assert!(estimate_write(&store, &meta, &id, 2, declaration, Some(&absent)).is_err());
|
|
299
|
+
assert_eq!(meta.derived_rows().unwrap(), before);
|
|
300
|
+
crate::gc::collect(store.root(), false).unwrap();
|
|
301
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -918,3 +918,153 @@ fn source_free_restore_holds_survive_gc_until_begin_or_explicit_abandon() {
|
|
|
918
918
|
release(&source_meta, writer);
|
|
919
919
|
std::fs::remove_dir_all(source.root()).unwrap();
|
|
920
920
|
}
|
|
921
|
+
|
|
922
|
+
/// Opt-in generated-data profile. Payloads are tiny so metadata scaling is visible.
|
|
923
|
+
/// Run in release mode; elapsed time is evidence, never a flaky unit-test threshold.
|
|
924
|
+
#[test]
|
|
925
|
+
#[ignore]
|
|
926
|
+
fn profile_derived_transaction_scaling() {
|
|
927
|
+
use std::time::Instant;
|
|
928
|
+
let scenarios = [
|
|
929
|
+
(219, 1, 1),
|
|
930
|
+
(219, 1, 10),
|
|
931
|
+
(219, 4, 10),
|
|
932
|
+
(219, 32, 10),
|
|
933
|
+
(219, 128, 10),
|
|
934
|
+
(3968, 1, 10),
|
|
935
|
+
(3968, 4, 10),
|
|
936
|
+
(219, 1, 219),
|
|
937
|
+
];
|
|
938
|
+
for (keys, histories, writes) in scenarios {
|
|
939
|
+
let (store, meta, id, _) = fixture("row-profile");
|
|
940
|
+
let mut declaration = super::tests::created_declaration();
|
|
941
|
+
let prototype = declaration.components[0].add[0].clone();
|
|
942
|
+
declaration.components[0].add = (0..keys)
|
|
943
|
+
.map(|index| {
|
|
944
|
+
let mut tensor = prototype.clone();
|
|
945
|
+
tensor.key = format!("weight_{index:04}");
|
|
946
|
+
tensor.shape = vec![4];
|
|
947
|
+
tensor.parts[0].shape = vec![4];
|
|
948
|
+
tensor
|
|
949
|
+
})
|
|
950
|
+
.collect();
|
|
951
|
+
declaration.order = declaration.components[0]
|
|
952
|
+
.add
|
|
953
|
+
.iter()
|
|
954
|
+
.map(|tensor| ("model".into(), tensor.key.clone()))
|
|
955
|
+
.collect();
|
|
956
|
+
declaration.max_new_bytes = (keys * 16) as u64;
|
|
957
|
+
let writer = begin(&store, &meta, &id, 1, declaration, None).unwrap();
|
|
958
|
+
// Populate representative unrelated transactions without timing fixture creation.
|
|
959
|
+
let mut txn = meta.txn().unwrap();
|
|
960
|
+
let prototype = txn.rows[0].clone();
|
|
961
|
+
let row_bytes = crate::canon::write(&prototype.to_value()).len();
|
|
962
|
+
for index in 1..histories {
|
|
963
|
+
let mut row = prototype.clone();
|
|
964
|
+
row.id = ObjectRef::of(format!("history-{index}").as_bytes()).id();
|
|
965
|
+
txn.rows.push(row);
|
|
966
|
+
}
|
|
967
|
+
txn.commit().unwrap();
|
|
968
|
+
let started = Instant::now();
|
|
969
|
+
for _ in 0..20 {
|
|
970
|
+
check_writer(&meta, &id, 1).unwrap();
|
|
971
|
+
}
|
|
972
|
+
let fence_ms = started.elapsed().as_secs_f64() * 1000.0;
|
|
973
|
+
let mut part_ms = 0.0;
|
|
974
|
+
let mut checkpoint_ms = 0.0;
|
|
975
|
+
let mut prior = None;
|
|
976
|
+
for index in 0..writes {
|
|
977
|
+
let key = format!("weight_{index:04}");
|
|
978
|
+
let started = Instant::now();
|
|
979
|
+
add_part(
|
|
980
|
+
&store,
|
|
981
|
+
&meta,
|
|
982
|
+
&id,
|
|
983
|
+
1,
|
|
984
|
+
("model", &key, "value"),
|
|
985
|
+
&mut [index as u8; 16].as_slice(),
|
|
986
|
+
)
|
|
987
|
+
.unwrap();
|
|
988
|
+
part_ms += started.elapsed().as_secs_f64() * 1000.0;
|
|
989
|
+
let started = Instant::now();
|
|
990
|
+
let (_, head) =
|
|
991
|
+
checkpoint_progress(&store, &meta, &id, 1, "profile", "model", prior.as_ref())
|
|
992
|
+
.unwrap();
|
|
993
|
+
prior = head.head;
|
|
994
|
+
checkpoint_ms += started.elapsed().as_secs_f64() * 1000.0;
|
|
995
|
+
}
|
|
996
|
+
eprintln!("PROFILE {{\"keys\":{keys},\"histories\":{histories},\"writes\":{writes},\"row_bytes\":{row_bytes},\"fence20_ms\":{fence_ms:.3},\"add_ms\":{part_ms:.3},\"checkpoint_ms\":{checkpoint_ms:.3}}}");
|
|
997
|
+
fence(&meta, &id, 1).unwrap();
|
|
998
|
+
release(&meta, writer);
|
|
999
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
#[test]
|
|
1004
|
+
fn named_writer_operations_leave_other_rows_and_writer_lock_alone() {
|
|
1005
|
+
use rusqlite::{params, Connection};
|
|
1006
|
+
let (store, meta, first_id, declaration) = fixture("scoped-row");
|
|
1007
|
+
let second_id = ObjectRef::of(b"independent writer").id();
|
|
1008
|
+
let first = begin(&store, &meta, &first_id, 1, declaration.clone(), None).unwrap();
|
|
1009
|
+
let second = begin(&store, &meta, &second_id, 1, declaration.clone(), None).unwrap();
|
|
1010
|
+
let connection = Connection::open(crate::catalog::Catalog::path(store.root())).unwrap();
|
|
1011
|
+
let untouched: Vec<u8> = connection
|
|
1012
|
+
.query_row(
|
|
1013
|
+
"SELECT bytes FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1014
|
+
[&second_id],
|
|
1015
|
+
|row| row.get(0),
|
|
1016
|
+
)
|
|
1017
|
+
.unwrap();
|
|
1018
|
+
// Corrupt unrelated history makes an accidental whole-table read fail, while
|
|
1019
|
+
// a scoped mutation must preserve it byte-for-byte and mutate only this writer.
|
|
1020
|
+
let damaged = ObjectRef::of(b"unrelated damaged history").id();
|
|
1021
|
+
connection
|
|
1022
|
+
.execute(
|
|
1023
|
+
"INSERT INTO tensorfs_derived_transactions(id,bytes) VALUES(?1,?2)",
|
|
1024
|
+
params![damaged, b"broken".as_slice()],
|
|
1025
|
+
)
|
|
1026
|
+
.unwrap();
|
|
1027
|
+
data(&store, &meta, &first_id, 1, 0x31);
|
|
1028
|
+
let after: Vec<u8> = connection
|
|
1029
|
+
.query_row(
|
|
1030
|
+
"SELECT bytes FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1031
|
+
[&second_id],
|
|
1032
|
+
|row| row.get(0),
|
|
1033
|
+
)
|
|
1034
|
+
.unwrap();
|
|
1035
|
+
assert_eq!(untouched, after);
|
|
1036
|
+
// A read fence must succeed while an unrelated SQLite writer is active. The old
|
|
1037
|
+
// BEGIN IMMEDIATE observation instead hit the five-second busy timeout.
|
|
1038
|
+
connection.execute_batch("BEGIN IMMEDIATE").unwrap();
|
|
1039
|
+
check_writer(&meta, &second_id, 1).unwrap();
|
|
1040
|
+
assert!(completed(&meta, &second_id, 1).unwrap().0.is_empty());
|
|
1041
|
+
connection.execute_batch("ROLLBACK").unwrap();
|
|
1042
|
+
// A named operation does not parse unrelated history. Global inventory still
|
|
1043
|
+
// reports the damaged row rather than silently skipping it.
|
|
1044
|
+
check_writer(&meta, &first_id, 1).unwrap();
|
|
1045
|
+
assert!(meta.derived_rows().is_err());
|
|
1046
|
+
connection
|
|
1047
|
+
.execute(
|
|
1048
|
+
"DELETE FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1049
|
+
[&damaged],
|
|
1050
|
+
)
|
|
1051
|
+
.unwrap();
|
|
1052
|
+
assert!(fence(&meta, &first_id, 1).unwrap());
|
|
1053
|
+
assert_eq!(
|
|
1054
|
+
check_writer(&meta, &first_id, 1).unwrap_err().code,
|
|
1055
|
+
Code::WRITER_FENCED
|
|
1056
|
+
);
|
|
1057
|
+
check_writer(&meta, &second_id, 1).unwrap();
|
|
1058
|
+
release(&meta, first);
|
|
1059
|
+
let resumed = begin(&store, &meta, &first_id, 2, declaration, None).unwrap();
|
|
1060
|
+
assert_eq!(completed(&meta, &first_id, 2).unwrap().0.len(), 1);
|
|
1061
|
+
assert_eq!(
|
|
1062
|
+
check_writer(&meta, &first_id, 1).unwrap_err().code,
|
|
1063
|
+
Code::WRITER_FENCED
|
|
1064
|
+
);
|
|
1065
|
+
fence(&meta, &first_id, 2).unwrap();
|
|
1066
|
+
fence(&meta, &second_id, 1).unwrap();
|
|
1067
|
+
release(&meta, resumed);
|
|
1068
|
+
release(&meta, second);
|
|
1069
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
1070
|
+
}
|