tensorfs 0.3.90__tar.gz → 0.3.91__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.
Files changed (204) hide show
  1. {tensorfs-0.3.90 → tensorfs-0.3.91}/Cargo.lock +3 -3
  2. {tensorfs-0.3.90 → tensorfs-0.3.91}/Cargo.toml +1 -1
  3. {tensorfs-0.3.90 → tensorfs-0.3.91}/PKG-INFO +1 -1
  4. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/API.md +16 -5
  5. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/cuda.rs +2 -0
  6. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/cursor.rs +4 -0
  7. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/device.rs +6 -1
  8. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/io.rs +8 -2
  9. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/plane.rs +57 -18
  10. tensorfs-0.3.91/crates/tensorfs-plane/tests/census.rs +189 -0
  11. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/tests/gpu.rs +6 -3
  12. {tensorfs-0.3.90 → tensorfs-0.3.91}/pyproject.toml +1 -1
  13. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/_ext.pyi +1 -0
  14. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/errors.py +2 -0
  15. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/Cargo.toml +0 -0
  16. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/build.rs +0 -0
  17. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  18. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  19. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  20. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  21. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/asset_update.rs +0 -0
  22. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/b64.rs +0 -0
  23. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  24. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  25. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/ensure.rs +0 -0
  26. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  27. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  28. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  29. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  30. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  31. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  32. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  33. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  34. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/canon.rs +0 -0
  35. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/capability.rs +0 -0
  36. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/catalog/lock.rs +0 -0
  37. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/catalog.rs +0 -0
  38. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/cbor.rs +0 -0
  39. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  40. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  41. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/corpus.rs +0 -0
  42. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  43. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
  44. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  45. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  46. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/group_commit_tests.rs +0 -0
  47. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/history_cost_tests.rs +0 -0
  48. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/part_rows_tests.rs +0 -0
  49. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  50. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  51. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  52. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  53. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/derived.rs +0 -0
  54. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/descriptors.rs +0 -0
  55. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/disk.rs +0 -0
  56. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/dtype.rs +0 -0
  57. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/durability.rs +0 -0
  58. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ensure/admission.rs +0 -0
  59. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ensure/flight.rs +0 -0
  60. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ensure.rs +0 -0
  61. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/err.rs +0 -0
  62. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/fetch.rs +0 -0
  63. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/filesystem_identity.rs +0 -0
  64. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/fill.rs +0 -0
  65. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/fit.rs +0 -0
  66. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/gc.rs +0 -0
  67. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/header.rs +0 -0
  68. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/home.rs +0 -0
  69. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ids.rs +0 -0
  70. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  71. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  72. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
  73. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  74. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  75. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  76. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  77. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  78. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/narrow.rs +0 -0
  79. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  80. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/routes/anima.routes.json +0 -0
  81. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/routes/anima.spec.json +0 -0
  82. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/routes/sdxl.routes.json +0 -0
  83. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/routes/sdxl.spec.json +0 -0
  84. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/routes.rs +0 -0
  85. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  86. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  87. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  88. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/jcs.rs +0 -0
  89. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/keyed_roots.rs +0 -0
  90. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/lib.rs +0 -0
  91. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/limits.rs +0 -0
  92. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/machine.rs +0 -0
  93. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/manifest.rs +0 -0
  94. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/meta.rs +0 -0
  95. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/project.rs +0 -0
  96. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/providers.rs +0 -0
  97. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/read.rs +0 -0
  98. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/receipt.rs +0 -0
  99. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/reclaim.rs +0 -0
  100. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/registry.rs +0 -0
  101. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/relation.rs +0 -0
  102. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  103. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/repository.rs +0 -0
  104. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/sha256.rs +0 -0
  105. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  106. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/spec.rs +0 -0
  107. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/staging.rs +0 -0
  108. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/stats.rs +0 -0
  109. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/storage.rs +0 -0
  110. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/store.rs +0 -0
  111. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/api.rs +0 -0
  112. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/decisions.rs +0 -0
  113. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/download.rs +0 -0
  114. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/http.rs +0 -0
  115. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
  116. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  117. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  118. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/memory.rs +0 -0
  119. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  120. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  121. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  122. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/push.rs +0 -0
  123. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/sources.rs +0 -0
  124. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/tests/tail.rs +0 -0
  125. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/transport/tests.rs +0 -0
  126. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/unsynced.rs +0 -0
  127. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/src/vectors.rs +0 -0
  128. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
  129. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/anima_hf_profile.rs +0 -0
  130. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/as_is.rs +0 -0
  131. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/catalog_owner_probe.c +0 -0
  132. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  133. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/civitai_anima.rs +0 -0
  134. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/civitai_sdxl_profile.rs +0 -0
  135. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/ensure.rs +0 -0
  136. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fd_budget.rs +0 -0
  137. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
  138. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fetch_faults.rs +0 -0
  139. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  140. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fit.rs +0 -0
  141. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  142. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  143. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  144. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  145. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  146. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  147. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  148. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
  149. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  150. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  151. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/home.rs +0 -0
  152. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/hub_tls.rs +0 -0
  153. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  154. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  155. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/keyed_roots.rs +0 -0
  156. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/liveness.rs +0 -0
  157. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
  158. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  159. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  160. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  161. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  162. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  163. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  164. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/routed_single_file.rs +0 -0
  165. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  166. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  167. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  168. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/source_download.rs +0 -0
  169. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  170. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  171. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  172. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/staging.rs +0 -0
  173. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  174. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  175. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
  176. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/trust_index.rs +0 -0
  177. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/version.rs +0 -0
  178. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-core/tests/write_through.rs +0 -0
  179. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/Cargo.toml +0 -0
  180. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/bin/g1_vmm.rs +0 -0
  181. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/bin/g2_overlap.rs +0 -0
  182. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/bin/gpu_bench/mod.rs +0 -0
  183. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/faults.rs +0 -0
  184. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/host.rs +0 -0
  185. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/layout.rs +0 -0
  186. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/src/lib.rs +0 -0
  187. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/tests/common/mod.rs +0 -0
  188. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-plane/tests/host_tier.rs +0 -0
  189. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/Cargo.toml +0 -0
  190. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/docs.rs +0 -0
  191. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/ensure.rs +0 -0
  192. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/errors.rs +0 -0
  193. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/ingest.rs +0 -0
  194. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/lib.rs +0 -0
  195. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/plane.rs +0 -0
  196. {tensorfs-0.3.90 → tensorfs-0.3.91}/crates/tensorfs-py/src/store.rs +0 -0
  197. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/__init__.py +0 -0
  198. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/_derived_channel.py +0 -0
  199. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/_source_channel.py +0 -0
  200. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/_tensors.py +0 -0
  201. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/derived.py +0 -0
  202. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/plane.py +0 -0
  203. {tensorfs-0.3.90 → tensorfs-0.3.91}/python/tensorfs/plane.pyi +0 -0
  204. {tensorfs-0.3.90 → tensorfs-0.3.91}/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.90"
