sharedbox 0.2.4__tar.gz → 0.3.0rc0__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.
- sharedbox-0.3.0rc0/.clang-format +4 -0
- sharedbox-0.3.0rc0/.github/workflows/ci.yaml +176 -0
- sharedbox-0.3.0rc0/.github/workflows/codspeed.yml +39 -0
- {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/.gitignore +0 -2
- sharedbox-0.3.0rc0/CHANGELOG.md +55 -0
- sharedbox-0.3.0rc0/CLAUDE.md +193 -0
- sharedbox-0.3.0rc0/CMakeLists.txt +41 -0
- {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/LICENSE +1 -1
- sharedbox-0.3.0rc0/PKG-INFO +222 -0
- sharedbox-0.3.0rc0/README.md +200 -0
- sharedbox-0.3.0rc0/benchmarks/test_bench_box.py +58 -0
- sharedbox-0.3.0rc0/docs/api.md +489 -0
- sharedbox-0.3.0rc0/docs/design/native-segment.md +546 -0
- sharedbox-0.3.0rc0/prek.toml +20 -0
- sharedbox-0.3.0rc0/pyproject.toml +164 -0
- sharedbox-0.3.0rc0/scripts/vscode_setup.py +96 -0
- sharedbox-0.3.0rc0/src/sharedbox/__init__.py +21 -0
- sharedbox-0.3.0rc0/src/sharedbox/_box.py +362 -0
- sharedbox-0.3.0rc0/src/sharedbox/_events.py +361 -0
- sharedbox-0.3.0rc0/src/sharedbox/_layout.py +192 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native/module.cpp +86 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native/notifier.cpp +80 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native/notifier.hpp +31 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native/segment.cpp +570 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native/segment.hpp +76 -0
- sharedbox-0.3.0rc0/src/sharedbox/_native.pyi +88 -0
- sharedbox-0.3.0rc0/src/sharedbox/_version.py +24 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/__init__.py +1 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/__main__.py +4 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/_app.py +183 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/cli.py +14 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/ops.py +278 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/roundtrip.py +308 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/size.py +125 -0
- sharedbox-0.3.0rc0/src/sharedbox/benchmarks/size_diff.py +86 -0
- sharedbox-0.3.0rc0/stubtest-allowlist.txt +8 -0
- sharedbox-0.3.0rc0/tests/conftest.py +16 -0
- sharedbox-0.3.0rc0/tests/test_box.py +393 -0
- sharedbox-0.3.0rc0/tests/test_events_async.py +177 -0
- sharedbox-0.3.0rc0/tests/test_events_signals.py +150 -0
- sharedbox-0.3.0rc0/tests/test_events_sync.py +140 -0
- sharedbox-0.3.0rc0/tests/test_fork.py +126 -0
- sharedbox-0.3.0rc0/tests/test_layout.py +136 -0
- sharedbox-0.3.0rc0/tests/test_native_segment.py +309 -0
- sharedbox-0.3.0rc0/tests/test_native_wait.py +85 -0
- sharedbox-0.3.0rc0/tests/type_checks/box_types.py +32 -0
- sharedbox-0.3.0rc0/uv.lock +763 -0
- sharedbox-0.3.0rc0/vcpkg.json +8 -0
- sharedbox-0.2.4/.github/workflows/ci.yaml +0 -119
- sharedbox-0.2.4/CHANGELOG.md +0 -37
- sharedbox-0.2.4/CMakeLists.txt +0 -102
- sharedbox-0.2.4/PKG-INFO +0 -175
- sharedbox-0.2.4/README.md +0 -158
- sharedbox-0.2.4/docs/readme.md +0 -234
- sharedbox-0.2.4/examples/basic_comparison.py +0 -328
- sharedbox-0.2.4/examples/concurrent_access.py +0 -415
- sharedbox-0.2.4/examples/getting_started.py +0 -285
- sharedbox-0.2.4/examples/initialization_demo.py +0 -355
- sharedbox-0.2.4/examples/memory_usage.py +0 -438
- sharedbox-0.2.4/examples/mixed_data_benchmark.py +0 -407
- sharedbox-0.2.4/examples/numpy_performance_benchmark.py +0 -291
- sharedbox-0.2.4/examples/shared_cache_example.py +0 -457
- sharedbox-0.2.4/install-vcpkg.sh +0 -57
- sharedbox-0.2.4/noxfile.py +0 -20
- sharedbox-0.2.4/pyproject.toml +0 -83
- sharedbox-0.2.4/src/sharedbox/__init__.py +0 -3
- sharedbox-0.2.4/src/sharedbox/_core/sharedmemory.cpp +0 -235
- sharedbox-0.2.4/src/sharedbox/_core/sharedmemory.hpp +0 -74
- sharedbox-0.2.4/src/sharedbox/_shareddict.pyi +0 -40
- sharedbox-0.2.4/src/sharedbox/_version.py +0 -34
- sharedbox-0.2.4/src/sharedbox/shareddict.cpp +0 -465
- sharedbox-0.2.4/src/sharedbox/shareddict.hpp +0 -86
- sharedbox-0.2.4/src/sharedbox/utils.py +0 -293
- sharedbox-0.2.4/tests/test_initialization_data.py +0 -263
- sharedbox-0.2.4/tests/test_mixed_data_types.py +0 -315
- sharedbox-0.2.4/tests/test_multiprocess_containers.py +0 -632
- sharedbox-0.2.4/tests/test_numpy_support.py +0 -216
- sharedbox-0.2.4/uv.lock +0 -328
- {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/src/sharedbox/py.typed +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
release:
|
|
9
|
+
types: [ published ]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
name: Lint
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
|
|
18
|
+
# local hooks in prek.toml call `uv run --locked --only-group lint ...`
|
|
19
|
+
- uses: astral-sh/setup-uv@v7
|
|
20
|
+
|
|
21
|
+
- uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
|
|
22
|
+
with:
|
|
23
|
+
prek-version: "0.5.3"
|
|
24
|
+
|
|
25
|
+
build_wheels:
|
|
26
|
+
name: Build wheel for ${{ matrix.python }}-${{ matrix.platform_id }}
|
|
27
|
+
runs-on: ${{ matrix.os }}
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
include:
|
|
32
|
+
- { os: windows-latest, python: cp311, platform_id: win_amd64 }
|
|
33
|
+
- { os: windows-latest, python: cp312, platform_id: win_amd64 }
|
|
34
|
+
- { os: windows-latest, python: cp314t, platform_id: win_amd64 }
|
|
35
|
+
- { os: ubuntu-latest, python: cp311, platform_id: manylinux_x86_64 }
|
|
36
|
+
- { os: ubuntu-latest, python: cp312, platform_id: manylinux_x86_64 }
|
|
37
|
+
- { os: ubuntu-latest, python: cp314t, platform_id: manylinux_x86_64 }
|
|
38
|
+
- { os: ubuntu-latest, python: cp311, platform_id: musllinux_x86_64 }
|
|
39
|
+
- { os: ubuntu-latest, python: cp312, platform_id: musllinux_x86_64 }
|
|
40
|
+
- { os: ubuntu-latest, python: cp314t, platform_id: musllinux_x86_64 }
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v5
|
|
44
|
+
with:
|
|
45
|
+
fetch-depth: 0
|
|
46
|
+
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
|
|
47
|
+
|
|
48
|
+
- name: Build wheels
|
|
49
|
+
uses: pypa/cibuildwheel@v3.2.0
|
|
50
|
+
env:
|
|
51
|
+
CIBW_BUILD: ${{ matrix.python }}-${{ matrix.platform_id }}
|
|
52
|
+
CIBW_CONTAINER_ENGINE: "docker; create_args: --shm-size=1g"
|
|
53
|
+
CIBW_TEST_REQUIRES: pytest
|
|
54
|
+
CIBW_TEST_COMMAND: pytest {project}/tests
|
|
55
|
+
with:
|
|
56
|
+
package-dir: .
|
|
57
|
+
output-dir: wheelhouse
|
|
58
|
+
|
|
59
|
+
- name: Upload wheel artifacts
|
|
60
|
+
uses: actions/upload-artifact@v4
|
|
61
|
+
with:
|
|
62
|
+
name: cibw-wheels-${{ matrix.python }}-${{ matrix.platform_id }}
|
|
63
|
+
path: ./wheelhouse/*.whl
|
|
64
|
+
|
|
65
|
+
- name: Measure wheel and extension size
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
echo "### Wheel size: ${{ matrix.python }}-${{ matrix.platform_id }}" >> "$GITHUB_STEP_SUMMARY"
|
|
69
|
+
# Run by path, not with -m: importing the sharedbox package loads its
|
|
70
|
+
# compiled extension and psygnal, which this runner does not have installed.
|
|
71
|
+
python src/sharedbox/benchmarks/size.py wheelhouse/*.whl --json wheelhouse/sizes.json --markdown >> "$GITHUB_STEP_SUMMARY"
|
|
72
|
+
|
|
73
|
+
- name: Upload size artifact
|
|
74
|
+
uses: actions/upload-artifact@v4
|
|
75
|
+
with:
|
|
76
|
+
name: wheel-sizes-${{ matrix.python }}-${{ matrix.platform_id }}
|
|
77
|
+
path: wheelhouse/sizes.json
|
|
78
|
+
|
|
79
|
+
sizes:
|
|
80
|
+
name: Report wheel size changes
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
needs: [build_wheels]
|
|
83
|
+
permissions:
|
|
84
|
+
contents: read
|
|
85
|
+
actions: read
|
|
86
|
+
continue-on-error: true
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v5
|
|
89
|
+
|
|
90
|
+
- name: Download this run's size artifacts
|
|
91
|
+
uses: actions/download-artifact@v5
|
|
92
|
+
with:
|
|
93
|
+
pattern: wheel-sizes-*
|
|
94
|
+
path: sizes/this
|
|
95
|
+
|
|
96
|
+
- name: Find the latest successful run on main
|
|
97
|
+
id: baseline
|
|
98
|
+
env:
|
|
99
|
+
GH_TOKEN: ${{ github.token }}
|
|
100
|
+
run: |
|
|
101
|
+
run_id=$(gh run list --repo "${{ github.repository }}" --workflow ci.yaml \
|
|
102
|
+
--branch main --status success --limit 1 --json databaseId --jq '.[0].databaseId // empty')
|
|
103
|
+
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
|
|
104
|
+
|
|
105
|
+
- name: Download the baseline's size artifacts
|
|
106
|
+
if: steps.baseline.outputs.run_id != ''
|
|
107
|
+
continue-on-error: true
|
|
108
|
+
uses: actions/download-artifact@v5
|
|
109
|
+
with:
|
|
110
|
+
pattern: wheel-sizes-*
|
|
111
|
+
path: sizes/main
|
|
112
|
+
run-id: ${{ steps.baseline.outputs.run_id }}
|
|
113
|
+
github-token: ${{ github.token }}
|
|
114
|
+
|
|
115
|
+
- name: Report size differences
|
|
116
|
+
shell: bash
|
|
117
|
+
# Run by path, not with -m: importing the sharedbox package loads its
|
|
118
|
+
# compiled extension and psygnal, which this runner does not have installed.
|
|
119
|
+
run: python src/sharedbox/benchmarks/size_diff.py sizes/this sizes/main >> "$GITHUB_STEP_SUMMARY"
|
|
120
|
+
|
|
121
|
+
make_sdist:
|
|
122
|
+
name: Make SDist
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
needs: [build_wheels]
|
|
125
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
126
|
+
steps:
|
|
127
|
+
- name: Check the release tag and pre-release flag
|
|
128
|
+
env:
|
|
129
|
+
TAG: ${{ github.event.release.tag_name }}
|
|
130
|
+
PRERELEASE: ${{ github.event.release.prerelease }}
|
|
131
|
+
run: |
|
|
132
|
+
if ! [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
|
|
133
|
+
echo "::error::tag $TAG is not vX.Y.Z or vX.Y.ZrcN"; exit 1
|
|
134
|
+
fi
|
|
135
|
+
case "$TAG" in
|
|
136
|
+
*rc*) [ "$PRERELEASE" = "true" ] || { echo "::error::$TAG must be marked pre-release"; exit 1; } ;;
|
|
137
|
+
*) [ "$PRERELEASE" = "false" ] || { echo "::error::$TAG must not be marked pre-release"; exit 1; } ;;
|
|
138
|
+
esac
|
|
139
|
+
|
|
140
|
+
- uses: actions/checkout@v5
|
|
141
|
+
with:
|
|
142
|
+
fetch-depth: 0
|
|
143
|
+
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
|
|
144
|
+
|
|
145
|
+
- name: Build SDist
|
|
146
|
+
run: pipx run build --sdist
|
|
147
|
+
|
|
148
|
+
- name: Check the sdist version matches the tag
|
|
149
|
+
env:
|
|
150
|
+
TAG: ${{ github.event.release.tag_name }}
|
|
151
|
+
run: test -f "dist/sharedbox-${TAG#v}.tar.gz"
|
|
152
|
+
|
|
153
|
+
- uses: actions/upload-artifact@v4
|
|
154
|
+
with:
|
|
155
|
+
name: cibw-sdist
|
|
156
|
+
path: dist/*.tar.gz
|
|
157
|
+
|
|
158
|
+
upload_pypi:
|
|
159
|
+
name: Upload to PyPI
|
|
160
|
+
needs: [lint, build_wheels, make_sdist]
|
|
161
|
+
runs-on: ubuntu-latest
|
|
162
|
+
environment:
|
|
163
|
+
name: pypi
|
|
164
|
+
url: https://pypi.org/p/sharedbox
|
|
165
|
+
permissions:
|
|
166
|
+
id-token: write
|
|
167
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
168
|
+
steps:
|
|
169
|
+
- uses: actions/download-artifact@v5
|
|
170
|
+
with:
|
|
171
|
+
# unpacks all CIBW artifacts into dist/
|
|
172
|
+
pattern: cibw-*
|
|
173
|
+
path: dist
|
|
174
|
+
merge-multiple: true
|
|
175
|
+
|
|
176
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CodSpeed
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
benchmarks:
|
|
16
|
+
name: Run benchmarks
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Set up vcpkg at the manifest baseline
|
|
24
|
+
run: |
|
|
25
|
+
git clone https://github.com/microsoft/vcpkg.git "${{ runner.temp }}/vcpkg"
|
|
26
|
+
git -C "${{ runner.temp }}/vcpkg" checkout "$(jq -r '."builtin-baseline"' vcpkg.json)"
|
|
27
|
+
"${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
|
|
28
|
+
echo "VCPKG_ROOT=${{ runner.temp }}/vcpkg" >> "$GITHUB_ENV"
|
|
29
|
+
|
|
30
|
+
- uses: astral-sh/setup-uv@v7
|
|
31
|
+
|
|
32
|
+
- name: Install the project
|
|
33
|
+
run: uv sync --dev --frozen --python 3.12
|
|
34
|
+
|
|
35
|
+
- name: Run benchmarks
|
|
36
|
+
uses: CodSpeedHQ/action@v5
|
|
37
|
+
with:
|
|
38
|
+
mode: simulation
|
|
39
|
+
run: uv run --no-sync pytest benchmarks --codspeed
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Dates are marked as `DD-MM-YYYY`
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - Unreleased
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `SharedBox`: base class whose annotated fields live in a shared-memory segment.
|
|
15
|
+
- `Capacity`: byte capacity for `str` and `bytes` fields.
|
|
16
|
+
- `FieldWatch`: `for` and `async for` over new values of a field.
|
|
17
|
+
- `SharedBox.events`: psygnal `SignalGroup` with one `(new, old)` signal per
|
|
18
|
+
field.
|
|
19
|
+
- `BoxClosedError`, `LockTimeoutError`, `SchemaMismatchError`,
|
|
20
|
+
`SegmentExistsError`, `SegmentNotFoundError`.
|
|
21
|
+
- `benchbox`: command with `ops`, `roundtrip`, `size` and `all`
|
|
22
|
+
benchmarks, also run as `python -m sharedbox.benchmarks`.
|
|
23
|
+
- `benchmarks` extra: installs Typer and pyperf for `benchbox`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Building the extension requires nanobind 3.1.0 or newer.
|
|
28
|
+
- Wheels per platform: `cp311-cp311`, `cp312-abi3` for CPython 3.12 and
|
|
29
|
+
newer, and `cp314-cp314t` for free-threaded CPython 3.14.
|
|
30
|
+
- Boost is installed through a vcpkg manifest pinned to one baseline.
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
34
|
+
- Python 3.10 support.
|
|
35
|
+
- `SharedDict` and `sharedbox.utils`.
|
|
36
|
+
|
|
37
|
+
## [0.2.4] - 05-10-2025
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Rewrite codebase in nanobind
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Parallelize CI so that each wheel is built with the correct version
|
|
46
|
+
- Also faster builds
|
|
47
|
+
|
|
48
|
+
## [0.1.0] - 29-09-2025
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- Initial release
|
|
53
|
+
|
|
54
|
+
[0.2.4]: (https://github.com/jacopoabramo/sharedbox/compare/0.1.0...0.2.4)
|
|
55
|
+
[0.1.0]: (https://github.com/jacopoabramo/sharedbox/commits/0.1.0)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
`sharedbox` gives Python processes a record that lives in shared memory.
|
|
4
|
+
`SharedBox` is a base class: a subclass's annotated fields are stored in one
|
|
5
|
+
named segment that every process can open. The segment is C++
|
|
6
|
+
(Boost.Interprocess) exposed to Python with nanobind; the Python side decides
|
|
7
|
+
the layout and encodes values.
|
|
8
|
+
|
|
9
|
+
## Repository layout
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
sharedbox/
|
|
13
|
+
|-- src/sharedbox/
|
|
14
|
+
| |-- __init__.py re-exports the public API
|
|
15
|
+
| |-- _box.py SharedBox: class keywords, fields, create/attach, update, snapshot, unlink
|
|
16
|
+
| |-- _layout.py Capacity, field offsets and encoding, schema hash
|
|
17
|
+
| |-- _events.py FieldWatch, the watcher thread, psygnal events
|
|
18
|
+
| |-- _native.pyi hand-written stub for the extension
|
|
19
|
+
| |-- py.typed
|
|
20
|
+
| |-- benchmarks/ the benchbox command (extra: benchmarks)
|
|
21
|
+
| | |-- cli.py entry point; exits with an install hint without Typer
|
|
22
|
+
| | |-- _app.py Typer commands: ops, roundtrip, size, all
|
|
23
|
+
| | |-- ops.py pyperf timings of single operations, against the stdlib
|
|
24
|
+
| | |-- roundtrip.py cross-process round trip percentiles
|
|
25
|
+
| | |-- size.py wheel and extension size (standard library only)
|
|
26
|
+
| | `-- size_diff.py wheel size table against main, for CI (standard library only)
|
|
27
|
+
| `-- _native/
|
|
28
|
+
| |-- module.cpp nanobind module: Segment and the error classes
|
|
29
|
+
| |-- segment.{hpp,cpp} the segment: header, record, sequence lock
|
|
30
|
+
| `-- notifier.{hpp,cpp} wakes waiters across processes (futex, named semaphore)
|
|
31
|
+
|-- tests/ pytest; many tests spawn processes
|
|
32
|
+
| `-- type_checks/ checked by mypy, never imported at run time
|
|
33
|
+
|-- benchmarks/ not collected by the default pytest run
|
|
34
|
+
| `-- test_bench_box.py pytest-codspeed benchmarks, run by codspeed.yml
|
|
35
|
+
|-- scripts/vscode_setup.py points VS Code's C/C++ extension at the build headers
|
|
36
|
+
|-- docs/api.md API reference
|
|
37
|
+
|-- docs/design/ design notes for the native segment
|
|
38
|
+
|-- .github/workflows/ci.yaml cibuildwheel wheels, tests, PyPI publish
|
|
39
|
+
|-- .github/workflows/codspeed.yml benchmarks on CodSpeed
|
|
40
|
+
|-- CMakeLists.txt extension build
|
|
41
|
+
|-- vcpkg.json Boost dependency and vcpkg baseline
|
|
42
|
+
|-- stubtest-allowlist.txt stubtest exceptions for nanobind types
|
|
43
|
+
|-- .clang-format clang-format style for src/sharedbox/_native/
|
|
44
|
+
|-- prek.toml prek hooks: ruff, clang-format, and the builtin checks
|
|
45
|
+
|-- pyproject.toml scikit-build-core, setuptools-scm, pytest, cibuildwheel, mypy, tox
|
|
46
|
+
`-- uv.lock
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- `_native.pyi` is written by hand. Update it whenever the bound API changes;
|
|
50
|
+
the tox `mypy` env runs stubtest against it.
|
|
51
|
+
|
|
52
|
+
## Memory layout
|
|
53
|
+
|
|
54
|
+
The design and its reasons are in `docs/design/native-segment.md`.
|
|
55
|
+
|
|
56
|
+
One segment per box: `managed_windows_shared_memory` on Windows (page-file
|
|
57
|
+
backed), `managed_shared_memory` on Linux (`/dev/shm`, mode `0600`). The
|
|
58
|
+
name is the `name` class keyword, the name passed to `create()`, or by
|
|
59
|
+
default `sharedbox-` plus 16 hex digits of SHA-256 over the class's
|
|
60
|
+
`module.qualname` (`__mp_main__` counts as `__main__`). Creating always asks
|
|
61
|
+
for a new name and raises `SegmentExistsError` if it is taken.
|
|
62
|
+
|
|
63
|
+
The segment holds a named object `"sharedbox.header"` (64 bytes, one cache
|
|
64
|
+
line) and one block with the tail and the record. The tail is `field_count`
|
|
65
|
+
`StoredField` entries of 8 bytes (`u32 offset`, `u32 capacity_and_kind`, top
|
|
66
|
+
bit set for a prefixed field), then one `u64` write count per field. The
|
|
67
|
+
record follows, 64-byte aligned. The segment size is these plus 1024 bytes
|
|
68
|
+
for Boost's bookkeeping, rounded up to 4 KiB. `static_assert`s in
|
|
69
|
+
`segment.cpp` check every `sizeof` and `offsetof`.
|
|
70
|
+
|
|
71
|
+
`Header` fields:
|
|
72
|
+
|
|
73
|
+
- `magic`: written last on create; `attach()` waits for it.
|
|
74
|
+
- `abi_version`: `2`; any other value is refused. `magic` and `abi_version`
|
|
75
|
+
keep their offsets across versions.
|
|
76
|
+
- `field_count`, `record_size`.
|
|
77
|
+
- `schema_hash`: first 8 bytes of SHA-256 over the class identity and each
|
|
78
|
+
field's `name:kind:capacity`. `attach()` raises `SchemaMismatchError` if
|
|
79
|
+
it differs.
|
|
80
|
+
- `record`, `tail`: `u32` offsets of the record and the tail in the segment.
|
|
81
|
+
`attach()` checks both against the segment size, checks each field table
|
|
82
|
+
entry and keeps its own copy.
|
|
83
|
+
- `seq`: sequence lock. Even when free, odd while a write runs. Readers copy
|
|
84
|
+
and retry if `seq` moved; writers take it with a compare-and-swap and give
|
|
85
|
+
up after `lock_timeout` with `LockTimeoutError`.
|
|
86
|
+
- `generation`: counts every write. `wake_word` and `waiters` wake threads
|
|
87
|
+
that wait for a change.
|
|
88
|
+
- `writer_pid`: process holding the write lock, cleared by a normal unlock.
|
|
89
|
+
`force_unlock()` releases the lock and leaves `writer_pid` as it is.
|
|
90
|
+
|
|
91
|
+
### Record encoding
|
|
92
|
+
|
|
93
|
+
Fields in declaration order, base classes first, each starting at a multiple
|
|
94
|
+
of 8 bytes. Everything is little-endian.
|
|
95
|
+
|
|
96
|
+
- `bool`: 1 byte, `0x00` or `0x01`.
|
|
97
|
+
- `int`: 8 bytes, signed.
|
|
98
|
+
- `float`: 8 bytes, IEEE 754 double.
|
|
99
|
+
- `str`, `bytes`: `u32` length, then up to `capacity` bytes (UTF-8 for `str`).
|
|
100
|
+
|
|
101
|
+
Nothing is pickled. At most 256 fields; a capacity is 1 byte to 1 MiB.
|
|
102
|
+
|
|
103
|
+
### Lifecycle
|
|
104
|
+
|
|
105
|
+
- `close()` stops the watcher thread and detaches this box. Later reads and
|
|
106
|
+
writes raise `BoxClosedError`. Garbage collection closes a box too.
|
|
107
|
+
- `unlink()` removes the name on Linux and does nothing on Windows, where the
|
|
108
|
+
OS frees the segment with its last handle.
|
|
109
|
+
- A Linux segment that is never unlinked stays in `/dev/shm`. Most tests use the
|
|
110
|
+
`unique_name` fixture, which removes the file afterwards.
|
|
111
|
+
|
|
112
|
+
## Build
|
|
113
|
+
|
|
114
|
+
scikit-build-core drives CMake (3.30 or newer), which needs:
|
|
115
|
+
|
|
116
|
+
- `VCPKG_ROOT` set; CMake stops without it.
|
|
117
|
+
- Boost from `vcpkg.json`, installed by CMake on the first build.
|
|
118
|
+
- nanobind (build dependency) and a C++17 compiler (MSVC or GCC). macOS is
|
|
119
|
+
not supported.
|
|
120
|
+
|
|
121
|
+
The version comes from git tags through setuptools-scm, written to
|
|
122
|
+
`src/sharedbox/_version.py` (ignored by git).
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
uv sync --dev # creates .venv, builds and installs the extension
|
|
126
|
+
uv run python scripts/vscode_setup.py # once, for VS Code's C/C++ extension
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`[tool.uv] cache-keys` lists the C++ sources, so `uv sync` rebuilds the
|
|
130
|
+
extension after they change. Build folders are `build/<wheel tag>`.
|
|
131
|
+
|
|
132
|
+
Wheels per platform (Windows x64, Linux x86_64 glibc and musl): `cp311-cp311`,
|
|
133
|
+
`cp312-abi3` for CPython 3.12 and newer, and `cp314-cp314t` for free-threaded
|
|
134
|
+
CPython 3.14. One `nanobind_add_module(... STABLE_ABI FREE_THREADED ...)` line
|
|
135
|
+
produces all three.
|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
uv run pytest # current interpreter
|
|
141
|
+
uv run pytest tests/test_box.py -k pickle
|
|
142
|
+
uv run tox # py311 to py314, py314t, mypy
|
|
143
|
+
uv run tox -e py314t # one env
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
CI (`.github/workflows/ci.yaml`) builds the wheels above with cibuildwheel,
|
|
147
|
+
runs pytest against each wheel, and publishes to PyPI. Publishing runs only
|
|
148
|
+
from a GitHub release tagged `vX.Y.Z` and marked as a release, or
|
|
149
|
+
`vX.Y.ZrcN` and marked as a pre-release; any other tag or mismatch between
|
|
150
|
+
the tag and the pre-release flag fails the build before it uploads. Docker
|
|
151
|
+
runs with `--shm-size=1g`, so keep test segments under that.
|
|
152
|
+
|
|
153
|
+
## Usage
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import multiprocessing as mp
|
|
157
|
+
from typing import Annotated
|
|
158
|
+
|
|
159
|
+
from sharedbox import Capacity, SharedBox
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class Motor(SharedBox):
|
|
163
|
+
position: int
|
|
164
|
+
enabled: bool
|
|
165
|
+
label: Annotated[str, Capacity(32)]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def worker() -> None:
|
|
169
|
+
motor = Motor.attach() # finds the box by its class
|
|
170
|
+
motor.position = 10
|
|
171
|
+
motor.close()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
with Motor(1, False, "x-axis") as motor:
|
|
176
|
+
motor.events.position.connect(lambda new, old: print(old, "->", new))
|
|
177
|
+
child = mp.Process(target=worker)
|
|
178
|
+
child.start()
|
|
179
|
+
child.join() # prints: 1 -> 10
|
|
180
|
+
Motor.unlink()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Every read decodes a fresh value from the segment. `update(**values)` writes
|
|
184
|
+
several fields at once; `watch(field)` and `events` report changes from any
|
|
185
|
+
process.
|
|
186
|
+
|
|
187
|
+
## Conventions
|
|
188
|
+
|
|
189
|
+
- Changelog: `CHANGELOG.md`, Keep a Changelog format, dates as `DD-MM-YYYY`.
|
|
190
|
+
- Lint and format Python with `ruff` and C++ with `clang-format`, both run
|
|
191
|
+
through prek: `uv run prek run --all-files`, `uv run tox -e lint`.
|
|
192
|
+
- Change dependencies with `uv add` / `uv remove`, never by editing
|
|
193
|
+
`pyproject.toml`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.30...4.4)
|
|
2
|
+
|
|
3
|
+
if(NOT DEFINED ENV{VCPKG_ROOT})
|
|
4
|
+
message(FATAL_ERROR "Set VCPKG_ROOT to a vcpkg checkout.")
|
|
5
|
+
endif()
|
|
6
|
+
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
|
|
7
|
+
CACHE STRING "vcpkg toolchain file")
|
|
8
|
+
|
|
9
|
+
project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)
|
|
10
|
+
|
|
11
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
12
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
13
|
+
|
|
14
|
+
find_package(Python 3.11 REQUIRED
|
|
15
|
+
COMPONENTS Interpreter Development.Module
|
|
16
|
+
OPTIONAL_COMPONENTS Development.SABIModule)
|
|
17
|
+
execute_process(
|
|
18
|
+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
|
|
19
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
|
|
20
|
+
find_package(nanobind CONFIG REQUIRED)
|
|
21
|
+
find_package(Boost REQUIRED CONFIG)
|
|
22
|
+
find_package(Threads REQUIRED)
|
|
23
|
+
|
|
24
|
+
nanobind_add_module(_native
|
|
25
|
+
STABLE_ABI
|
|
26
|
+
FREE_THREADED
|
|
27
|
+
LTO
|
|
28
|
+
NB_DOMAIN sharedbox
|
|
29
|
+
src/sharedbox/_native/module.cpp
|
|
30
|
+
src/sharedbox/_native/segment.cpp
|
|
31
|
+
src/sharedbox/_native/notifier.cpp)
|
|
32
|
+
target_link_libraries(_native PRIVATE Boost::headers)
|
|
33
|
+
|
|
34
|
+
if(WIN32)
|
|
35
|
+
target_compile_definitions(_native PRIVATE
|
|
36
|
+
BOOST_ALL_NO_LIB WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0A00)
|
|
37
|
+
else()
|
|
38
|
+
target_link_libraries(_native PRIVATE rt Threads::Threads)
|
|
39
|
+
endif()
|
|
40
|
+
|
|
41
|
+
install(TARGETS _native LIBRARY DESTINATION sharedbox)
|
|
@@ -198,4 +198,4 @@
|
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
201
|
+
limitations under the License.
|