sharedbox 0.4.1__tar.gz → 0.5.0__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 (236) hide show
  1. sharedbox-0.5.0/.github/workflows/contention.yaml +45 -0
  2. {sharedbox-0.4.1 → sharedbox-0.5.0}/CHANGELOG.md +41 -1
  3. {sharedbox-0.4.1 → sharedbox-0.5.0}/CLAUDE.md +13 -7
  4. {sharedbox-0.4.1 → sharedbox-0.5.0}/PKG-INFO +37 -11
  5. {sharedbox-0.4.1 → sharedbox-0.5.0}/README.md +36 -10
  6. {sharedbox-0.4.1 → sharedbox-0.5.0}/benchmarks/test_bench_box.py +41 -0
  7. sharedbox-0.5.0/docs/diagrams/style.d2 +61 -0
  8. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/store_arrays.py +12 -0
  9. sharedbox-0.5.0/docs/explanation/checking-a-process-is-alive.md +89 -0
  10. sharedbox-0.5.0/docs/explanation/closing-and-lifetime.md +242 -0
  11. sharedbox-0.5.0/docs/explanation/field-types.md +141 -0
  12. sharedbox-0.5.0/docs/explanation/glossary.md +130 -0
  13. sharedbox-0.5.0/docs/explanation/how-a-box-is-stored.md +214 -0
  14. sharedbox-0.5.0/docs/explanation/how-the-module-is-built.md +96 -0
  15. sharedbox-0.5.0/docs/explanation/index.md +44 -0
  16. sharedbox-0.5.0/docs/explanation/limits.md +74 -0
  17. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/explanation/performance.md +14 -10
  18. sharedbox-0.5.0/docs/explanation/reading-and-writing.md +272 -0
  19. sharedbox-0.5.0/docs/explanation/references.md +154 -0
  20. sharedbox-0.5.0/docs/explanation/waiting-for-changes.md +268 -0
  21. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/explanation/when-to-use-sharedbox.md +11 -6
  22. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/accept-a-box-in-c.md +30 -18
  23. sharedbox-0.5.0/docs/how-to/accept-a-box-in-cpp.md +192 -0
  24. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/build-docs.md +3 -0
  25. sharedbox-0.5.0/docs/how-to/change-several-fields-at-once.md +60 -0
  26. sharedbox-0.5.0/docs/how-to/clean-up-segments.md +93 -0
  27. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/commits-and-prs.md +4 -0
  28. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/contribute.md +3 -2
  29. sharedbox-0.5.0/docs/how-to/follow-a-whole-reference-graph.md +73 -0
  30. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/index.md +6 -5
  31. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/install-sharedbox.md +9 -8
  32. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/make-a-release.md +3 -2
  33. sharedbox-0.5.0/docs/how-to/name-a-box.md +83 -0
  34. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/open-a-box-from-a-program.md +13 -11
  35. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/run-benchmarks.md +34 -2
  36. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/run-commit-checks.md +5 -4
  37. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/run-tests.md +7 -2
  38. sharedbox-0.5.0/docs/how-to/send-a-box-to-another-process.md +127 -0
  39. sharedbox-0.5.0/docs/how-to/set-defaults-and-check-values.md +84 -0
  40. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/set-up-development.md +3 -1
  41. sharedbox-0.5.0/docs/how-to/store-arrays.md +175 -0
  42. sharedbox-0.5.0/docs/how-to/store-collections.md +71 -0
  43. sharedbox-0.5.0/docs/how-to/store-records.md +79 -0
  44. sharedbox-0.5.0/docs/how-to/store-text-and-bytes.md +68 -0
  45. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/write-docs.md +68 -6
  46. sharedbox-0.5.0/docs/index.md +185 -0
  47. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/cpp-and-c-api.md +9 -6
  48. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/index.md +4 -2
  49. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/segment-layout.md +11 -7
  50. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/tutorials/index.md +6 -4
  51. sharedbox-0.5.0/docs/tutorials/react-to-changes.md +149 -0
  52. sharedbox-0.5.0/docs/tutorials/refer-to-another-box.md +104 -0
  53. sharedbox-0.5.0/docs/tutorials/share-a-record.md +131 -0
  54. {sharedbox-0.4.1 → sharedbox-0.5.0}/include/sharedbox/sharedbox.hpp +34 -0
  55. {sharedbox-0.4.1 → sharedbox-0.5.0}/pyproject.toml +1 -0
  56. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_box.py +93 -10
  57. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_layout.py +6 -0
  58. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/module.cpp +173 -7
  59. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/segment.cpp +44 -1
  60. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/segment.hpp +31 -0
  61. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/types.cpp +130 -21
  62. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/types.hpp +17 -2
  63. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native.pyi +56 -2
  64. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_types.py +34 -1
  65. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_version.py +3 -3
  66. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/_app.py +37 -1
  67. sharedbox-0.5.0/src/sharedbox/benchmarks/contention.py +324 -0
  68. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/ops.py +9 -0
  69. {sharedbox-0.4.1 → sharedbox-0.5.0}/stubtest-allowlist.txt +5 -0
  70. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/CMakeLists.txt +1 -1
  71. sharedbox-0.5.0/tests/cpp/test_write_in_place.cpp +83 -0
  72. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/forged_open.py +6 -2
  73. sharedbox-0.5.0/tests/stress/test_stress_read_into.py +61 -0
  74. sharedbox-0.5.0/tests/test_benchbox_contention.py +15 -0
  75. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_box.py +5 -4
  76. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_fork.py +34 -0
  77. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_properties_stateful.py +12 -1
  78. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_properties_types.py +5 -2
  79. sharedbox-0.5.0/tests/test_read_into.py +197 -0
  80. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_arrays_bfloat16.py +21 -0
  81. sharedbox-0.5.0/tests/test_types_records_attrs.py +83 -0
  82. sharedbox-0.5.0/tests/test_types_records_msgspec.py +92 -0
  83. sharedbox-0.5.0/tests/test_value_reuse.py +446 -0
  84. sharedbox-0.5.0/tests/test_writing.py +171 -0
  85. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/type_checks/box_types.py +21 -1
  86. {sharedbox-0.4.1 → sharedbox-0.5.0}/uv.lock +32 -0
  87. {sharedbox-0.4.1 → sharedbox-0.5.0}/zensical.toml +1 -1
  88. sharedbox-0.4.1/docs/explanation/checking-a-process-is-alive.md +0 -63
  89. sharedbox-0.4.1/docs/explanation/closing-and-lifetime.md +0 -136
  90. sharedbox-0.4.1/docs/explanation/field-types.md +0 -91
  91. sharedbox-0.4.1/docs/explanation/glossary.md +0 -114
  92. sharedbox-0.4.1/docs/explanation/how-a-box-is-stored.md +0 -145
  93. sharedbox-0.4.1/docs/explanation/how-the-module-is-built.md +0 -68
  94. sharedbox-0.4.1/docs/explanation/index.md +0 -44
  95. sharedbox-0.4.1/docs/explanation/limits.md +0 -63
  96. sharedbox-0.4.1/docs/explanation/reading-and-writing.md +0 -121
  97. sharedbox-0.4.1/docs/explanation/references.md +0 -79
  98. sharedbox-0.4.1/docs/explanation/waiting-for-changes.md +0 -184
  99. sharedbox-0.4.1/docs/how-to/accept-a-box-in-cpp.md +0 -174
  100. sharedbox-0.4.1/docs/how-to/change-several-fields-at-once.md +0 -59
  101. sharedbox-0.4.1/docs/how-to/clean-up-segments.md +0 -82
  102. sharedbox-0.4.1/docs/how-to/follow-a-whole-reference-graph.md +0 -70
  103. sharedbox-0.4.1/docs/how-to/name-a-box.md +0 -76
  104. sharedbox-0.4.1/docs/how-to/send-a-box-to-another-process.md +0 -69
  105. sharedbox-0.4.1/docs/how-to/set-defaults-and-check-values.md +0 -75
  106. sharedbox-0.4.1/docs/how-to/store-arrays.md +0 -58
  107. sharedbox-0.4.1/docs/how-to/store-collections.md +0 -59
  108. sharedbox-0.4.1/docs/how-to/store-records.md +0 -68
  109. sharedbox-0.4.1/docs/how-to/store-text-and-bytes.md +0 -66
  110. sharedbox-0.4.1/docs/index.md +0 -63
  111. sharedbox-0.4.1/docs/tutorials/react-to-changes.md +0 -130
  112. sharedbox-0.4.1/docs/tutorials/refer-to-another-box.md +0 -92
  113. sharedbox-0.4.1/docs/tutorials/share-a-record.md +0 -115
  114. sharedbox-0.4.1/tests/test_types_records_attrs.py +0 -21
  115. sharedbox-0.4.1/tests/test_types_records_msgspec.py +0 -35
  116. {sharedbox-0.4.1 → sharedbox-0.5.0}/.clang-format +0 -0
  117. {sharedbox-0.4.1 → sharedbox-0.5.0}/.claude/settings.json +0 -0
  118. {sharedbox-0.4.1 → sharedbox-0.5.0}/.github/dependabot.yml +0 -0
  119. {sharedbox-0.4.1 → sharedbox-0.5.0}/.github/workflows/check-docs.yaml +0 -0
  120. {sharedbox-0.4.1 → sharedbox-0.5.0}/.github/workflows/ci.yaml +0 -0
  121. {sharedbox-0.4.1 → sharedbox-0.5.0}/.github/workflows/codspeed.yml +0 -0
  122. {sharedbox-0.4.1 → sharedbox-0.5.0}/.github/workflows/publish-docs.yaml +0 -0
  123. {sharedbox-0.4.1 → sharedbox-0.5.0}/.gitignore +0 -0
  124. {sharedbox-0.4.1 → sharedbox-0.5.0}/CMakeLists.txt +0 -0
  125. {sharedbox-0.4.1 → sharedbox-0.5.0}/LICENSE +0 -0
  126. {sharedbox-0.4.1 → sharedbox-0.5.0}/cmake/sharedbox-config.cmake +0 -0
  127. {sharedbox-0.4.1 → sharedbox-0.5.0}/cmake/sharedbox-require-cxx.cmake +0 -0
  128. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/assets/benchmarks/ops-dark.svg +0 -0
  129. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/assets/benchmarks/ops-light.svg +0 -0
  130. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/assets/benchmarks/roundtrip-dark.svg +0 -0
  131. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/assets/benchmarks/roundtrip-light.svg +0 -0
  132. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/change_several_fields_at_once.py +0 -0
  133. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/clean_up_segments.py +0 -0
  134. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/follow_a_whole_reference_graph.py +0 -0
  135. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/name_a_box.py +0 -0
  136. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/send_a_box_to_another_process.py +0 -0
  137. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/set_defaults_and_check_values.py +0 -0
  138. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/store_collections.py +0 -0
  139. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/store_records.py +0 -0
  140. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/examples/store_text_and_bytes.py +0 -0
  141. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/how-to/ai-contribution-policy.md +0 -0
  142. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/arrays.md +0 -0
  143. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/box.md +0 -0
  144. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/errors.md +0 -0
  145. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/events.md +0 -0
  146. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/index.md +0 -0
  147. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/library-authors.md +0 -0
  148. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/api/references.md +0 -0
  149. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/reference/changelog.md +0 -0
  150. {sharedbox-0.4.1 → sharedbox-0.5.0}/docs/tutorials/motor.py +0 -0
  151. {sharedbox-0.4.1 → sharedbox-0.5.0}/include/sharedbox/sharedbox_c.cpp +0 -0
  152. {sharedbox-0.4.1 → sharedbox-0.5.0}/include/sharedbox/sharedbox_c.h +0 -0
  153. {sharedbox-0.4.1 → sharedbox-0.5.0}/includes/abbreviations.md +0 -0
  154. {sharedbox-0.4.1 → sharedbox-0.5.0}/prek.toml +0 -0
  155. {sharedbox-0.4.1 → sharedbox-0.5.0}/scripts/check_xrefs.py +0 -0
  156. {sharedbox-0.4.1 → sharedbox-0.5.0}/scripts/vscode_setup.py +0 -0
  157. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/__init__.py +0 -0
  158. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_arrays.py +0 -0
  159. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_events.py +0 -0
  160. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_follow.py +0 -0
  161. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/codec.cpp +0 -0
  162. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/codec.hpp +0 -0
  163. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/scalars.cpp +0 -0
  164. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_native/scalars.hpp +0 -0
  165. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/_refs.py +0 -0
  166. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/__init__.py +0 -0
  167. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/__main__.py +0 -0
  168. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/cli.py +0 -0
  169. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/plot.py +0 -0
  170. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/roundtrip.py +0 -0
  171. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/size.py +0 -0
  172. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/benchmarks/size_diff.py +0 -0
  173. {sharedbox-0.4.1 → sharedbox-0.5.0}/src/sharedbox/py.typed +0 -0
  174. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/conftest.py +0 -0
  175. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/compile_fail.cpp +0 -0
  176. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/consumer/CMakeLists.txt +0 -0
  177. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/consumer/consumer.c +0 -0
  178. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/table_builder.hpp +0 -0
  179. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_atomics.cpp +0 -0
  180. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_c_smoke.c +0 -0
  181. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_c_smoke_box.cpp +0 -0
  182. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_fork.cpp +0 -0
  183. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_layout2.cpp +0 -0
  184. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_lock.cpp +0 -0
  185. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_mapping.cpp +0 -0
  186. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_result.cpp +0 -0
  187. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_slots.cpp +0 -0
  188. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_small_shm.cpp +0 -0
  189. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_types.cpp +0 -0
  190. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_values.cpp +0 -0
  191. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_wait.cpp +0 -0
  192. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/test_windows_h.cpp +0 -0
  193. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/cpp/unique.hpp +0 -0
  194. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/crossproc.py +0 -0
  195. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/forged_types.py +0 -0
  196. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/refs_future.py +0 -0
  197. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/stress_helpers.py +0 -0
  198. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_contention.py +0 -0
  199. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_crashes.py +0 -0
  200. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_follow.py +0 -0
  201. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_lock_timeout.py +0 -0
  202. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_memory.py +0 -0
  203. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_waiters.py +0 -0
  204. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/stress/test_stress_watchers.py +0 -0
  205. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_benchbox_plot.py +0 -0
  206. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_capsule.py +0 -0
  207. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_doc_examples.py +0 -0
  208. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_doc_tutorials.py +0 -0
  209. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_events_async.py +0 -0
  210. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_events_bytes.py +0 -0
  211. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_events_signals.py +0 -0
  212. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_events_sync.py +0 -0
  213. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_exit.py +0 -0
  214. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_fields.py +0 -0
  215. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_follow.py +0 -0
  216. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_layout.py +0 -0
  217. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_layout_v1.py +0 -0
  218. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_codec.py +0 -0
  219. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_liveness.py +0 -0
  220. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_segment.py +0 -0
  221. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_types.py +0 -0
  222. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_wait.py +0 -0
  223. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_native_waiters.py +0 -0
  224. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_properties_codec.py +0 -0
  225. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_properties_forged.py +0 -0
  226. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_properties_layout.py +0 -0
  227. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_refs.py +0 -0
  228. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_refs_lazy.py +0 -0
  229. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_arrays.py +0 -0
  230. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_arrays_torch.py +0 -0
  231. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_choices.py +0 -0
  232. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_collections.py +0 -0
  233. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_records.py +0 -0
  234. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_scalars.py +0 -0
  235. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/test_types_unions.py +0 -0
  236. {sharedbox-0.4.1 → sharedbox-0.5.0}/tests/type_checks/events_types.py +0 -0