452
+ version = "0.3.91"
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.90"
467
+ version = "0.3.91"
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.90"
477
+ version = "0.3.91"
478
478
  dependencies = [
479
479
  "pyo3",
480
480
  "tensorfs-core",
@@ -3,5 +3,5 @@ resolver = "2"
3
3
  members = ["crates/tensorfs-core", "crates/tensorfs-plane", "crates/tensorfs-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.3.90"
6
+ version = "0.3.91"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.90
3
+ Version: 0.3.91
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -1,4 +1,4 @@
1
- # tensorfs.plane — Python API (v3.1, 2026-10-02)
1
+ # tensorfs.plane — Python API (v3.2, 2026-10-02)
2
2
 
3
3
  The weight plane moves bytes; cozy-runtime decides. The plane never measures free memory,
4
4
  never picks victims among equals, never reorders a schedule. It ships inside the `tensorfs`
@@ -240,10 +240,16 @@ stream cannot drain is `Poisoned`.
240
240
  **Poison is latched plane-wide**: an ambiguous driver outcome (a failed unmap or rollback, an
241
241
  undrainable copy failure, a driver mapping that disagrees with the books) or a lease dropped
242
242
  unreleased makes every later call raise `Poisoned` with the first cause; the runtime replaces
243
- the executor. A poisoned plane never unmaps, unregisters or frees a range (copies may still
244
- touch it): `ws.close()`, `cur.close()` and `p.close()` raise `Poisoned` and the process keeps
245
- the ranges until it exits. Capacity never poisons (`BudgetExceeded`, `Shortfall` leave the
246
- plane usable).
243
+ the executor. A poisoned plane never unmaps, unregisters or frees a range while it lives
244
+ (copies may still touch it): `ws.close()`, `cur.close()` and `p.close()` raise `Poisoned`.
245
+ Capacity never poisons (`BudgetExceeded`, `Shortfall` leave the plane usable).
246
+
247
+ **A plane collected without `close()`** (its handles dropped in any order, or poisoned) is
248
+ torn down when its last handle goes: its threads stop, and once every device has run all
249
+ queued work, its device ranges are unmapped, its handles released and its pinned RAM let go as
250
+ `close()` would. Only a device that cannot be synchronized keeps its ranges and the pinned
251
+ tier until the process exits. Closing is still the contract: collection order decides when
252
+ an unclosed plane ends.
247
253
 
248
254
  ## Host tier across processes
249
255
 
@@ -274,6 +280,11 @@ plane usable).
274
280
 
275
281
  ## Changelog
276
282
 
283
+ - v3.2 2026-10-02 (W3: fd and thread growth per weight set): a plane collected unclosed or
284
+ poisoned is torn down at its last handle (threads, device memory, pinned memfds) once its
285
+ devices are synchronized; before, its reader threads stayed and a poisoned plane kept its
286
+ memfds for the life of the process.
287
+
277
288
  - v3.1 2026-10-02 (Xid 31; W3 run 2675): a failed copy is reported only after its stream drained;
278
289
  an undrainable one poisons; a poisoned plane never unmaps (close raises `Poisoned`).
279
290
  `verify_bytes()` after release raises `Invalid`. Pinned-tier claims: close and eviction punch
@@ -106,6 +106,7 @@ pub struct Driver {
106
106
  pub ctx_get_current: F<*mut CUcontext>,
107
107
  pub ctx_set_current: F<CUcontext>,
108
108
  pub ctx_synchronize: unsafe extern "C" fn() -> CUresult,
109
+ pub mem_get_info: unsafe extern "C" fn(*mut usize, *mut usize) -> CUresult,
109
110
  pub stream_create: unsafe extern "C" fn(*mut CUstream, c_uint) -> CUresult,
110
111
  pub stream_destroy: F<CUstream>,
111
112
  pub stream_synchronize: F<CUstream>,
@@ -189,6 +190,7 @@ impl Driver {
189
190
  ctx_get_current: load!(lib, b"cuCtxGetCurrent"),
190
191
  ctx_set_current: load!(lib, b"cuCtxSetCurrent"),
191
192
  ctx_synchronize: load!(lib, b"cuCtxSynchronize"),
193
+ mem_get_info: load!(lib, b"cuMemGetInfo_v2"),
192
194
  stream_create: load!(lib, b"cuStreamCreate"),
193
195
  stream_destroy: load!(lib, b"cuStreamDestroy_v2"),
194
196
  stream_synchronize: load!(lib, b"cuStreamSynchronize"),
@@ -72,6 +72,10 @@ pub(crate) struct Cur {
72
72
  }
73
73
 
74
74
  impl Cur {
75
+ /// (device slot, ring base, ring length, ring handles), for a plane torn down unclosed.
76
+ pub(crate) fn into_ring(self) -> (usize, u64, u64, Vec<Chunk>) {
77
+ (self.dev, self.ring_base, self.ring_len, self.ring_chunks)
78
+ }
75
79
  fn streamed_total(&self) -> u64 {
76
80
  if self.total == u64::MAX {
77
81
  return u64::MAX;
@@ -431,8 +431,13 @@ impl Mover {
431
431
  }
432
432
  }
433
433
 
434
- pub fn stop(&self) {
434
+ /// Close the queue: the thread drains its stream, frees its buffers and exits by itself.
435
+ pub fn detach(&self) {
435
436
  self.tx.lock().unwrap().take();
437
+ }
438
+
439
+ pub fn stop(&self) {
440
+ self.detach();
436
441
  if let Some(t) = self.thread.lock().unwrap().take() {
437
442
  let _ = t.join();
438
443
  }
@@ -278,10 +278,16 @@ impl Readers {
278
278
  self.q.1.notify_one();
279
279
  }
280
280
 
281
- /// Finish queued jobs, then stop the threads.
282
- pub fn shutdown(&self) {
281
+ /// Let the threads exit once the queue is empty; waits for nothing (the caller may be one
282
+ /// of them).
283
+ pub fn stop(&self) {
283
284
  self.q.0.lock().unwrap().stop = true;
284
285
  self.q.1.notify_all();
286
+ }
287
+
288
+ /// Finish queued jobs, then stop the threads.
289
+ pub fn shutdown(&self) {
290
+ self.stop();
285
291
  for t in self.threads.lock().unwrap().drain(..) {
286
292
  let _ = t.join();
287
293
  }
@@ -1555,33 +1555,72 @@ impl Drop for Plane {
1555
1555
  }
1556
1556
 
1557
1557
  impl Drop for Inner {
1558
- /// The last reference can go while copies are still on the device: a plane dropped with a
1559
- /// fill queued ends inside that copy's completion. Pinned ranges are unregistered and
1560
- /// unmapped only after every copy reading them; a poisoned plane (or one whose events
1561
- /// cannot be waited) keeps them for the life of the process instead. Device ranges are
1562
- /// never unmapped here.
1558
+ /// The last reference can go without `close()`: handles collected in any order, a plane
1559
+ /// dropped with a fill queued (it ends inside that copy's completion), a poisoned plane.
1560
+ /// Its threads stop, and once every device has run all queued work its ranges are unmapped,
1561
+ /// its handles released and its pinned tier let go, exactly as `close()` would. A device
1562
+ /// that cannot be synchronized keeps its ranges and the pinned tier for the life of the
1563
+ /// process: a copy may still touch them.
1563
1564
  fn drop(&mut self) {
1565
+ self.readers.stop();
1566
+ for dev in &self.devices {
1567
+ for m in &dev.movers {
1568
+ m.detach();
1569
+ }
1570
+ }
1564
1571
  let c = self.core.get_mut().unwrap_or_else(|p| p.into_inner());
1565
- let mut settled = c.poisoned.is_none();
1566
- for w in c.sets.iter_mut().flatten() {
1567
- for h in &mut w.hregs {
1568
- settled &= sync_all(&mut h.pending).is_ok();
1572
+ if teardown(&self.devices, c).is_err() {
1573
+ for w in c.sets.iter().flatten() {
1574
+ std::mem::forget(w.host.clone());
1569
1575
  }
1570
- for dw in w.devs.iter_mut().flatten() {
1571
- for reg in &mut dw.regs {
1572
- if let DState::Filling(ev) = &reg.state {
1573
- settled &= ev.sync().is_ok();
1574
- }
1575
- settled &= sync_all(&mut reg.pending).is_ok();
1576
+ }
1577
+ }
1578
+ }
1579
+
1580
+ /// Free everything the books still hold, after the devices ran all queued work.
1581
+ fn teardown(devices: &[DeviceRt], c: &mut Core) -> Result<()> {
1582
+ for dev in devices {
1583
+ let _g = dev.bind()?;
1584
+ // SAFETY: the device's context is bound.
1585
+ cuda::check("cuCtxSynchronize", unsafe { (cuda::driver()?.ctx_synchronize)() })?;
1586
+ }
1587
+ let mut rings: Vec<(usize, u64, u64, Vec<Chunk>)> = c.cursors.drain().map(|(_, k)| k.into_ring()).collect();
1588
+ for (d, dev) in devices.iter().enumerate() {
1589
+ let _g = dev.bind()?;
1590
+ let mut chunks = Vec::new();
1591
+ for (_, base, len, held) in rings.iter_mut().filter(|r| r.0 == d && r.2 > 0) {
1592
+ device::unmap_span(*base, *len)?;
1593
+ device::free_va(*base, *len)?;
1594
+ chunks.append(held);
1595
+ }
1596
+ for w in c.sets.iter_mut().flatten() {
1597
+ let Some(dw) = w.devs[d].take() else { continue };
1598
+ for (reg, region) in dw.regs.into_iter().zip(&w.layout.regions) {
1599
+ if !reg.chunks.is_empty() {
1600
+ device::unmap_span(dw.base + region.offset, region.span)?;
1601
+ chunks.extend(reg.chunks);
1576
1602
  }
1577
1603
  }
1604
+ device::free_va(dw.base, w.layout.nbytes)?;
1578
1605
  }
1579
- if !settled {
1580
- for w in c.sets.iter().flatten() {
1581
- std::mem::forget(w.host.clone());
1606
+ c.arenas[d].put_idle(chunks);
1607
+ c.arenas[d].release_idle()?;
1608
+ }
1609
+ let first = devices.first().map(|d| d.ctx);
1610
+ for w in c.sets.iter_mut().flatten() {
1611
+ for (r, (h, region)) in w.hregs.iter_mut().zip(&w.layout.regions).enumerate() {
1612
+ if h.registered {
1613
+ let _g = first.map(CtxGuard::enter).transpose()?;
1614
+ w.host.unregister(region.offset)?;
1615
+ h.registered = false;
1616
+ }
1617
+ if matches!(h.state, HState::Ready) {
1618
+ w.host.release(r as u32, region.offset, region.span)?;
1619
+ h.state = HState::Absent;
1582
1620
  }
1583
1621
  }
1584
1622
  }
1623
+ Ok(())
1585
1624
  }
1586
1625
 
1587
1626
  #[derive(Debug, Clone, Default)]
@@ -0,0 +1,189 @@
1
+ //! What a plane leaves behind in its process: descriptors, plane memfds, store files and
2
+ //! threads return to the baseline however a plane ends: closed, or collected unclosed with a
3
+ //! handle outliving it. Alone in its binary: the census is process-wide.
4
+
5
+ mod common;
6
+
7
+ use std::time::Duration;
8
+
9
+ use common::*;
10
+ use tensorfs_plane::cuda::{self, CUstream, CtxGuard};
11
+ use tensorfs_plane::{Error, Plane, PlaneConfig, Tier};
12
+
13
+ #[derive(Debug, Clone, PartialEq, Eq)]
14
+ struct Census {
15
+ fds: usize,
16
+ memfds: usize,
17
+ store: usize,
18
+ threads: usize,
19
+ }
20
+
21
+ fn census(fx: &Fixture) -> Census {
22
+ let root = fx.root.to_string_lossy().to_string();
23
+ let links: Vec<String> = std::fs::read_dir("/proc/self/fd")
24
+ .unwrap()
25
+ .filter_map(|e| std::fs::read_link(e.ok()?.path()).ok())
26
+ .map(|p| p.to_string_lossy().to_string())
27
+ .collect();
28
+ let threads = std::fs::read_dir("/proc/self/task")
29
+ .unwrap()
30
+ .filter_map(|e| std::fs::read_to_string(e.ok()?.path().join("comm")).ok())
31
+ .filter(|name| name.starts_with("tfs-plane"))
32
+ .count();
33
+ Census {
34
+ // The directory read holds one descriptor of its own.
35
+ fds: links.len() - 1,
36
+ memfds: links.iter().filter(|l| l.contains("memfd:tfs-plane")).count(),
37
+ store: links.iter().filter(|l| l.starts_with(&root)).count(),
38
+ threads,
39
+ }
40
+ }
41
+
42
+ /// The baseline: the census once no plane thread is left (a joined thread's task entry can
43
+ /// outlive the join by a moment).
44
+ fn baseline(fx: &Fixture) -> Census {
45
+ let quiet = Census { threads: 0, ..census(fx) };
46
+ let seen = settled(fx, &quiet);
47
+ assert_eq!(seen.threads, 0, "plane threads outlived every plane");
48
+ seen
49
+ }
50
+
51
+ /// Threads of an unclosed plane exit on their own: wait for the census to settle.
52
+ fn settled(fx: &Fixture, want: &Census) -> Census {
53
+ let mut seen = census(fx);
54
+ for _ in 0..2000 {
55
+ if &seen == want {
56
+ break;
57
+ }
58
+ std::thread::sleep(Duration::from_millis(1));
59
+ seen = census(fx);
60
+ }
61
+ seen
62
+ }
63
+
64
+ #[test]
65
+ fn host_planes_leave_no_descriptors_or_threads() {
66
+ let fx = Fixture::new("census");
67
+ let cycle = |closed: bool| {
68
+ let plane = host_only();
69
+ let a = fx.register(&plane, None);
70
+ let b = fx.register(&plane, Some(plane.host_fd(a).unwrap()));
71
+ plane.set_pinned_budget(1 << 30).unwrap();
72
+ let ticket = plane.want(a, Tier::Pinned, None, 0, false).unwrap();
73
+ ticket.wait().unwrap();
74
+ plane.want(b, Tier::Pinned, Some(&[0]), 0, false).unwrap().wait().unwrap();
75
+ if closed {
76
+ plane.close_ws(a).unwrap();
77
+ plane.close().unwrap();
78
+ }
79
+ // Unclosed: a ticket outlives the handle, so the plane ends when the ticket goes.
80
+ drop(plane);
81
+ drop(ticket);
82
+ };
83
+ cycle(true);
84
+ let base = baseline(&fx);
85
+ for closed in [true, false, false, true, false] {
86
+ cycle(closed);
87
+ assert_eq!(settled(&fx, &base), base, "after a plane {}", if closed { "closed" } else { "dropped unclosed" });
88
+ }
89
+ }
90
+
91
+ const DEV: i32 = 0;
92
+
93
+ struct Stream(CUstream, #[allow(dead_code)] CtxGuard);
94
+ impl Stream {
95
+ fn new() -> Stream {
96
+ let (_, ctx) = cuda::primary_context(DEV).unwrap();
97
+ let g = CtxGuard::enter(ctx).unwrap();
98
+ let mut s: CUstream = std::ptr::null_mut();
99
+ let d = cuda::driver().unwrap();
100
+ cuda::check("cuStreamCreate", unsafe { (d.stream_create)(&mut s, cuda::STREAM_NON_BLOCKING) }).unwrap();
101
+ Stream(s, g)
102
+ }
103
+ fn raw(&self) -> u64 {
104
+ self.0 as u64
105
+ }
106
+ }
107
+ impl Drop for Stream {
108
+ fn drop(&mut self) {
109
+ let d = cuda::driver().unwrap();
110
+ unsafe {
111
+ (d.stream_synchronize)(self.0);
112
+ (d.stream_destroy)(self.0);
113
+ }
114
+ }
115
+ }
116
+
117
+ #[derive(Debug, Clone, Copy, PartialEq)]
118
+ enum End {
119
+ Closed,
120
+ /// The plane handle goes first; a view of leased bytes holds the last reference.
121
+ Unclosed,
122
+ /// A lease is dropped unreleased: `close` refuses, the plane is collected poisoned.
123
+ Poisoned,
124
+ }
125
+
126
+ fn device_cycle(fx: &Fixture, end: End) {
127
+ let p = Plane::open(PlaneConfig {
128
+ devices: vec![DEV],
129
+ readers: 4,
130
+ staging_buffers: 3,
131
+ staging_bytes: 16 << 20,
132
+ ..Default::default()
133
+ })
134
+ .unwrap();
135
+ let ws = fx.register(&p, None);
136
+ let s = Stream::new();
137
+ p.set_pinned_budget(1 << 30).unwrap();
138
+ p.want(ws, Tier::Pinned, Some(&[0, 1]), 0, false).unwrap().wait().unwrap();
139
+ p.set_vram_budget(DEV, 1 << 30).unwrap();
140
+ p.want(ws, Tier::Device(DEV), Some(&[0]), 0, false).unwrap().wait().unwrap();
141
+ let home = p.acquire(ws, DEV, 0, s.raw(), None).unwrap();
142
+ let view = home.view().unwrap();
143
+ // Region 1 streams from the pinned tier, region 2 from disk through staging.
144
+ let cur = p.stream(ws, DEV, &[1, 2], 1, 1, 0, "census", None).unwrap();
145
+ cur.acquire(1, s.raw()).unwrap().release(s.raw()).unwrap();
146
+ let last = cur.acquire(2, s.raw()).unwrap();
147
+ home.release(s.raw()).unwrap();
148
+ match end {
149
+ End::Closed => {
150
+ last.release(s.raw()).unwrap();
151
+ drop(view);
152
+ cur.close().unwrap();
153
+ p.close().unwrap();
154
+ }
155
+ End::Unclosed => {
156
+ last.release(s.raw()).unwrap();
157
+ drop(p);
158
+ drop(cur);
159
+ drop(view);
160
+ }
161
+ End::Poisoned => {
162
+ drop(last);
163
+ drop(view);
164
+ drop(cur);
165
+ assert!(matches!(p.close(), Err(Error::Poisoned(_))));
166
+ drop(p);
167
+ }
168
+ }
169
+ }
170
+
171
+ #[test]
172
+ #[ignore = "GPU"]
173
+ fn device_planes_leave_no_descriptors_threads_or_device_memory() {
174
+ let fx = Fixture::new("gpu-census");
175
+ let free = || {
176
+ let (_, ctx) = cuda::primary_context(DEV).unwrap();
177
+ let _g = CtxGuard::enter(ctx).unwrap();
178
+ let (mut free, mut total) = (0usize, 0usize);
179
+ cuda::check("cuMemGetInfo", unsafe { (cuda::driver().unwrap().mem_get_info)(&mut free, &mut total) }).unwrap();
180
+ free
181
+ };
182
+ device_cycle(&fx, End::Closed);
183
+ let (base, vram) = (baseline(&fx), free());
184
+ for end in [End::Closed, End::Unclosed, End::Unclosed, End::Poisoned, End::Closed, End::Poisoned] {
185
+ device_cycle(&fx, end);
186
+ assert_eq!(settled(&fx, &base), base, "after a plane ended {end:?}");
187
+ assert_eq!(free(), vram, "device memory after a plane ended {end:?}");
188
+ }
189
+ }
@@ -662,7 +662,7 @@ mod copy_faults {
662
662
 
663
663
  #[test]
664
664
  #[ignore = "GPU"]
665
- fn an_undrainable_failure_poisons_and_nothing_is_unmapped_under_it() {
665
+ fn an_undrainable_failure_poisons_and_its_ranges_outlive_the_queued_copies() {
666
666
  let fx = Fixture::new("gpu-undrained");
667
667
  let p = staging_plane();
668
668
  let ws = fx.register(&p, None);
@@ -675,9 +675,12 @@ mod copy_faults {
675
675
  assert!(matches!(r, Err(Error::Poisoned(_))), "{r:?}");
676
676
  assert!(matches!(p.close_ws(ws), Err(Error::Poisoned(_))), "a poisoned plane must not unmap");
677
677
  assert!(matches!(p.drop_regions(ws, Tier::Device(DEV), None), Err(Error::Poisoned(_))));
678
+ // Collecting it waits for the device before anything is unmapped: the copies still
679
+ // queued behind the gate land in mapped memory.
680
+ let opener = open_later(&gate);
678
681
  drop(p);
679
- gate.open();
680
- std::thread::sleep(Duration::from_millis(200));
682
+ assert!(gate.is_open(), "the plane was torn down while its copies still waited on the device");
683
+ opener.join().unwrap();
681
684
  healthy();
682
685
  }
683
686
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "tensorfs"
7
- version = "0.3.90"
7
+ version = "0.3.91"
8
8
  description = "TensorFS — canonical tensor artifact substrate"
9
9
  requires-python = ">=3.12"
10
10
  classifiers = [
@@ -164,6 +164,7 @@ class Refusal(Exception):
164
164
  code: str
165
165
  detail: str
166
166
 
167
+ class AdapterTargetUnsupported(Refusal): ...
167
168
  class AmbiguousClassification(Refusal): ...
168
169
  class ArchiveRefused(Refusal): ...
169
170
  class ArithOverflow(Refusal): ...
@@ -22,6 +22,7 @@ from __future__ import annotations
22
22
 
23
23
  from . import _ext
24
24
  from ._ext import (
25
+ AdapterTargetUnsupported,
25
26
  AmbiguousClassification,
26
27
  ArchiveRefused,
27
28
  ArithOverflow,
@@ -181,6 +182,7 @@ CODES: dict[str, type[Refusal]] = _ext.CODES
181
182
 
182
183
  __all__ = [
183
184
  "CODES",
185
+ "AdapterTargetUnsupported",
184
186
  "AmbiguousClassification",
185
187
  "ArchiveRefused",
186
188
  "ArithOverflow",