tensorfs 0.3.92__tar.gz → 0.3.93__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.92 → tensorfs-0.3.93}/Cargo.lock +3 -3
- {tensorfs-0.3.92 → tensorfs-0.3.93}/Cargo.toml +1 -1
- {tensorfs-0.3.92 → tensorfs-0.3.93}/PKG-INFO +1 -1
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/API.md +14 -28
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/cursor.rs +1 -1
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/device.rs +13 -73
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/faults.rs +3 -19
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/io.rs +14 -133
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/plane.rs +26 -60
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/tests/common/mod.rs +0 -25
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/tests/gpu.rs +9 -148
- {tensorfs-0.3.92 → tensorfs-0.3.93}/pyproject.toml +1 -1
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/plane.pyi +3 -3
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/asset_update.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/ensure.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/catalog/lock.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/group_commit_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/history_cost_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/part_rows_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/descriptors.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/disk.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ensure/admission.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ensure/flight.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ensure.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/filesystem_identity.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/narrow.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/routes/anima.routes.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/routes/anima.spec.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/routes/sdxl.routes.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/routes/sdxl.spec.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/routes.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/keyed_roots.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/stats.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/decisions.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/download.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/memory.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/sources.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/tests/tail.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/unsynced.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/anima_hf_profile.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/as_is.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/catalog_owner_probe.c +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/civitai_anima.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/civitai_sdxl_profile.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/ensure.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fd_budget.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fetch_faults.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/hub_tls.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/keyed_roots.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/routed_single_file.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/source_download.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/trust_index.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/Cargo.toml +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/bin/g1_vmm.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/bin/g2_overlap.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/bin/gpu_bench/mod.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/cuda.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/host.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/layout.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/src/lib.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/tests/census.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-plane/tests/host_tier.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/ensure.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/ingest.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/plane.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/crates/tensorfs-py/src/store.rs +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/_derived_channel.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/_ext.pyi +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/_source_channel.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/plane.py +0 -0
- {tensorfs-0.3.92 → tensorfs-0.3.93}/python/tensorfs/py.typed +0 -0
|
@@ -449,7 +449,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
449
449
|
|
|
450
450
|
[[package]]
|
|
451
451
|
name = "tensorfs-core"
|
|
452
|
-
version = "0.3.
|
|
452
|
+
version = "0.3.93"
|
|
453
453
|
dependencies = [
|
|
454
454
|
"fs2",
|
|
455
455
|
"libc",
|
|
@@ -464,7 +464,7 @@ dependencies = [
|
|
|
464
464
|
|
|
465
465
|
[[package]]
|
|
466
466
|
name = "tensorfs-plane"
|
|
467
|
-
version = "0.3.
|
|
467
|
+
version = "0.3.93"
|
|
468
468
|
dependencies = [
|
|
469
469
|
"libc",
|
|
470
470
|
"libloading",
|
|
@@ -474,7 +474,7 @@ dependencies = [
|
|
|
474
474
|
|
|
475
475
|
[[package]]
|
|
476
476
|
name = "tensorfs-py"
|
|
477
|
-
version = "0.3.
|
|
477
|
+
version = "0.3.93"
|
|
478
478
|
dependencies = [
|
|
479
479
|
"pyo3",
|
|
480
480
|
"tensorfs-core",
|
|
@@ -99,18 +99,7 @@ ts = p.replicate(ws, devices, regions=None, priority=0, pin=False) # [Ticket]
|
|
|
99
99
|
wait meanwhile; unregister on eviction ~0.03 ms/MiB): want the pinned tier at admission or
|
|
100
100
|
between stages, never while this process computes. A host-only plane registers nothing.
|
|
101
101
|
- device: map the regions (slab-sized handles), then copy from the pinned tier if it holds the
|
|
102
|
-
region
|
|
103
|
-
of their objects (no host copy, no wait for a pinned fill, which then runs beside it;
|
|
104
|
-
counted in `host_copy_bytes` and, as its part, `mapped_copy_bytes`); else after a pinned
|
|
105
|
-
fill that is running (one disk read
|
|
106
|
-
serves both); else disk → staging buffers → device. A demand miss (`acquire`) and a
|
|
107
|
-
streaming ring never map. An object that cannot be mapped (not ext4, xfs, btrfs, tmpfs or
|
|
108
|
-
overlayfs; not at its declared length; pages gone from the cache) sends its region through
|
|
109
|
-
staging (`map_refused`). A mapping's fault is a signal, not an error: a disk read error on a
|
|
110
|
-
page evicted between the check and the copy kills the process with SIGBUS. The cache query
|
|
111
|
-
runs on the caller before the fill is queued and opens each object of the wanted regions
|
|
112
|
-
(a record lookup, a rehash when the record is missing): a device `want` can block on a
|
|
113
|
-
first open.
|
|
102
|
+
region, after it if it is filling, else disk → staging buffers → device.
|
|
114
103
|
- `replicate`: one pinned fill (if the pinned budget allows), then one copy per device.
|
|
115
104
|
- A `want` of regions already resident only updates their priority/pin: no bytes move and
|
|
116
105
|
nothing is evicted. To re-prioritise without any chance of a fill, use:
|
|
@@ -212,13 +201,8 @@ p.stats() -> {
|
|
|
212
201
|
"devices": [{"ordinal", "uuid", "granularity", "exportable", "budget", "committed", "mapped",
|
|
213
202
|
"idle", "leased_bytes", "held_bytes", "over_budget_unreleasable", "reserved_va",
|
|
214
203
|
"copies", "host_copy_bytes", "host_copy_ns", "disk_copy_bytes", "disk_copy_ns",
|
|
215
|
-
"mapped_copy_bytes", "mapped_copy_ns",
|
|
216
|
-
"trims"}],
|
|
217
|
-
# host_copy_* counts every copy from host memory with no staging: the pinned tier's and the
|
|
218
|
-
# mappings' (mapped_copy_* is that part of it; subtract it for the tier alone, whose rate a
|
|
219
|
-
# reader of the host pair otherwise sees blended). host + disk copies are every byte that
|
|
220
|
-
# landed on the device. A pinned fill served by the page cache counts in fill_bytes and
|
|
221
|
-
# cached_bytes, never in disk_read_bytes.
|
|
204
|
+
"mapped_copy_bytes", "mapped_copy_ns",
|
|
205
|
+
"misses", "evictions", "evicted_bytes", "trims"}],
|
|
222
206
|
"sets": [{"id", "name", "nbytes", "regions", "host_ready_bytes", "host_memfd_bytes",
|
|
223
207
|
"evictions", "evicted_bytes", "devices": [{"ordinal", "ready_bytes"}]}],
|
|
224
208
|
"stages": [{"ws", "device", "tag", "acquires", "skipped", "misses", "late", "late_bytes",
|
|
@@ -297,17 +281,19 @@ an unclosed plane ends.
|
|
|
297
281
|
|
|
298
282
|
## Changelog
|
|
299
283
|
|
|
284
|
+
- v3.4 2026-10-02: the mapped device copy of v3.3 is removed; a device fill is again pinned
|
|
285
|
+
tier, else after a filling tier, else staging. In real first loads it never acted at full
|
|
286
|
+
CPU (the tier was ready first) and was slower where it did (L4 held to 4 CPUs, warm cache:
|
|
287
|
+
SDXL load to denoise 6.44 s -> 7.54 s, Anima 5.54 s -> 8.31 s): the pinned fill copied the
|
|
288
|
+
same bytes beside it. `mapped_copy_bytes`, `mapped_copy_ns` and `map_refused` stay in
|
|
289
|
+
`stats()` and read 0. Kept from v3.3: the page-cache query that answers on overlayfs and
|
|
290
|
+
where seccomp refuses `cachestat` (a pinned fill there copies cached bytes instead of
|
|
291
|
+
re-reading them), and `stats()["host"]["disk_read_bytes"]`.
|
|
292
|
+
|
|
300
293
|
- v3.3 2026-10-02: `stats()["host"]["disk_read_bytes"]` exists. The plane has counted it
|
|
301
294
|
since v3.1 and this file listed it, but the Python facade never carried it: a caller read 0
|
|
302
|
-
whatever the disk did, so a "nothing read from disk" check on it could not fail.
|
|
303
|
-
|
|
304
|
-
- v3.3 2026-10-02 (warm loads bound by the plane's own host copy): a device `want` of bytes
|
|
305
|
-
the page cache holds copies straight from mappings of their objects, beside any pinned
|
|
306
|
-
fill. `host_copy_*` counts them with the pinned tier's copies (host + disk copies stay every
|
|
307
|
-
byte that landed on the device); `mapped_copy_*` is their part of it. The cache query now also answers where `cachestat` cannot:
|
|
308
|
-
overlayfs and a seccomp filter that refuses it. Pod-visible: there the pinned fill copies
|
|
309
|
-
freshly downloaded weights from the page cache where it re-read them by O_DIRECT, so the
|
|
310
|
-
tier and the cache copy coexist until the kernel reclaims the cache.
|
|
295
|
+
whatever the disk did, so a "nothing read from disk" check on it could not fail. (v3.3 also
|
|
296
|
+
added a device copy from page-cache mappings; v3.4 removed it.)
|
|
311
297
|
|
|
312
298
|
- v3.2 2026-10-02 (W3: fd and thread growth per weight set): a plane collected unclosed or
|
|
313
299
|
poisoned is torn down at its last handle (threads, device memory, pinned memfds) once its
|
|
@@ -166,7 +166,7 @@ fn submit(inner: &Arc<Inner>, c: &mut Core, id: u64, k: u64, off: u64, waits: Ve
|
|
|
166
166
|
c.timings.push_back(Timing {
|
|
167
167
|
dev: d,
|
|
168
168
|
ws: ws.0,
|
|
169
|
-
kind: TimingKind::Copy {
|
|
169
|
+
kind: TimingKind::Copy { from_host },
|
|
170
170
|
a: cp.start.clone(),
|
|
171
171
|
b: cp.ready.clone(),
|
|
172
172
|
bytes: cp.bytes,
|
|
@@ -11,7 +11,7 @@ use std::sync::{Arc, Condvar, Mutex};
|
|
|
11
11
|
use std::thread::JoinHandle;
|
|
12
12
|
|
|
13
13
|
use crate::cuda::{self, AccessDesc, AllocationProp, CUcontext, CUevent, CUstream, CtxGuard, Handle};
|
|
14
|
-
use crate::io::{Batch, Dst, IoTally,
|
|
14
|
+
use crate::io::{Batch, Dst, IoTally, Mode, Readers, Source};
|
|
15
15
|
use crate::layout::{Item, ItemSource, REGION_ALIGN};
|
|
16
16
|
use crate::{Error, Result};
|
|
17
17
|
|
|
@@ -368,31 +368,14 @@ pub enum CopySrc {
|
|
|
368
368
|
items: Vec<Item>,
|
|
369
369
|
base: u64,
|
|
370
370
|
},
|
|
371
|
-
/// Bytes the page cache holds, read through mappings of their objects: no host copy. An
|
|
372
|
-
/// object that cannot be mapped sends the whole copy through staging instead.
|
|
373
|
-
Mapped {
|
|
374
|
-
source: Arc<Source>,
|
|
375
|
-
items: Vec<Item>,
|
|
376
|
-
base: u64,
|
|
377
|
-
},
|
|
378
371
|
}
|
|
379
372
|
unsafe impl Send for CopySrc {}
|
|
380
373
|
|
|
381
|
-
/// Where a copy's bytes came from, for its counters.
|
|
382
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
383
|
-
pub enum CopyFrom {
|
|
384
|
-
Pinned,
|
|
385
|
-
Disk,
|
|
386
|
-
Mapped,
|
|
387
|
-
}
|
|
388
|
-
|
|
389
374
|
pub struct Copied {
|
|
390
375
|
pub ready: EvRef,
|
|
391
376
|
pub start: EvRef,
|
|
392
377
|
pub bytes: u64,
|
|
393
|
-
pub
|
|
394
|
-
/// Host time before `start` that belongs to the copy: mapping and faulting its source in.
|
|
395
|
-
pub host_ns: u64,
|
|
378
|
+
pub from_host: bool,
|
|
396
379
|
}
|
|
397
380
|
|
|
398
381
|
pub struct CopyJob {
|
|
@@ -491,16 +474,8 @@ fn mover_main(m: MoverCtx, rx: Receiver<CopyJob>, ok: Sender<Result<()>>) {
|
|
|
491
474
|
};
|
|
492
475
|
while let Ok(job) = rx.recv() {
|
|
493
476
|
let done = job.done;
|
|
494
|
-
let
|
|
495
|
-
// The driver stages a pageable source after the call returns, so a mapped copy's
|
|
496
|
-
// mappings outlive its event. The region is reported first: its consumer queues behind
|
|
497
|
-
// the event with no host wait.
|
|
498
|
-
let mapped = r.as_ref().ok().filter(|_| !maps.is_empty()).map(|cp| cp.ready.clone());
|
|
477
|
+
let r = run_copy(&m, stream, &mut bounces, job.waits, job.dst, job.len, job.src);
|
|
499
478
|
done(r);
|
|
500
|
-
match mapped.map(|ready| ready.sync()) {
|
|
501
|
-
Some(Err(_)) => std::mem::forget(maps), // the copy's end is unknown: it may still read them
|
|
502
|
-
_ => drop(maps),
|
|
503
|
-
}
|
|
504
479
|
}
|
|
505
480
|
if let Ok(d) = cuda::driver() {
|
|
506
481
|
// SAFETY: our stream and buffers; the stream is drained first.
|
|
@@ -523,9 +498,6 @@ struct Waiter {
|
|
|
523
498
|
/// no reader still writes a bounce: a failed copy is drained before it is reported, so no
|
|
524
499
|
/// caller unmaps, unregisters or reuses a range a copy still touches. A drain that itself fails
|
|
525
500
|
/// is ambiguous device state: `Poisoned`.
|
|
526
|
-
///
|
|
527
|
-
/// The mappings a queued mapped copy reads come back with it, for the caller to hold past its
|
|
528
|
-
/// event. A failed one's are unmapped here, after the drain, and never if the drain failed.
|
|
529
501
|
fn run_copy(
|
|
530
502
|
m: &MoverCtx,
|
|
531
503
|
stream: CUstream,
|
|
@@ -534,36 +506,20 @@ fn run_copy(
|
|
|
534
506
|
dst: u64,
|
|
535
507
|
len: u64,
|
|
536
508
|
src: CopySrc,
|
|
537
|
-
) ->
|
|
538
|
-
let
|
|
539
|
-
let (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// Nothing is queued yet: the same bytes go through the staging buffers.
|
|
544
|
-
m.tally.map_refused.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
545
|
-
(CopySrc::Disk { source, items, base }, Vec::new())
|
|
546
|
-
}
|
|
547
|
-
},
|
|
548
|
-
src => (src, Vec::new()),
|
|
549
|
-
};
|
|
550
|
-
let host_ns = if maps.is_empty() { 0 } else { began.elapsed().as_nanos() as u64 };
|
|
551
|
-
let r = enqueue_copy(m, stream, bounces, waits, dst, len, src, &maps).map(|cp| Copied { host_ns, ..cp });
|
|
552
|
-
let Err(e) = r else { return (r, maps) };
|
|
553
|
-
let drained = crate::faults::hit("drain").and_then(|_| cuda::driver()).and_then(|d| {
|
|
554
|
-
// SAFETY: our own stream.
|
|
509
|
+
) -> Result<Copied> {
|
|
510
|
+
let r = enqueue_copy(m, stream, bounces, waits, dst, len, src);
|
|
511
|
+
let Err(e) = r else { return r };
|
|
512
|
+
let d = cuda::driver()?;
|
|
513
|
+
// SAFETY: our own stream.
|
|
514
|
+
let drained = crate::faults::hit("drain").and_then(|_| {
|
|
555
515
|
cuda::check("cuStreamSynchronize", unsafe { (d.stream_synchronize)(stream) })
|
|
556
516
|
});
|
|
557
517
|
match drained {
|
|
558
|
-
Ok(()) =>
|
|
559
|
-
Err(drain) => {
|
|
560
|
-
std::mem::forget(maps); // a copy may still read them
|
|
561
|
-
(Err(Error::Poisoned(format!("a failed copy ({e}) could not be drained: {drain}"))), Vec::new())
|
|
562
|
-
}
|
|
518
|
+
Ok(()) => Err(e),
|
|
519
|
+
Err(drain) => Err(Error::Poisoned(format!("a failed copy ({e}) could not be drained: {drain}"))),
|
|
563
520
|
}
|
|
564
521
|
}
|
|
565
522
|
|
|
566
|
-
#[allow(clippy::too_many_arguments)]
|
|
567
523
|
fn enqueue_copy(
|
|
568
524
|
m: &MoverCtx,
|
|
569
525
|
stream: CUstream,
|
|
@@ -572,7 +528,6 @@ fn enqueue_copy(
|
|
|
572
528
|
dst: u64,
|
|
573
529
|
len: u64,
|
|
574
530
|
src: CopySrc,
|
|
575
|
-
maps: &[Mapping],
|
|
576
531
|
) -> Result<Copied> {
|
|
577
532
|
let d = cuda::driver()?;
|
|
578
533
|
let start = m.events.get(true)?;
|
|
@@ -595,22 +550,7 @@ fn enqueue_copy(
|
|
|
595
550
|
})?;
|
|
596
551
|
crate::faults::hit("host-copy-recorded")?;
|
|
597
552
|
ready.record(stream)?;
|
|
598
|
-
Ok(Copied { ready, start, bytes: len,
|
|
599
|
-
}
|
|
600
|
-
CopySrc::Mapped { items, base, .. } => {
|
|
601
|
-
wait_all(&waits)?;
|
|
602
|
-
crate::faults::gate("copy", stream)?;
|
|
603
|
-
start.record(stream)?;
|
|
604
|
-
for (it, map) in items.iter().zip(maps).filter(|(it, _)| it.len > 0) {
|
|
605
|
-
crate::faults::hit("mapped-copy")?;
|
|
606
|
-
// SAFETY: the mapping covers `it.len` bytes and the mover keeps it past this
|
|
607
|
-
// copy's event.
|
|
608
|
-
cuda::check("cuMemcpyHtoDAsync", unsafe {
|
|
609
|
-
(d.memcpy_htod_async)(dst + (it.offset - base), map.ptr() as *const libc::c_void, it.len as usize, stream)
|
|
610
|
-
})?;
|
|
611
|
-
}
|
|
612
|
-
ready.record(stream)?;
|
|
613
|
-
Ok(Copied { ready, start, bytes: len, from: CopyFrom::Mapped, host_ns: 0 })
|
|
553
|
+
Ok(Copied { ready, start, bytes: len, from_host: true })
|
|
614
554
|
}
|
|
615
555
|
CopySrc::Disk { source, items, base } => {
|
|
616
556
|
let cap = m.staging.1;
|
|
@@ -697,7 +637,7 @@ fn enqueue_copy(
|
|
|
697
637
|
start.record(stream)?;
|
|
698
638
|
}
|
|
699
639
|
ready.record(stream)?;
|
|
700
|
-
Ok(Copied { ready, start, bytes: len,
|
|
640
|
+
Ok(Copied { ready, start, bytes: len, from_host: false })
|
|
701
641
|
}
|
|
702
642
|
}
|
|
703
643
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//! Fault points on the copy
|
|
2
|
-
//!
|
|
1
|
+
//! Fault points on the copy path, armed only by tests (cargo feature `faults`). Without the
|
|
2
|
+
//! feature every point is a no-op.
|
|
3
3
|
|
|
4
4
|
#[cfg(feature = "faults")]
|
|
5
5
|
mod imp {
|
|
@@ -74,24 +74,12 @@ mod imp {
|
|
|
74
74
|
// SAFETY: our stream; the host function only waits on the flag.
|
|
75
75
|
cuda::check("cuLaunchHostFunc", unsafe { (d.launch_host_func)(stream, hold, p) })
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
/// Holds the calling thread at `point` until the gate armed there opens.
|
|
79
|
-
pub(crate) fn wait(point: &'static str) {
|
|
80
|
-
let f = {
|
|
81
|
-
let mut g = GATES.lock().unwrap();
|
|
82
|
-
let Some(i) = g.iter().position(|(p, _)| *p == point) else { return };
|
|
83
|
-
g.remove(i).1
|
|
84
|
-
};
|
|
85
|
-
while !f.load(Ordering::Acquire) {
|
|
86
|
-
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
77
|
}
|
|
90
78
|
|
|
91
79
|
#[cfg(feature = "faults")]
|
|
92
80
|
pub use imp::{arm, arm_gate, Gate};
|
|
93
81
|
#[cfg(feature = "faults")]
|
|
94
|
-
pub(crate) use imp::{gate, hit
|
|
82
|
+
pub(crate) use imp::{gate, hit};
|
|
95
83
|
|
|
96
84
|
#[cfg(not(feature = "faults"))]
|
|
97
85
|
#[inline(always)]
|
|
@@ -104,7 +92,3 @@ pub(crate) fn hit(_point: &'static str) -> crate::Result<()> {
|
|
|
104
92
|
pub(crate) fn gate(_point: &'static str, _stream: crate::cuda::CUstream) -> crate::Result<()> {
|
|
105
93
|
Ok(())
|
|
106
94
|
}
|
|
107
|
-
|
|
108
|
-
#[cfg(not(feature = "faults"))]
|
|
109
|
-
#[inline(always)]
|
|
110
|
-
pub(crate) fn wait(_point: &'static str) {}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
//! descriptors the plane opens through the store's own verification (`open_verified`). A
|
|
3
3
|
//! pinned-tier fill copies what the page cache already holds and reads the rest by O_DIRECT
|
|
4
4
|
//! into the destination (no second host copy), else through `tensorfs_core::read::read_into`.
|
|
5
|
-
//!
|
|
6
|
-
//! host copy at all. Each mode's bytes are counted.
|
|
5
|
+
//! Each mode's bytes are counted.
|
|
7
6
|
|
|
8
7
|
use std::collections::HashMap;
|
|
9
8
|
use std::ffi::CString;
|
|
@@ -30,8 +29,6 @@ pub struct IoTally {
|
|
|
30
29
|
pub inline_bytes: AtomicU64,
|
|
31
30
|
/// Objects whose filesystem refused O_DIRECT: their bytes went buffered.
|
|
32
31
|
pub direct_refused: AtomicU64,
|
|
33
|
-
/// Device copies whose objects could not be mapped: they went through staging.
|
|
34
|
-
pub map_refused: AtomicU64,
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
/// How a fill wants its bytes: `Cache` for the pinned tier (O_DIRECT for what the page cache
|
|
@@ -45,94 +42,8 @@ pub enum Mode {
|
|
|
45
42
|
struct Fds {
|
|
46
43
|
buffered: File,
|
|
47
44
|
direct: Option<File>,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/// What the plane may do with an object by its filesystem. A fault on a mapping is a signal,
|
|
52
|
-
/// not an error, so only local filesystems are mapped.
|
|
53
|
-
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
54
|
-
enum Fs {
|
|
55
|
-
Local,
|
|
56
|
-
/// Local too, but its files report no page cache of their own (a container's root).
|
|
57
|
-
Overlay,
|
|
58
|
-
Other,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
impl Fs {
|
|
62
|
-
fn of(f: &File) -> Fs {
|
|
63
|
-
// SAFETY: out-param of the right type on our own descriptor.
|
|
64
|
-
let mut fs: libc::statfs = unsafe { std::mem::zeroed() };
|
|
65
|
-
if unsafe { libc::fstatfs(f.as_raw_fd(), &mut fs) } != 0 {
|
|
66
|
-
return Fs::Other;
|
|
67
|
-
}
|
|
68
|
-
#[allow(clippy::unnecessary_cast)] // f_type is not an i64 on every target
|
|
69
|
-
match fs.f_type as i64 {
|
|
70
|
-
0x794c7630 => Fs::Overlay,
|
|
71
|
-
// ext4, xfs, btrfs, tmpfs
|
|
72
|
-
0xEF53 | 0x58465342 | 0x9123683E | 0x01021994 => Fs::Local,
|
|
73
|
-
_ => Fs::Other,
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// One item's bytes for a device copy, with no host copy: a populated read-only mapping of its
|
|
79
|
-
/// object range (unmapped on drop), or the inline bytes themselves.
|
|
80
|
-
pub enum Mapping {
|
|
81
|
-
Inline(Arc<[u8]>),
|
|
82
|
-
File { base: *mut u8, span: usize, at: usize },
|
|
83
|
-
}
|
|
84
|
-
// SAFETY: a read-only mapping (or shared immutable bytes) owned by whoever holds the value.
|
|
85
|
-
unsafe impl Send for Mapping {}
|
|
86
|
-
|
|
87
|
-
impl Mapping {
|
|
88
|
-
pub fn ptr(&self) -> *const u8 {
|
|
89
|
-
match self {
|
|
90
|
-
Mapping::Inline(b) => b.as_ptr(),
|
|
91
|
-
// SAFETY: `at` lies inside the mapping.
|
|
92
|
-
Mapping::File { base, at, .. } => unsafe { base.add(*at) },
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/// `[off, off+len)` of `f`, which the page cache must hold: a cold range is refused, never
|
|
97
|
-
/// read from disk through the mapping. Populated and checked again, so the copy that
|
|
98
|
-
/// follows is not the range's first reader.
|
|
99
|
-
fn file(f: &File, off: u64, len: u64) -> Result<Mapping> {
|
|
100
|
-
if !resident(f, off, len) {
|
|
101
|
-
return Err(Error::Io("the page cache let go of the range".into()));
|
|
102
|
-
}
|
|
103
|
-
let at = (off % 4096) as usize;
|
|
104
|
-
let span = at + len as usize;
|
|
105
|
-
// SAFETY: a fresh read-only shared mapping of our descriptor.
|
|
106
|
-
let p = unsafe {
|
|
107
|
-
libc::mmap(
|
|
108
|
-
std::ptr::null_mut(),
|
|
109
|
-
span,
|
|
110
|
-
libc::PROT_READ,
|
|
111
|
-
libc::MAP_SHARED | libc::MAP_POPULATE,
|
|
112
|
-
f.as_raw_fd(),
|
|
113
|
-
(off - at as u64) as libc::off_t,
|
|
114
|
-
)
|
|
115
|
-
};
|
|
116
|
-
if p == libc::MAP_FAILED {
|
|
117
|
-
return Err(Error::Io(format!("mmap: {}", std::io::Error::last_os_error())));
|
|
118
|
-
}
|
|
119
|
-
let m = Mapping::File { base: p as *mut u8, span, at };
|
|
120
|
-
let mut pages = vec![0u8; span.div_ceil(4096)];
|
|
121
|
-
// SAFETY: the vector holds one byte per page of the mapping.
|
|
122
|
-
if unsafe { libc::mincore(p, span, pages.as_mut_ptr()) } != 0 || pages.iter().any(|b| b & 1 == 0) {
|
|
123
|
-
return Err(Error::Io("the page cache let go of a mapped range".into()));
|
|
124
|
-
}
|
|
125
|
-
Ok(m)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
impl Drop for Mapping {
|
|
130
|
-
fn drop(&mut self) {
|
|
131
|
-
if let Mapping::File { base, span, .. } = self {
|
|
132
|
-
// SAFETY: our own mapping, no longer read by anyone (the holder's contract).
|
|
133
|
-
unsafe { libc::munmap(*base as *mut libc::c_void, *span) };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
45
|
+
/// On overlayfs (a container's root): the file reports no page cache of its own.
|
|
46
|
+
overlay: bool,
|
|
136
47
|
}
|
|
137
48
|
|
|
138
49
|
pub struct Source {
|
|
@@ -173,7 +84,16 @@ fn cached(f: &Fds, off: u64, len: u64) -> bool {
|
|
|
173
84
|
if rc == 0 && st.nr_cache >= (off + len).div_ceil(page) - off / page {
|
|
174
85
|
return true;
|
|
175
86
|
}
|
|
176
|
-
(rc != 0 || f.
|
|
87
|
+
(rc != 0 || f.overlay) && resident(&f.buffered, off, len)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn overlay(f: &File) -> bool {
|
|
91
|
+
const OVERLAYFS_SUPER_MAGIC: i64 = 0x794c7630;
|
|
92
|
+
// SAFETY: out-param of the right type on our own descriptor.
|
|
93
|
+
let mut fs: libc::statfs = unsafe { std::mem::zeroed() };
|
|
94
|
+
#[allow(clippy::unnecessary_cast)] // f_type is not an i64 on every target
|
|
95
|
+
let magic = unsafe { libc::fstatfs(f.as_raw_fd(), &mut fs) == 0 }.then_some(fs.f_type as i64);
|
|
96
|
+
magic == Some(OVERLAYFS_SUPER_MAGIC)
|
|
177
97
|
}
|
|
178
98
|
|
|
179
99
|
/// Every page of `[off, off+len)` is in core, by `mincore(2)` over a mapping (never faults).
|
|
@@ -250,50 +170,11 @@ impl Source {
|
|
|
250
170
|
} else {
|
|
251
171
|
None
|
|
252
172
|
};
|
|
253
|
-
let
|
|
254
|
-
let f = Arc::new(Fds { buffered, direct, fs });
|
|
173
|
+
let f = Arc::new(Fds { overlay: overlay(&buffered), buffered, direct });
|
|
255
174
|
self.fds.lock().unwrap().insert(obj.sha256.clone(), f.clone());
|
|
256
175
|
Ok(f)
|
|
257
176
|
}
|
|
258
177
|
|
|
259
|
-
/// Whether the page cache holds every object byte of `items`. An object that will not open
|
|
260
|
-
/// counts as not held: its reader reports the error.
|
|
261
|
-
pub(crate) fn cached(&self, items: &[Item], tally: &IoTally) -> bool {
|
|
262
|
-
items.iter().all(|it| match &it.source {
|
|
263
|
-
ItemSource::Inline(_) => true,
|
|
264
|
-
ItemSource::Object(o) => {
|
|
265
|
-
self.fds(&o.obj, tally).is_ok_and(|f| cached(&f, o.off, it.len))
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/// `items` mapped for a device copy. Refuses (the caller stages instead) an object on a
|
|
271
|
-
/// filesystem where a fault could be a network error, one not at its declared length, and
|
|
272
|
-
/// pages the cache no longer holds.
|
|
273
|
-
pub fn map(&self, items: &[Item], tally: &IoTally) -> Result<Vec<Mapping>> {
|
|
274
|
-
crate::faults::hit("map")?;
|
|
275
|
-
let guard = self.lease.read().unwrap();
|
|
276
|
-
let lease = guard.as_ref().ok_or(Error::Closed)?;
|
|
277
|
-
let one = |it: &Item| -> Result<Mapping> {
|
|
278
|
-
let o = match &it.source {
|
|
279
|
-
ItemSource::Inline(b) => return Ok(Mapping::Inline(b.clone())),
|
|
280
|
-
ItemSource::Object(_) if it.len == 0 => return Ok(Mapping::Inline(Arc::from([]))),
|
|
281
|
-
ItemSource::Object(o) => o,
|
|
282
|
-
};
|
|
283
|
-
lease.covers(o)?;
|
|
284
|
-
let fds = self.fds(&o.obj, tally)?;
|
|
285
|
-
if fds.fs == Fs::Other {
|
|
286
|
-
return Err(Error::Invalid(format!("{}: not on a local filesystem", o.obj.sha256)));
|
|
287
|
-
}
|
|
288
|
-
let size = fds.buffered.metadata().map_err(|e| Error::Io(format!("fstat {}: {e}", o.obj.sha256)))?.len();
|
|
289
|
-
if size != o.obj.length {
|
|
290
|
-
return Err(Error::Io(format!("{} is {size} bytes, declared {}", o.obj.sha256, o.obj.length)));
|
|
291
|
-
}
|
|
292
|
-
Mapping::file(&fds.buffered, o.off, it.len)
|
|
293
|
-
};
|
|
294
|
-
items.iter().map(one).collect()
|
|
295
|
-
}
|
|
296
|
-
|
|
297
178
|
/// Fill `item.len` bytes at `dst`. `Mode::Cache` reads a range the page cache fully
|
|
298
179
|
/// holds by copy and everything else by O_DIRECT when alignment allows, which may DMA up
|
|
299
180
|
/// to the next `PART_ALIGN` boundary past the item: the layout reserves that as padding.
|