@@ -0,0 +1,45 @@
1
+ name: Contention
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ main ]
6
+ paths:
7
+ - include/sharedbox/**
8
+ - src/sharedbox/_native/**
9
+ - src/sharedbox/benchmarks/contention.py
10
+ - .github/workflows/contention.yaml
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ contention:
18
+ name: Contention on ${{ matrix.os }}
19
+ runs-on: ${{ matrix.os }}
20
+ timeout-minutes: 30
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ os: [ ubuntu-latest, windows-latest ]
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+ with:
28
+ fetch-depth: 0
29
+
30
+ - uses: astral-sh/setup-uv@v7
31
+
32
+ - name: Install the project
33
+ run: uv sync --frozen --python 3.12
34
+
35
+ - name: Run benchbox contention
36
+ shell: bash
37
+ run: |
38
+ cores=$(uv run --no-sync python -c "import os; print(os.cpu_count())")
39
+ echo "### Contention on ${{ matrix.os }}, $cores logical cores" >> "$GITHUB_STEP_SUMMARY"
40
+ uv run --no-sync benchbox contention --json contention.json --markdown >> "$GITHUB_STEP_SUMMARY"
41
+
42
+ - uses: actions/upload-artifact@v7
43
+ with:
44
+ name: contention-${{ matrix.os }}
45
+ path: contention.json
@@ -9,6 +9,44 @@ Dates are marked as `DD-MM-YYYY`
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.5.0] - 09-10-2026
13
+
14
+ ### Added
15
+
16
+ - `SharedBox.read_into`: copies an array field into an array the caller
17
+ passes and returns it, without allocating a new array.
18
+ - `SharedBox.writing`: a context manager that holds the box's write lock
19
+ and yields an array field as an array to fill in place.
20
+ - `benchbox contention`: times writes and reads of an `int` while several
21
+ writer and reader processes share it, for a box, `mp.Value` and
22
+ `SharedMemory` with a `Lock`.
23
+
24
+ ### Changed
25
+
26
+ - `SharedBox`: a field named `read_into` raises `TypeError`.
27
+ - `SharedBox`: a field named `writing` raises `TypeError`.
28
+ - `SharedBox`: reading a collection, tuple or record field whose members
29
+ are `bool`, `int`, `float` or `str` takes less time: about 90 ns less
30
+ for a list of 16 `float` on Windows.
31
+ - `SharedBox`: reading a field whose value cannot be changed (a frozen
32
+ dataclass, attrs class or msgspec Struct, a NamedTuple, a tuple, a
33
+ frozenset, an enum, flag or literal, `complex`, `date`, `time`,
34
+ `datetime`, `timedelta`, `UUID` or `Decimal`) returns the value the
35
+ previous read built until the field is written: 40 ns instead of 540 ns
36
+ for a frozen dataclass of four fields on Windows. A tuple, frozenset or
37
+ record qualifies only when its members are of these kinds or `bool`,
38
+ `int`, `float`, `str` or `bytes`.
39
+ - `SharedBox`: a frozen record's `__post_init__`, attrs converters and
40
+ validators run on the first read after each write instead of on every
41
+ read.
42
+
43
+ ### Fixed
44
+
45
+ - `SharedBox.attach`: a box whose header gives a field count other than the
46
+ class's raises `SchemaMismatchError` instead of `ValueError`.
47
+
48
+ ## [0.4.1] - 07-10-2026
49
+
12
50
  ### Changed
13
51
 
14
52
  - `SharedBox.update`: takes about 7 to 9 ns less per call on Windows.
@@ -292,7 +330,9 @@ stage.events.nested.connect(lambda path, new, old: print(path, new))
292
330
 
293
331
  - Initial release
294
332
 
295
- [Unreleased]: https://github.com/jacopoabramo/sharedbox/compare/v0.4.0...HEAD
333
+ [Unreleased]: https://github.com/jacopoabramo/sharedbox/compare/v0.5.0...HEAD
334
+ [0.5.0]: https://github.com/jacopoabramo/sharedbox/compare/v0.4.1...v0.5.0
335
+ [0.4.1]: https://github.com/jacopoabramo/sharedbox/compare/v0.4.0...v0.4.1
296
336
  [0.4.0]: https://github.com/jacopoabramo/sharedbox/compare/v0.3.1...v0.4.0
297
337
  [0.3.1]: https://github.com/jacopoabramo/sharedbox/compare/v0.3.0...v0.3.1
298
338
  [0.3.0]: https://github.com/jacopoabramo/sharedbox/compare/0.2.4...v0.3.0
@@ -33,15 +33,17 @@ sharedbox/
33
33
  | |-- py.typed
34
34
  | |-- benchmarks/ the benchbox command (extra: benchmarks)
35
35
  | | |-- cli.py entry point; exits with an install hint without Typer
36
- | | |-- _app.py Typer commands: ops, roundtrip, size, plot, all
36
+ | | |-- _app.py Typer commands: ops, roundtrip, contention, size, plot, all
37
37
  | | |-- ops.py pyperf timings of single operations, against the stdlib
38
38
  | | |-- roundtrip.py cross-process round trip percentiles
39
+ | | |-- contention.py throughput and percentiles with several writer and reader processes
39
40
  | | |-- plot.py SVG charts of the ops and roundtrip results (matplotlib)
40
41
  | | |-- size.py wheel and extension size (standard library only)
41
42
  | | `-- size_diff.py wheel size table against main, for CI (standard library only)
42
43
  | `-- _native/
43
44
  | |-- module.cpp nanobind module: Segment, the Field descriptor, the BoxMethod type
44
- | | (native update and snapshot) and the error classes
45
+ | | (native update and snapshot), ValueCache (values kept for reuse) and the
46
+ | | error classes
45
47
  | |-- codec.{hpp,cpp} converts field values to and from their stored bytes; encode_all
46
48
  | | checks every value of an update before any is written,
47
49
  | | with_record reads the whole record at once
@@ -51,6 +53,7 @@ sharedbox/
51
53
  |-- tests/ pytest; many tests spawn processes
52
54
  | |-- crossproc.py helpers that run a box in another process
53
55
  | |-- forged_types.py builds segments with forged description tables
56
+ | |-- test_benchbox_contention.py benchbox contention, a short run
54
57
  | |-- test_benchbox_plot.py benchbox plot; skipped without matplotlib and pyperf
55
58
  | |-- test_capsule.py __sharedbox_box__, and the C consumer in tests/cpp/consumer/
56
59
  | |-- test_doc_examples.py runs each docs/examples/*.py script
@@ -78,6 +81,8 @@ sharedbox/
78
81
  |-- .github/workflows/check-docs.yaml builds the site and runs check_xrefs.py
79
82
  |-- .github/workflows/publish-docs.yaml deploys the checked site to GitHub Pages
80
83
  |-- .github/workflows/codspeed.yml benchmarks on CodSpeed
84
+ |-- .github/workflows/contention.yaml benchbox contention on Linux and Windows, for pull requests
85
+ | that touch the native code, and by hand
81
86
  |-- CMakeLists.txt sharedbox::headers, sharedbox::c, extension build
82
87
  |-- stubtest-allowlist.txt stubtest exceptions for nanobind types
83
88
  |-- .clang-format clang-format style for the C and C++ sources
@@ -280,11 +285,12 @@ if __name__ == "__main__":
280
285
  Motor.unlink()
281
286
  ```
282
287
 
283
- Every read decodes a fresh value from the segment. `update(**values)` writes
284
- several fields at once; `watch(field)` and `events` report changes from any
285
- process. C++ and C code take a box through `__sharedbox_box__` or open it by
286
- name; see `docs/how-to/accept-a-box-in-cpp.md`,
287
- `docs/how-to/accept-a-box-in-c.md` and
288
+ A read decodes the stored bytes; a value that cannot be changed (a
289
+ `datetime`, or a frozen record, tuple or frozenset holding only such values)
290
+ is reused until its field is written. `update(**values)` writes several
291
+ fields at once; `watch(field)` and `events` report changes from any process.
292
+ C++ and C code take a box through `__sharedbox_box__` or open it by name; see
293
+ `docs/how-to/accept-a-box-in-cpp.md`, `docs/how-to/accept-a-box-in-c.md` and
288
294
  `docs/how-to/open-a-box-from-a-program.md`.
289
295
 
290
296
  ## Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sharedbox
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Python containers using shared memory.
5
5
  Author-Email: Jacopo Abramo <jacopo.abramo@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -34,16 +34,24 @@ Description-Content-Type: text/markdown
34
34
  > [!WARNING]
35
35
  > This project is a work in progress; be patient or feel free to contribute.
36
36
 
37
- `sharedbox` keeps records in shared memory. Each box is one named segment, and every process that opens it reads and writes the same fields.
37
+ `sharedbox` lets several Python processes share one record, as if they all
38
+ held the same dataclass. You declare the fields once, and every process that
39
+ opens the record reads and writes the same values in shared memory, so no
40
+ process has to send them to another.
38
41
 
39
42
  ## Installation
40
43
 
41
- It is recommended to install `sharedbox` in a virtual environment; for example using `uv`:
44
+ `sharedbox` needs CPython 3.11 or newer on Windows x64 or Linux x86_64. PyPI
45
+ has compiled wheels, so you don't need a compiler:
42
46
 
43
47
  ```sh
44
- uv venv --python 3.11
45
- .venv\Scripts\activate
46
- uv pip install sharedbox
48
+ pip install sharedbox
49
+ ```
50
+
51
+ or, in a project that [`uv`](https://docs.astral.sh/uv/) manages:
52
+
53
+ ```sh
54
+ uv add sharedbox
47
55
  ```
48
56
 
49
57
  ## Quick start
@@ -76,14 +84,32 @@ if __name__ == "__main__":
76
84
  Motor.unlink()
77
85
  ```
78
86
 
87
+ The child finds the box by its class alone, and the parent prints the change
88
+ as soon as the child makes it.
89
+
79
90
  ## Documentation
80
91
 
81
- The [documentation site](https://jacopoabramo.github.io/sharedbox) has a
82
- tutorial, how-to guides, explanations of how a box works, and the API
83
- reference.
92
+ The [documentation site](https://jacopoabramo.github.io/sharedbox) starts
93
+ with three tutorials, then has how-to guides, explanations of how a
94
+ box works, and the API reference. C and C++ code can use a box too, either
95
+ handed over from Python or opened by name; the
96
+ [C and C++ guides](https://jacopoabramo.github.io/sharedbox/how-to/accept-a-box-in-cpp/)
97
+ show how.
98
+
99
+ ## Development
100
+
101
+ To change `sharedbox` itself you need `git`, `uv`, CMake 3.30 or newer and a
102
+ C++20 compiler (MSVC or GCC):
103
+
104
+ ```sh
105
+ git clone https://github.com/jacopoabramo/sharedbox.git
106
+ cd sharedbox
107
+ uv sync --dev
108
+ uv run pytest
109
+ ```
84
110
 
85
- C++ and C code can take a box from Python or open one by name; see the
86
- [C and C++ guides](https://jacopoabramo.github.io/sharedbox/how-to/accept-a-box-in-cpp/).
111
+ [How to set up a development environment](https://jacopoabramo.github.io/sharedbox/how-to/set-up-development/)
112
+ has the details.
87
113
 
88
114
  ## License
89
115
 
@@ -10,16 +10,24 @@
10
10
  > [!WARNING]
11
11
  > This project is a work in progress; be patient or feel free to contribute.
12
12
 
13
- `sharedbox` keeps records in shared memory. Each box is one named segment, and every process that opens it reads and writes the same fields.
13
+ `sharedbox` lets several Python processes share one record, as if they all
14
+ held the same dataclass. You declare the fields once, and every process that
15
+ opens the record reads and writes the same values in shared memory, so no
16
+ process has to send them to another.
14
17
 
15
18
  ## Installation
16
19
 
17
- It is recommended to install `sharedbox` in a virtual environment; for example using `uv`:
20
+ `sharedbox` needs CPython 3.11 or newer on Windows x64 or Linux x86_64. PyPI
21
+ has compiled wheels, so you don't need a compiler:
18
22
 
19
23
  ```sh
20
- uv venv --python 3.11
21
- .venv\Scripts\activate
22
- uv pip install sharedbox
24
+ pip install sharedbox
25
+ ```
26
+
27
+ or, in a project that [`uv`](https://docs.astral.sh/uv/) manages:
28
+
29
+ ```sh
30
+ uv add sharedbox
23
31
  ```
24
32
 
25
33
  ## Quick start
@@ -52,14 +60,32 @@ if __name__ == "__main__":
52
60
  Motor.unlink()
53
61
  ```
54
62
 
63
+ The child finds the box by its class alone, and the parent prints the change
64
+ as soon as the child makes it.
65
+
55
66
  ## Documentation
56
67
 
57
- The [documentation site](https://jacopoabramo.github.io/sharedbox) has a
58
- tutorial, how-to guides, explanations of how a box works, and the API
59
- reference.
68
+ The [documentation site](https://jacopoabramo.github.io/sharedbox) starts
69
+ with three tutorials, then has how-to guides, explanations of how a
70
+ box works, and the API reference. C and C++ code can use a box too, either
71
+ handed over from Python or opened by name; the
72
+ [C and C++ guides](https://jacopoabramo.github.io/sharedbox/how-to/accept-a-box-in-cpp/)
73
+ show how.
74
+
75
+ ## Development
76
+
77
+ To change `sharedbox` itself you need `git`, `uv`, CMake 3.30 or newer and a
78
+ C++20 compiler (MSVC or GCC):
79
+
80
+ ```sh
81
+ git clone https://github.com/jacopoabramo/sharedbox.git
82
+ cd sharedbox
83
+ uv sync --dev
84
+ uv run pytest
85
+ ```
60
86
 
61
- C++ and C code can take a box from Python or open one by name; see the
62
- [C and C++ guides](https://jacopoabramo.github.io/sharedbox/how-to/accept-a-box-in-cpp/).
87
+ [How to set up a development environment](https://jacopoabramo.github.io/sharedbox/how-to/set-up-development/)
88
+ has the details.
63
89
 
64
90
  ## License
65
91
 
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from collections.abc import Iterator
3
+ from dataclasses import dataclass
3
4
  from itertools import count
4
5
  from typing import Annotated
5
6
 
@@ -17,6 +18,22 @@ class Record(SharedBox):
17
18
  s: Annotated[str, Capacity(32)]
18
19
 
19
20
 
21
+ class Samples(SharedBox):
22
+ floats: Annotated[list[float], Capacity(16)]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class Quad:
27
+ a: int
28
+ b: float
29
+ c: bool
30
+ d: int
31
+
32
+
33
+ class Quads(SharedBox):
34
+ quad: Quad
35
+
36
+
20
37
  @pytest.fixture
21
38
  def box() -> Iterator[Record]:
22
39
  name = f"bench-codspeed-{os.getpid()}-{next(NAMES)}"
@@ -55,3 +72,27 @@ def test_native_set_int(benchmark: BenchmarkFixture, box: Record) -> None:
55
72
 
56
73
  def test_native_get_int(benchmark: BenchmarkFixture, box: Record) -> None:
57
74
  benchmark(box._segment.get, 0)
75
+
76
+
77
+ @pytest.fixture
78
+ def samples() -> Iterator[Samples]:
79
+ name = f"bench-codspeed-{os.getpid()}-{next(NAMES)}"
80
+ with Samples.create(name, [0.5] * 16) as box:
81
+ yield box
82
+ Samples.unlink(name)
83
+
84
+
85
+ def test_read_float_list(benchmark: BenchmarkFixture, samples: Samples) -> None:
86
+ benchmark(getattr, samples, "floats")
87
+
88
+
89
+ @pytest.fixture
90
+ def quads() -> Iterator[Quads]:
91
+ name = f"bench-codspeed-{os.getpid()}-{next(NAMES)}"
92
+ with Quads.create(name, Quad(1, 1.5, True, 2)) as box:
93
+ yield box
94
+ Quads.unlink(name)
95
+
96
+
97
+ def test_read_record(benchmark: BenchmarkFixture, quads: Quads) -> None:
98
+ benchmark(getattr, quads, "quad")
@@ -0,0 +1,61 @@
1
+ # Shared look of every diagram in the docs. A diagram pulls it in with
2
+ # `...@diagrams/style` on its first line and gives shapes these classes.
3
+ classes: {
4
+ layer: {
5
+ style: {
6
+ border-radius: 10
7
+ font-size: 18
8
+ bold: true
9
+ }
10
+ }
11
+ step: {
12
+ style.border-radius: 6
13
+ }
14
+ current: {
15
+ style: {
16
+ border-radius: 6
17
+ stroke-width: 4
18
+ bold: true
19
+ shadow: true
20
+ }
21
+ }
22
+ done: {
23
+ style: {
24
+ border-radius: 6
25
+ opacity: 0.4
26
+ }
27
+ }
28
+ failed: {
29
+ style: {
30
+ border-radius: 6
31
+ stroke: "#c62828"
32
+ stroke-dash: 4
33
+ font-color: "#c62828"
34
+ }
35
+ }
36
+ process: {
37
+ shape: hexagon
38
+ }
39
+ hardware: {
40
+ shape: cylinder
41
+ }
42
+ file: {
43
+ shape: page
44
+ }
45
+ note: {
46
+ shape: text
47
+ style: {
48
+ italic: true
49
+ font-size: 14
50
+ }
51
+ }
52
+ gap: {
53
+ label: ""
54
+ style.opacity: 0
55
+ }
56
+ # a shape a later step brings in: declared on the first board so that every
57
+ # board keeps the same layout, and shown by a step that changes its class
58
+ hidden: {
59
+ style.opacity: 0
60
+ }
61
+ }
@@ -23,6 +23,18 @@ sensor.frame = np.full((4, 6), 7, np.uint8)
23
23
  print(int(sensor.frame.sum())) # 168
24
24
  # --8<-- [end:write]
25
25
 
26
+ # --8<-- [start:read-into]
27
+ frame = np.empty((4, 6), np.uint8)
28
+ sensor.read_into("frame", frame)
29
+ print(int(frame.sum())) # 168
30
+ # --8<-- [end:read-into]
31
+
32
+ # --8<-- [start:writing]
33
+ with sensor.writing("frame") as frame:
34
+ frame[0, :] = 255
35
+ print(int(sensor.frame[0].sum())) # 1530
36
+ # --8<-- [end:writing]
37
+
26
38
  # --8<-- [start:any-library]
27
39
  raw = sensor.raw
28
40
  print(np.from_dlpack(raw)) # [0. 0. 0.]
@@ -0,0 +1,89 @@
1
+ ---
2
+ icon: lucide/lightbulb
3
+ ---
4
+
5
+ # Checking a process is alive
6
+
7
+ Sometimes `sharedbox` has to answer a simple-sounding question: is the
8
+ process recorded in a [segment](glossary.md#segment) still running? The
9
+ answer decides whether a crashed waiter's slot can be freed, and what an
10
+ error message tells you about who holds a name. This page explains how it
11
+ answers, and why the obvious check isn't enough.
12
+
13
+ ## A pid and a start time
14
+
15
+ The obvious check would be "is there a process with this process id
16
+ (pid)?", but that isn't enough: once a process exits, the operating system
17
+ can give its pid to a new one. So `sharedbox.hpp` names a process by its
18
+ pid and its start time, the way the `psutil` library tells a process from a
19
+ later one with the same pid.[^psutil] On Windows the start
20
+ time is the creation time from `GetProcessTimes`. On Linux it is field 22
21
+ of `/proc/<pid>/stat`.[^proc-stat] `process_alive()` reads the start time
22
+ of whatever process has the pid now and decides like this:
23
+
24
+ ```d2 title="Is the recorded process still running?"
25
+ ...@diagrams/style
26
+ direction: down
27
+ pid: "a process has the pid?" {class: step}
28
+ zombie: "on Linux: a zombie?" {
29
+ class: step
30
+ tooltip: A zombie, state Z or X in /proc/PID/stat, has exited and keeps its entry only until its parent collects it.
31
+ }
32
+ readable: "start time readable?" {
33
+ class: step
34
+ tooltip: It is not when the process belongs to another user or /proc is mounted with hidepid.
35
+ }
36
+ same: "same start time?" {class: step}
37
+ dead: "dead" {class: failed}
38
+ alive: "alive" {class: current}
39
+ pid -> dead: "no"
40
+ pid -> zombie: "yes"
41
+ zombie -> dead: "yes"
42
+ zombie -> readable: "no"
43
+ readable -> alive: "no" {
44
+ tooltip: The process exists and nothing shows it is a different one, so it counts as alive.
45
+ }
46
+ readable -> same: "yes"
47
+ same -> alive: "yes"
48
+ same -> dead: "no, the pid was reused"
49
+ ```
50
+
51
+ A recorded start time that couldn't be read is treated the same way: the
52
+ process counts as alive.[^proc-stat]
53
+
54
+ On Linux there's one more catch: containers give their processes their own
55
+ set of pids, called a pid namespace, so the same pid can mean different
56
+ processes in different containers. That's why
57
+ [waiter slots](glossary.md#waiter-slot) and the header's creator fields also
58
+ record the namespace, and a process in another namespace, or one whose
59
+ namespace is unknown, is never judged dead.
60
+
61
+ ## Where the check is used
62
+
63
+ - Freeing the waiter slots of dead processes, as [Waiting for
64
+ changes](waiting-for-changes.md#waking-a-process) describes.
65
+ - Naming the creator in
66
+ [`SegmentExistsError`][sharedbox.SegmentExistsError]. When a create finds
67
+ the name taken, the extension reads the existing header with
68
+ `sharedbox::inspect()` and checks the creator it records. The creator
69
+ writes the header before it publishes the [box](glossary.md#box), so a
70
+ name that holds no published box is checked the same way. The docstring
71
+ of `SegmentExistsError` lists the messages that result.
72
+
73
+ Even when the creator has exited, nothing is removed automatically, because
74
+ other processes may still be using a segment whose creator died. Removing
75
+ it is your call; [How to clean up segments](../how-to/clean-up-segments.md)
76
+ shows how.
77
+
78
+ ## Sources
79
+
80
+ [^psutil]:
81
+ psutil (BSD-3-Clause), `Process`: a process is identified by its pid
82
+ and its creation time, so a reused pid is not mistaken for the same
83
+ process.
84
+ <https://github.com/giampaolo/psutil>
85
+
86
+ [^proc-stat]:
87
+ Linux manual page `proc_pid_stat(5)`: the process state (field 3) and
88
+ `starttime` (field 22).
89
+ <https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html>