rusty-ring 0.2.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.
- rusty_ring-0.2.0/CLAUDE.md +31 -0
- rusty_ring-0.2.0/Cargo.lock +197 -0
- rusty_ring-0.2.0/Cargo.toml +14 -0
- rusty_ring-0.2.0/PKG-INFO +26 -0
- rusty_ring-0.2.0/README.md +3 -0
- rusty_ring-0.2.0/justfile +34 -0
- rusty_ring-0.2.0/pyproject.toml +36 -0
- rusty_ring-0.2.0/python/rusty_ring/__init__.py +121 -0
- rusty_ring-0.2.0/python/rusty_ring/__init__.pyi +169 -0
- rusty_ring-0.2.0/python/rusty_ring/py.typed +0 -0
- rusty_ring-0.2.0/src/lib.rs +678 -0
- rusty_ring-0.2.0/src/main.rs +3 -0
- rusty_ring-0.2.0/tests/test_ring.py +77 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# rusty-ring
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Rust-based io_uring bindings for Python via PyO3. Replaces the external `liburing` (Cython) dependency with bindings built on the `io-uring` crate from tokio-rs.
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/ # Rust source code
|
|
11
|
+
python/rusty_ring/ # Python type stubs (maturin mixed layout)
|
|
12
|
+
tests/ # Python tests
|
|
13
|
+
pyproject.toml # Package metadata (maturin build backend)
|
|
14
|
+
Cargo.toml # Rust package config
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
just dev # Build debug extension via maturin develop
|
|
21
|
+
just dev-release # Build release extension
|
|
22
|
+
just test # Run Python tests (builds first)
|
|
23
|
+
just test-rust # Run Rust tests
|
|
24
|
+
just check # Run all checks
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Notes
|
|
28
|
+
|
|
29
|
+
- Uses maturin (not hatchling) as build backend
|
|
30
|
+
- Not managed by copier/bonfire template
|
|
31
|
+
- `src/` contains Rust code, not Python — Python stubs live in `python/`
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "bitflags"
|
|
13
|
+
version = "2.11.0"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "cfg-if"
|
|
19
|
+
version = "1.0.4"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "heck"
|
|
25
|
+
version = "0.5.0"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "indoc"
|
|
31
|
+
version = "2.0.7"
|
|
32
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
33
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
34
|
+
dependencies = [
|
|
35
|
+
"rustversion",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "io-uring"
|
|
40
|
+
version = "0.7.11"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344"
|
|
43
|
+
dependencies = [
|
|
44
|
+
"bitflags",
|
|
45
|
+
"cfg-if",
|
|
46
|
+
"libc",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[[package]]
|
|
50
|
+
name = "libc"
|
|
51
|
+
version = "0.2.182"
|
|
52
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
53
|
+
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "memoffset"
|
|
57
|
+
version = "0.9.1"
|
|
58
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
59
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
60
|
+
dependencies = [
|
|
61
|
+
"autocfg",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "once_cell"
|
|
66
|
+
version = "1.21.3"
|
|
67
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "portable-atomic"
|
|
72
|
+
version = "1.13.1"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "proc-macro2"
|
|
78
|
+
version = "1.0.106"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
81
|
+
dependencies = [
|
|
82
|
+
"unicode-ident",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "pyo3"
|
|
87
|
+
version = "0.27.2"
|
|
88
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
89
|
+
checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d"
|
|
90
|
+
dependencies = [
|
|
91
|
+
"indoc",
|
|
92
|
+
"libc",
|
|
93
|
+
"memoffset",
|
|
94
|
+
"once_cell",
|
|
95
|
+
"portable-atomic",
|
|
96
|
+
"pyo3-build-config",
|
|
97
|
+
"pyo3-ffi",
|
|
98
|
+
"pyo3-macros",
|
|
99
|
+
"unindent",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "pyo3-build-config"
|
|
104
|
+
version = "0.27.2"
|
|
105
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
106
|
+
checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6"
|
|
107
|
+
dependencies = [
|
|
108
|
+
"target-lexicon",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "pyo3-ffi"
|
|
113
|
+
version = "0.27.2"
|
|
114
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
|
+
checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089"
|
|
116
|
+
dependencies = [
|
|
117
|
+
"libc",
|
|
118
|
+
"pyo3-build-config",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
[[package]]
|
|
122
|
+
name = "pyo3-macros"
|
|
123
|
+
version = "0.27.2"
|
|
124
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
125
|
+
checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02"
|
|
126
|
+
dependencies = [
|
|
127
|
+
"proc-macro2",
|
|
128
|
+
"pyo3-macros-backend",
|
|
129
|
+
"quote",
|
|
130
|
+
"syn",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "pyo3-macros-backend"
|
|
135
|
+
version = "0.27.2"
|
|
136
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
137
|
+
checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9"
|
|
138
|
+
dependencies = [
|
|
139
|
+
"heck",
|
|
140
|
+
"proc-macro2",
|
|
141
|
+
"pyo3-build-config",
|
|
142
|
+
"quote",
|
|
143
|
+
"syn",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
[[package]]
|
|
147
|
+
name = "quote"
|
|
148
|
+
version = "1.0.44"
|
|
149
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
150
|
+
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
|
151
|
+
dependencies = [
|
|
152
|
+
"proc-macro2",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
[[package]]
|
|
156
|
+
name = "rustversion"
|
|
157
|
+
version = "1.0.22"
|
|
158
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
159
|
+
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
160
|
+
|
|
161
|
+
[[package]]
|
|
162
|
+
name = "rusty-ring"
|
|
163
|
+
version = "0.1.0"
|
|
164
|
+
dependencies = [
|
|
165
|
+
"io-uring",
|
|
166
|
+
"libc",
|
|
167
|
+
"pyo3",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "syn"
|
|
172
|
+
version = "2.0.117"
|
|
173
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
174
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
175
|
+
dependencies = [
|
|
176
|
+
"proc-macro2",
|
|
177
|
+
"quote",
|
|
178
|
+
"unicode-ident",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
[[package]]
|
|
182
|
+
name = "target-lexicon"
|
|
183
|
+
version = "0.13.5"
|
|
184
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
185
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
186
|
+
|
|
187
|
+
[[package]]
|
|
188
|
+
name = "unicode-ident"
|
|
189
|
+
version = "1.0.24"
|
|
190
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
191
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
192
|
+
|
|
193
|
+
[[package]]
|
|
194
|
+
name = "unindent"
|
|
195
|
+
version = "0.2.4"
|
|
196
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
197
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rusty-ring"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "rusty_ring"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
pyo3 = { version = "0.27", features = ["extension-module"] }
|
|
13
|
+
io-uring = "0.7"
|
|
14
|
+
libc = "0.2"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rusty-ring
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Classifier: Programming Language :: Rust
|
|
11
|
+
Classifier: Topic :: System :: Networking
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Summary: Rust-based io_uring bindings via PyO3
|
|
14
|
+
Keywords: io_uring,rust,pyo3,async,io,linux
|
|
15
|
+
Author-email: Otto Sellerstam <ottosellerstam@gmail.com>
|
|
16
|
+
License: MIT
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
19
|
+
Project-URL: Homepage, https://github.com/otto-sellerstam/one-ring
|
|
20
|
+
Project-URL: Issues, https://github.com/otto-sellerstam/one-ring/issues
|
|
21
|
+
Project-URL: Repository, https://github.com/otto-sellerstam/one-ring
|
|
22
|
+
|
|
23
|
+
# rusty-ring
|
|
24
|
+
|
|
25
|
+
Rust-based `io_uring` bindings for Python via PyO3, built on the [`io-uring`](https://crates.io/crates/io-uring) crate from `tokio-rs`.
|
|
26
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# List available recipes
|
|
2
|
+
default:
|
|
3
|
+
@just --list
|
|
4
|
+
|
|
5
|
+
# Build the Rust extension (debug, for development)
|
|
6
|
+
dev:
|
|
7
|
+
uv run maturin develop
|
|
8
|
+
|
|
9
|
+
# Build the Rust extension (release)
|
|
10
|
+
dev-release:
|
|
11
|
+
uv run maturin develop --release
|
|
12
|
+
|
|
13
|
+
# Run tests (builds first)
|
|
14
|
+
test: dev
|
|
15
|
+
uv run pytest tests/
|
|
16
|
+
|
|
17
|
+
# Run clippy linter (fail on warnings)
|
|
18
|
+
clippy:
|
|
19
|
+
cargo clippy -- -D warnings
|
|
20
|
+
|
|
21
|
+
# Format Rust code
|
|
22
|
+
fmt:
|
|
23
|
+
cargo fmt
|
|
24
|
+
|
|
25
|
+
# Check Rust formatting
|
|
26
|
+
fmt-check:
|
|
27
|
+
cargo fmt -- --check
|
|
28
|
+
|
|
29
|
+
# Run Rust tests
|
|
30
|
+
test-rust:
|
|
31
|
+
cargo test
|
|
32
|
+
|
|
33
|
+
# Run all checks
|
|
34
|
+
check: clippy fmt-check test-rust test
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rusty-ring"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Rust-based io_uring bindings via PyO3"
|
|
5
|
+
requires-python = ">=3.14"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Otto Sellerstam", email = "ottosellerstam@gmail.com" },
|
|
9
|
+
]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["io_uring", "rust", "pyo3", "async", "io", "linux"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: POSIX :: Linux",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Programming Language :: Rust",
|
|
20
|
+
"Topic :: System :: Networking",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/otto-sellerstam/one-ring"
|
|
26
|
+
Repository = "https://github.com/otto-sellerstam/one-ring"
|
|
27
|
+
Issues = "https://github.com/otto-sellerstam/one-ring/issues"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["maturin>=1.8,<2.0"]
|
|
31
|
+
build-backend = "maturin"
|
|
32
|
+
|
|
33
|
+
[tool.maturin]
|
|
34
|
+
features = ["pyo3/extension-module"]
|
|
35
|
+
python-source = "python"
|
|
36
|
+
module-name = "rusty_ring._rusty_ring"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from rusty_ring._rusty_ring import (
|
|
2
|
+
AF_INET,
|
|
3
|
+
AF_INET6,
|
|
4
|
+
AF_UNIX,
|
|
5
|
+
AT_EMPTY_PATH,
|
|
6
|
+
AT_FDCWD,
|
|
7
|
+
AT_SYMLINK_NOFOLLOW,
|
|
8
|
+
IPPROTO_TCP,
|
|
9
|
+
MSG_DONTWAIT,
|
|
10
|
+
MSG_NOSIGNAL,
|
|
11
|
+
O_APPEND,
|
|
12
|
+
O_CLOEXEC,
|
|
13
|
+
O_CREAT,
|
|
14
|
+
O_NONBLOCK,
|
|
15
|
+
O_RDONLY,
|
|
16
|
+
O_RDWR,
|
|
17
|
+
O_TRUNC,
|
|
18
|
+
O_WRONLY,
|
|
19
|
+
S_IFDIR,
|
|
20
|
+
S_IFIFO,
|
|
21
|
+
S_IFLNK,
|
|
22
|
+
S_IFMT,
|
|
23
|
+
S_IFREG,
|
|
24
|
+
S_IFSOCK,
|
|
25
|
+
S_IRGRP,
|
|
26
|
+
S_IROTH,
|
|
27
|
+
S_IRUSR,
|
|
28
|
+
S_IWGRP,
|
|
29
|
+
S_IWOTH,
|
|
30
|
+
S_IWUSR,
|
|
31
|
+
S_IXGRP,
|
|
32
|
+
S_IXOTH,
|
|
33
|
+
S_IXUSR,
|
|
34
|
+
SFD_CLOEXEC,
|
|
35
|
+
SFD_NONBLOCK,
|
|
36
|
+
SIGHUP,
|
|
37
|
+
SIGINT,
|
|
38
|
+
SIGTERM,
|
|
39
|
+
SO_KEEPALIVE,
|
|
40
|
+
SO_REUSEADDR,
|
|
41
|
+
SO_REUSEPORT,
|
|
42
|
+
SOCK_CLOEXEC,
|
|
43
|
+
SOCK_DGRAM,
|
|
44
|
+
SOCK_NONBLOCK,
|
|
45
|
+
SOCK_STREAM,
|
|
46
|
+
SOL_SOCKET,
|
|
47
|
+
STATX_ALL,
|
|
48
|
+
STATX_ATIME,
|
|
49
|
+
STATX_CTIME,
|
|
50
|
+
STATX_INO,
|
|
51
|
+
STATX_MODE,
|
|
52
|
+
STATX_MTIME,
|
|
53
|
+
STATX_SIZE,
|
|
54
|
+
STATX_TYPE,
|
|
55
|
+
TCP_NODELAY,
|
|
56
|
+
CompletionEvent,
|
|
57
|
+
Ring,
|
|
58
|
+
SockAddr,
|
|
59
|
+
StatxBuffer,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"AF_INET",
|
|
64
|
+
"AF_INET6",
|
|
65
|
+
"AF_UNIX",
|
|
66
|
+
"AT_EMPTY_PATH",
|
|
67
|
+
"AT_FDCWD",
|
|
68
|
+
"AT_SYMLINK_NOFOLLOW",
|
|
69
|
+
"IPPROTO_TCP",
|
|
70
|
+
"MSG_DONTWAIT",
|
|
71
|
+
"MSG_NOSIGNAL",
|
|
72
|
+
"O_APPEND",
|
|
73
|
+
"O_CLOEXEC",
|
|
74
|
+
"O_CREAT",
|
|
75
|
+
"O_NONBLOCK",
|
|
76
|
+
"O_RDONLY",
|
|
77
|
+
"O_RDWR",
|
|
78
|
+
"O_TRUNC",
|
|
79
|
+
"O_WRONLY",
|
|
80
|
+
"SFD_CLOEXEC",
|
|
81
|
+
"SFD_NONBLOCK",
|
|
82
|
+
"SIGHUP",
|
|
83
|
+
"SIGINT",
|
|
84
|
+
"SIGTERM",
|
|
85
|
+
"SOCK_CLOEXEC",
|
|
86
|
+
"SOCK_DGRAM",
|
|
87
|
+
"SOCK_NONBLOCK",
|
|
88
|
+
"SOCK_STREAM",
|
|
89
|
+
"SOL_SOCKET",
|
|
90
|
+
"SO_KEEPALIVE",
|
|
91
|
+
"SO_REUSEADDR",
|
|
92
|
+
"SO_REUSEPORT",
|
|
93
|
+
"STATX_ALL",
|
|
94
|
+
"STATX_ATIME",
|
|
95
|
+
"STATX_CTIME",
|
|
96
|
+
"STATX_INO",
|
|
97
|
+
"STATX_MODE",
|
|
98
|
+
"STATX_MTIME",
|
|
99
|
+
"STATX_SIZE",
|
|
100
|
+
"STATX_TYPE",
|
|
101
|
+
"S_IFDIR",
|
|
102
|
+
"S_IFIFO",
|
|
103
|
+
"S_IFLNK",
|
|
104
|
+
"S_IFMT",
|
|
105
|
+
"S_IFREG",
|
|
106
|
+
"S_IFSOCK",
|
|
107
|
+
"S_IRGRP",
|
|
108
|
+
"S_IROTH",
|
|
109
|
+
"S_IRUSR",
|
|
110
|
+
"S_IWGRP",
|
|
111
|
+
"S_IWOTH",
|
|
112
|
+
"S_IWUSR",
|
|
113
|
+
"S_IXGRP",
|
|
114
|
+
"S_IXOTH",
|
|
115
|
+
"S_IXUSR",
|
|
116
|
+
"TCP_NODELAY",
|
|
117
|
+
"CompletionEvent",
|
|
118
|
+
"Ring",
|
|
119
|
+
"SockAddr",
|
|
120
|
+
"StatxBuffer",
|
|
121
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
class SockAddr:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def v4(ip: str, port: int) -> SockAddr: ...
|
|
7
|
+
@staticmethod
|
|
8
|
+
def v6(ip: str, port: int) -> SockAddr: ...
|
|
9
|
+
|
|
10
|
+
class CompletionEvent:
|
|
11
|
+
@property
|
|
12
|
+
def user_data(self) -> int: ...
|
|
13
|
+
@property
|
|
14
|
+
def res(self) -> int: ...
|
|
15
|
+
@property
|
|
16
|
+
def flags(self) -> int: ...
|
|
17
|
+
|
|
18
|
+
class Ring:
|
|
19
|
+
def __init__(self, depth: int = 32) -> None: ...
|
|
20
|
+
def __enter__(self) -> Self: ...
|
|
21
|
+
def __exit__(
|
|
22
|
+
self,
|
|
23
|
+
exc_type: type[BaseException] | None,
|
|
24
|
+
exc_val: BaseException | None,
|
|
25
|
+
exc_tb: types.TracebackType | None,
|
|
26
|
+
) -> bool: ...
|
|
27
|
+
def submit(self) -> int: ...
|
|
28
|
+
def peek(self) -> CompletionEvent | None: ...
|
|
29
|
+
def wait(self) -> CompletionEvent: ...
|
|
30
|
+
def prep_nop(self, user_data: int) -> None: ...
|
|
31
|
+
def prep_timeout(self, user_data: int, sec: int, nsec: int) -> None: ...
|
|
32
|
+
def prep_close(
|
|
33
|
+
self,
|
|
34
|
+
user_data: int,
|
|
35
|
+
fd: int,
|
|
36
|
+
) -> None: ...
|
|
37
|
+
def prep_cancel(
|
|
38
|
+
self, user_data: int, target_user_data: int, flags: int = 0
|
|
39
|
+
) -> None: ...
|
|
40
|
+
def prep_read(
|
|
41
|
+
self, user_data: int, fd: int, buf: bytearray, nbytes: int, offset: int
|
|
42
|
+
) -> None: ...
|
|
43
|
+
def prep_write(self, user_data: int, fd: int, buf: bytes, offset: int) -> None: ...
|
|
44
|
+
def prep_openat(
|
|
45
|
+
self, user_data: int, path: str, flags: int, mode: int, dir_fd: int
|
|
46
|
+
) -> None: ...
|
|
47
|
+
def prep_statx(
|
|
48
|
+
self,
|
|
49
|
+
user_data: int,
|
|
50
|
+
path: str,
|
|
51
|
+
buf: StatxBuffer,
|
|
52
|
+
flags: int,
|
|
53
|
+
mask: int,
|
|
54
|
+
dir_fd: int,
|
|
55
|
+
) -> None: ...
|
|
56
|
+
def prep_socket(
|
|
57
|
+
self,
|
|
58
|
+
user_data: int,
|
|
59
|
+
domain: int,
|
|
60
|
+
sock_type: int,
|
|
61
|
+
protocol: int = 0,
|
|
62
|
+
flags: int = 0,
|
|
63
|
+
) -> None: ...
|
|
64
|
+
def prep_socket_setopt(
|
|
65
|
+
self,
|
|
66
|
+
user_data: int,
|
|
67
|
+
fd: int,
|
|
68
|
+
) -> None: ...
|
|
69
|
+
def prep_socket_bind(
|
|
70
|
+
self, user_data: int, fd: int, sock_addr: SockAddr
|
|
71
|
+
) -> None: ...
|
|
72
|
+
def prep_socket_listen(self, user_data: int, fd: int, backlog: int) -> None: ...
|
|
73
|
+
def prep_socket_accept(self, user_data: int, fd: int) -> None: ...
|
|
74
|
+
def prep_socket_recv(
|
|
75
|
+
self, user_data: int, fd: int, buf: bytearray, flags: int = 0
|
|
76
|
+
) -> None: ...
|
|
77
|
+
def prep_socket_send(
|
|
78
|
+
self, user_data: int, fd: int, buf: bytes, flags: int = 0
|
|
79
|
+
) -> None: ...
|
|
80
|
+
def prep_socket_connect(
|
|
81
|
+
self, user_data: int, fd: int, sock_addr: SockAddr
|
|
82
|
+
) -> None: ...
|
|
83
|
+
|
|
84
|
+
class StatxBuffer:
|
|
85
|
+
def __init__(self) -> None: ...
|
|
86
|
+
@property
|
|
87
|
+
def size(self) -> int: ...
|
|
88
|
+
@property
|
|
89
|
+
def mtime_sec(self) -> int: ...
|
|
90
|
+
@property
|
|
91
|
+
def ino(self) -> int: ...
|
|
92
|
+
@property
|
|
93
|
+
def mode(self) -> int: ...
|
|
94
|
+
|
|
95
|
+
# File open flags
|
|
96
|
+
O_RDONLY: int
|
|
97
|
+
O_WRONLY: int
|
|
98
|
+
O_RDWR: int
|
|
99
|
+
O_CREAT: int
|
|
100
|
+
O_TRUNC: int
|
|
101
|
+
O_APPEND: int
|
|
102
|
+
O_NONBLOCK: int
|
|
103
|
+
O_CLOEXEC: int
|
|
104
|
+
|
|
105
|
+
# File mode bits (permissions)
|
|
106
|
+
S_IRUSR: int
|
|
107
|
+
S_IWUSR: int
|
|
108
|
+
S_IXUSR: int
|
|
109
|
+
S_IRGRP: int
|
|
110
|
+
S_IWGRP: int
|
|
111
|
+
S_IXGRP: int
|
|
112
|
+
S_IROTH: int
|
|
113
|
+
S_IWOTH: int
|
|
114
|
+
S_IXOTH: int
|
|
115
|
+
|
|
116
|
+
# File type bits
|
|
117
|
+
S_IFREG: int
|
|
118
|
+
S_IFDIR: int
|
|
119
|
+
S_IFLNK: int
|
|
120
|
+
S_IFSOCK: int
|
|
121
|
+
S_IFIFO: int
|
|
122
|
+
S_IFMT: int
|
|
123
|
+
|
|
124
|
+
# AT flags (path resolution)
|
|
125
|
+
AT_FDCWD: int
|
|
126
|
+
AT_EMPTY_PATH: int
|
|
127
|
+
AT_SYMLINK_NOFOLLOW: int
|
|
128
|
+
|
|
129
|
+
# Statx mask
|
|
130
|
+
STATX_TYPE: int
|
|
131
|
+
STATX_MODE: int
|
|
132
|
+
STATX_INO: int
|
|
133
|
+
STATX_SIZE: int
|
|
134
|
+
STATX_MTIME: int
|
|
135
|
+
STATX_ATIME: int
|
|
136
|
+
STATX_CTIME: int
|
|
137
|
+
STATX_ALL: int
|
|
138
|
+
|
|
139
|
+
# Socket: address families
|
|
140
|
+
AF_INET: int
|
|
141
|
+
AF_INET6: int
|
|
142
|
+
AF_UNIX: int
|
|
143
|
+
|
|
144
|
+
# Socket: types
|
|
145
|
+
SOCK_STREAM: int
|
|
146
|
+
SOCK_DGRAM: int
|
|
147
|
+
SOCK_NONBLOCK: int
|
|
148
|
+
SOCK_CLOEXEC: int
|
|
149
|
+
|
|
150
|
+
# Socket: options
|
|
151
|
+
SOL_SOCKET: int
|
|
152
|
+
SO_REUSEADDR: int
|
|
153
|
+
SO_REUSEPORT: int
|
|
154
|
+
SO_KEEPALIVE: int
|
|
155
|
+
IPPROTO_TCP: int
|
|
156
|
+
TCP_NODELAY: int
|
|
157
|
+
|
|
158
|
+
# Socket: send/recv flags
|
|
159
|
+
MSG_NOSIGNAL: int
|
|
160
|
+
MSG_DONTWAIT: int
|
|
161
|
+
|
|
162
|
+
# Signals
|
|
163
|
+
SIGINT: int
|
|
164
|
+
SIGTERM: int
|
|
165
|
+
SIGHUP: int
|
|
166
|
+
|
|
167
|
+
# Signalfd flags
|
|
168
|
+
SFD_NONBLOCK: int
|
|
169
|
+
SFD_CLOEXEC: int
|
|
File without changes
|
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
use io_uring::{IoUring, opcode, types};
|
|
2
|
+
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
|
3
|
+
use pyo3::prelude::*;
|
|
4
|
+
use pyo3::types::{PyByteArray, PyBytes};
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::ffi::CString;
|
|
7
|
+
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
8
|
+
use std::os::unix::io::RawFd;
|
|
9
|
+
|
|
10
|
+
/// A completed io_uring operation.
|
|
11
|
+
#[pyclass(frozen)]
|
|
12
|
+
#[derive(Clone, Debug)]
|
|
13
|
+
struct CompletionEvent {
|
|
14
|
+
#[pyo3(get)]
|
|
15
|
+
user_data: u64,
|
|
16
|
+
#[pyo3(get)]
|
|
17
|
+
res: i32,
|
|
18
|
+
#[pyo3(get)]
|
|
19
|
+
flags: u32,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[pymethods]
|
|
23
|
+
impl CompletionEvent {
|
|
24
|
+
fn __repr__(&self) -> String {
|
|
25
|
+
format!(
|
|
26
|
+
"CompletionEvent(user_data={}, res={}, flags={})",
|
|
27
|
+
self.user_data, self.res, self.flags
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[allow(dead_code)]
|
|
33
|
+
struct StatxRequest {
|
|
34
|
+
path: CString,
|
|
35
|
+
statxbuf: Py<StatxBuffer>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Owns an io_uring instance and exposes prep/submit/complete operations.
|
|
39
|
+
///
|
|
40
|
+
/// Usage from Python:
|
|
41
|
+
/// ```python
|
|
42
|
+
/// with Ring(depth=32) as ring:
|
|
43
|
+
/// ring.prep_nop(user_data=1)
|
|
44
|
+
/// ring.submit()
|
|
45
|
+
/// cqe = ring.wait()
|
|
46
|
+
/// ```
|
|
47
|
+
///
|
|
48
|
+
#[pyclass]
|
|
49
|
+
struct Ring {
|
|
50
|
+
ring: Option<IoUring>,
|
|
51
|
+
depth: u32,
|
|
52
|
+
|
|
53
|
+
/// Buffers that are currently owned by the kernel (between submit and CQE).
|
|
54
|
+
/// Keyed by `user_data` so they can be released when the CQE arrives.
|
|
55
|
+
///
|
|
56
|
+
/// The kernel holds raw pointers into these buffers. They must be kept
|
|
57
|
+
/// alive and un-resized until the corresponding CQE is consumed.
|
|
58
|
+
/// TODO: Consolidate into 1.
|
|
59
|
+
pinned_mutable_buffers: HashMap<u64, Py<PyByteArray>>,
|
|
60
|
+
|
|
61
|
+
pinned_immutable_buffers: HashMap<u64, Py<PyBytes>>,
|
|
62
|
+
|
|
63
|
+
/// CStrings for paths passed to openat.
|
|
64
|
+
pinned_paths: HashMap<u64, CString>,
|
|
65
|
+
|
|
66
|
+
/// Timespecs for timeouts.
|
|
67
|
+
pinned_timespecs: HashMap<u64, types::Timespec>,
|
|
68
|
+
|
|
69
|
+
/// Addresses for sockets.
|
|
70
|
+
pinned_sockaddr: HashMap<u64, SockAddrInner>,
|
|
71
|
+
|
|
72
|
+
/// Socket option values. Boxed for pointer stability across HashMap resizes.
|
|
73
|
+
pinned_sockopts: HashMap<u64, Box<i32>>,
|
|
74
|
+
|
|
75
|
+
// Statx buffers
|
|
76
|
+
pinned_statx_buffers: HashMap<u64, StatxRequest>,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl Ring {
|
|
80
|
+
fn uring_mut(&mut self) -> PyResult<&mut IoUring> {
|
|
81
|
+
self.ring
|
|
82
|
+
.as_mut()
|
|
83
|
+
.ok_or_else(|| PyRuntimeError::new_err("Ring not initialised (use as context manager)"))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Push an entry onto the SQ. Panics if SQ is full.
|
|
87
|
+
fn push_entry(&mut self, entry: io_uring::squeue::Entry) -> PyResult<()> {
|
|
88
|
+
let ring = self.uring_mut()?;
|
|
89
|
+
// SAFETY: we trust that the caller has set up the entry correctly and
|
|
90
|
+
// that any buffers referenced are pinned in `pinned_buffers`.
|
|
91
|
+
unsafe {
|
|
92
|
+
ring.submission()
|
|
93
|
+
.push(&entry)
|
|
94
|
+
.map_err(|_| PyRuntimeError::new_err("Submission queue is full"))?;
|
|
95
|
+
}
|
|
96
|
+
Ok(())
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Release any pinned resources associated with a completed user_data.
|
|
100
|
+
fn release_pinned(&mut self, user_data: u64) {
|
|
101
|
+
self.pinned_mutable_buffers.remove(&user_data);
|
|
102
|
+
self.pinned_immutable_buffers.remove(&user_data);
|
|
103
|
+
self.pinned_paths.remove(&user_data);
|
|
104
|
+
self.pinned_sockaddr.remove(&user_data);
|
|
105
|
+
self.pinned_timespecs.remove(&user_data);
|
|
106
|
+
self.pinned_sockopts.remove(&user_data);
|
|
107
|
+
self.pinned_statx_buffers.remove(&user_data);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn cqe_to_event(&mut self, cqe: &io_uring::cqueue::Entry) -> CompletionEvent {
|
|
111
|
+
let user_data = cqe.user_data();
|
|
112
|
+
self.release_pinned(user_data);
|
|
113
|
+
CompletionEvent {
|
|
114
|
+
user_data,
|
|
115
|
+
res: cqe.result(),
|
|
116
|
+
flags: cqe.flags(),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[pymethods]
|
|
122
|
+
impl Ring {
|
|
123
|
+
#[new]
|
|
124
|
+
#[pyo3(signature = (depth = 32))]
|
|
125
|
+
fn new(depth: u32) -> Self {
|
|
126
|
+
Ring {
|
|
127
|
+
ring: None,
|
|
128
|
+
depth,
|
|
129
|
+
pinned_mutable_buffers: HashMap::new(),
|
|
130
|
+
pinned_immutable_buffers: HashMap::new(),
|
|
131
|
+
pinned_paths: HashMap::new(),
|
|
132
|
+
pinned_timespecs: HashMap::new(),
|
|
133
|
+
pinned_sockaddr: HashMap::new(),
|
|
134
|
+
pinned_sockopts: HashMap::new(),
|
|
135
|
+
pinned_statx_buffers: HashMap::new(),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Python CM protocol.
|
|
140
|
+
fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyResult<PyRefMut<'_, Self>> {
|
|
141
|
+
let ring = IoUring::new(slf.depth)
|
|
142
|
+
.map_err(|e| PyRuntimeError::new_err(format!("io_uring_setup failed: {e}")))?;
|
|
143
|
+
slf.ring = Some(ring);
|
|
144
|
+
Ok(slf)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[pyo3(signature = (_exc_type=None, _exc_val=None, _exc_tb=None))]
|
|
148
|
+
fn __exit__(
|
|
149
|
+
&mut self,
|
|
150
|
+
_exc_type: Option<&Bound<'_, PyAny>>,
|
|
151
|
+
_exc_val: Option<&Bound<'_, PyAny>>,
|
|
152
|
+
_exc_tb: Option<&Bound<'_, PyAny>>,
|
|
153
|
+
) -> PyResult<bool> {
|
|
154
|
+
self.pinned_mutable_buffers.clear();
|
|
155
|
+
self.pinned_immutable_buffers.clear();
|
|
156
|
+
self.pinned_paths.clear();
|
|
157
|
+
self.pinned_sockaddr.clear();
|
|
158
|
+
self.pinned_timespecs.clear();
|
|
159
|
+
self.pinned_sockopts.clear();
|
|
160
|
+
self.pinned_statx_buffers.clear();
|
|
161
|
+
self.ring = None; // Drop triggers internal io_uring cleanup
|
|
162
|
+
Ok(false)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Submit all queued SQEs to the kernel. Returns number submitted.
|
|
166
|
+
fn submit(&mut self) -> PyResult<u32> {
|
|
167
|
+
let n = self
|
|
168
|
+
.uring_mut()?
|
|
169
|
+
.submit()
|
|
170
|
+
.map_err(|e| PyRuntimeError::new_err(format!("io_uring_submit failed: {e}")))?;
|
|
171
|
+
Ok(n as u32)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Non-blocking peek.
|
|
175
|
+
fn peek(&mut self) -> PyResult<Option<CompletionEvent>> {
|
|
176
|
+
let ring = self.uring_mut()?;
|
|
177
|
+
let cq = ring.completion();
|
|
178
|
+
let cqe = cq.into_iter().next();
|
|
179
|
+
match cqe {
|
|
180
|
+
Some(cqe) => Ok(Some(self.cqe_to_event(&cqe))),
|
|
181
|
+
None => Ok(None),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Blocking wait for at least one CQE and return it.
|
|
186
|
+
fn wait(&mut self, py: Python<'_>) -> PyResult<CompletionEvent> {
|
|
187
|
+
let ring = self.uring_mut()?;
|
|
188
|
+
py.detach(|| ring.submit_and_wait(1))
|
|
189
|
+
.map_err(|e| PyRuntimeError::new_err(format!("io_uring_wait failed: {e}")))?;
|
|
190
|
+
let cqe = ring
|
|
191
|
+
.completion()
|
|
192
|
+
.next()
|
|
193
|
+
.ok_or_else(|| PyRuntimeError::new_err("No CQE after wait"))?;
|
|
194
|
+
Ok(self.cqe_to_event(&cqe))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Submit a no-op.
|
|
198
|
+
fn prep_nop(&mut self, user_data: u64) -> PyResult<()> {
|
|
199
|
+
let entry = opcode::Nop::new().build().user_data(user_data);
|
|
200
|
+
self.push_entry(entry)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Submit a timeout (sleep).
|
|
204
|
+
fn prep_timeout(&mut self, user_data: u64, sec: u64, nsec: u32) -> PyResult<()> {
|
|
205
|
+
let timespec = types::Timespec::new().sec(sec).nsec(nsec);
|
|
206
|
+
self.pinned_timespecs.insert(user_data, timespec);
|
|
207
|
+
let ts = self.pinned_timespecs.get(&user_data).unwrap();
|
|
208
|
+
|
|
209
|
+
let entry = opcode::Timeout::new(ts).build().user_data(user_data);
|
|
210
|
+
|
|
211
|
+
self.push_entry(entry)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Prep a read into `buf`.
|
|
215
|
+
/// The `buf` (a Python `bytearray`) is pinned until the CQE is consumed.
|
|
216
|
+
/// **Do not resize `buf` between prep and consuming the CQE.**
|
|
217
|
+
#[pyo3(signature = (user_data, fd, buf, nbytes, offset))]
|
|
218
|
+
fn prep_read(
|
|
219
|
+
&mut self,
|
|
220
|
+
_py: Python<'_>,
|
|
221
|
+
user_data: u64,
|
|
222
|
+
fd: RawFd,
|
|
223
|
+
buf: Bound<'_, PyByteArray>,
|
|
224
|
+
nbytes: u32,
|
|
225
|
+
offset: u64,
|
|
226
|
+
) -> PyResult<()> {
|
|
227
|
+
let ptr = buf.data();
|
|
228
|
+
let len = nbytes.min(buf.len() as u32);
|
|
229
|
+
|
|
230
|
+
let entry = opcode::Read::new(types::Fd(fd), ptr.cast(), len)
|
|
231
|
+
.offset(offset)
|
|
232
|
+
.build()
|
|
233
|
+
.user_data(user_data);
|
|
234
|
+
|
|
235
|
+
self.pinned_mutable_buffers.insert(user_data, buf.unbind());
|
|
236
|
+
self.push_entry(entry)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Prepares statx for metadata extraction.
|
|
240
|
+
fn prep_statx(
|
|
241
|
+
&mut self,
|
|
242
|
+
user_data: u64,
|
|
243
|
+
path: &str,
|
|
244
|
+
buf: Bound<'_, StatxBuffer>,
|
|
245
|
+
flags: i32,
|
|
246
|
+
mask: u32,
|
|
247
|
+
dir_fd: RawFd,
|
|
248
|
+
) -> PyResult<()> {
|
|
249
|
+
let c_path =
|
|
250
|
+
CString::new(path).map_err(|_| PyRuntimeError::new_err("Path contains null byte"))?;
|
|
251
|
+
let path_ptr = c_path.as_ptr();
|
|
252
|
+
|
|
253
|
+
let mut guard = buf.borrow_mut();
|
|
254
|
+
let statxbuf_ptr = &mut *guard.inner as *mut libc::statx as *mut io_uring::types::statx;
|
|
255
|
+
|
|
256
|
+
let entry = opcode::Statx::new(types::Fd(dir_fd), path_ptr, statxbuf_ptr)
|
|
257
|
+
.flags(flags)
|
|
258
|
+
.mask(mask)
|
|
259
|
+
.build()
|
|
260
|
+
.user_data(user_data);
|
|
261
|
+
|
|
262
|
+
drop(guard);
|
|
263
|
+
|
|
264
|
+
self.pinned_statx_buffers.insert(
|
|
265
|
+
user_data,
|
|
266
|
+
StatxRequest {
|
|
267
|
+
path: c_path,
|
|
268
|
+
statxbuf: buf.unbind(),
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
self.push_entry(entry)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Prep a file write.
|
|
275
|
+
#[pyo3(signature = (user_data, fd, buf, offset))]
|
|
276
|
+
fn prep_write(
|
|
277
|
+
&mut self,
|
|
278
|
+
_py: Python<'_>,
|
|
279
|
+
user_data: u64,
|
|
280
|
+
fd: RawFd,
|
|
281
|
+
buf: Bound<'_, PyBytes>,
|
|
282
|
+
offset: u64,
|
|
283
|
+
) -> PyResult<()> {
|
|
284
|
+
let data = buf.as_bytes();
|
|
285
|
+
let ptr = data.as_ptr();
|
|
286
|
+
let len = data.len() as u32;
|
|
287
|
+
|
|
288
|
+
let entry = opcode::Write::new(types::Fd(fd), ptr.cast(), len)
|
|
289
|
+
.offset(offset)
|
|
290
|
+
.build()
|
|
291
|
+
.user_data(user_data);
|
|
292
|
+
|
|
293
|
+
self.pinned_immutable_buffers
|
|
294
|
+
.insert(user_data, buf.unbind());
|
|
295
|
+
self.push_entry(entry)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Prep a file open.
|
|
299
|
+
#[pyo3(signature = (user_data, path, flags, mode, dir_fd))]
|
|
300
|
+
fn prep_openat(
|
|
301
|
+
&mut self,
|
|
302
|
+
user_data: u64,
|
|
303
|
+
path: &str,
|
|
304
|
+
flags: i32,
|
|
305
|
+
mode: u32,
|
|
306
|
+
dir_fd: RawFd,
|
|
307
|
+
) -> PyResult<()> {
|
|
308
|
+
let c_path =
|
|
309
|
+
CString::new(path).map_err(|_| PyRuntimeError::new_err("Path contains null byte"))?;
|
|
310
|
+
let ptr = c_path.as_ptr();
|
|
311
|
+
|
|
312
|
+
let entry = opcode::OpenAt::new(types::Fd(dir_fd), ptr)
|
|
313
|
+
.flags(flags)
|
|
314
|
+
.mode(mode)
|
|
315
|
+
.build()
|
|
316
|
+
.user_data(user_data);
|
|
317
|
+
|
|
318
|
+
// Pin the CString so the pointer stays valid until CQE
|
|
319
|
+
self.pinned_paths.insert(user_data, c_path);
|
|
320
|
+
self.push_entry(entry)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Prep a file/socket close.
|
|
324
|
+
fn prep_close(&mut self, user_data: u64, fd: RawFd) -> PyResult<()> {
|
|
325
|
+
let entry = opcode::Close::new(types::Fd(fd))
|
|
326
|
+
.build()
|
|
327
|
+
.user_data(user_data);
|
|
328
|
+
self.push_entry(entry)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Prep a cancellation of another in-flight operation.
|
|
332
|
+
#[pyo3(signature = (user_data, target_user_data, flags = 0))]
|
|
333
|
+
fn prep_cancel(&mut self, user_data: u64, target_user_data: u64, flags: i32) -> PyResult<()> {
|
|
334
|
+
let entry = opcode::AsyncCancel::new(target_user_data)
|
|
335
|
+
.build()
|
|
336
|
+
.user_data(user_data);
|
|
337
|
+
// TODO: use flags once io-uring crate exposes cancel flags
|
|
338
|
+
let _ = flags;
|
|
339
|
+
self.push_entry(entry)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// Prep a socket creation.
|
|
343
|
+
#[pyo3(signature = (user_data, domain, sock_type, protocol = 0, flags = 0))]
|
|
344
|
+
fn prep_socket(
|
|
345
|
+
&mut self,
|
|
346
|
+
user_data: u64,
|
|
347
|
+
domain: i32,
|
|
348
|
+
sock_type: i32,
|
|
349
|
+
protocol: i32,
|
|
350
|
+
flags: u32,
|
|
351
|
+
) -> PyResult<()> {
|
|
352
|
+
let entry = opcode::Socket::new(domain, sock_type, protocol)
|
|
353
|
+
.build()
|
|
354
|
+
.user_data(user_data);
|
|
355
|
+
let _ = flags; // TODO: pass flags if opcode supports it
|
|
356
|
+
self.push_entry(entry)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// Prep a recv from a connected socket into `buf`.
|
|
360
|
+
#[pyo3(signature = (user_data, fd, buf, flags = 0))]
|
|
361
|
+
fn prep_socket_recv(
|
|
362
|
+
&mut self,
|
|
363
|
+
_py: Python<'_>,
|
|
364
|
+
user_data: u64,
|
|
365
|
+
fd: RawFd,
|
|
366
|
+
buf: Bound<'_, PyByteArray>,
|
|
367
|
+
flags: u32,
|
|
368
|
+
) -> PyResult<()> {
|
|
369
|
+
let ptr = buf.data();
|
|
370
|
+
let len = buf.len() as u32;
|
|
371
|
+
|
|
372
|
+
let entry = opcode::Recv::new(types::Fd(fd), ptr.cast(), len)
|
|
373
|
+
.flags(flags as i32)
|
|
374
|
+
.build()
|
|
375
|
+
.user_data(user_data);
|
|
376
|
+
|
|
377
|
+
self.pinned_mutable_buffers.insert(user_data, buf.unbind());
|
|
378
|
+
self.push_entry(entry)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Prep a send to a connected socket.
|
|
382
|
+
#[pyo3(signature = (user_data, fd, buf, flags = 0))]
|
|
383
|
+
fn prep_socket_send(
|
|
384
|
+
&mut self,
|
|
385
|
+
_py: Python<'_>,
|
|
386
|
+
user_data: u64,
|
|
387
|
+
fd: RawFd,
|
|
388
|
+
buf: Bound<'_, PyBytes>,
|
|
389
|
+
flags: u32,
|
|
390
|
+
) -> PyResult<()> {
|
|
391
|
+
let data = buf.as_bytes();
|
|
392
|
+
let ptr = data.as_ptr();
|
|
393
|
+
let len = data.len() as u32;
|
|
394
|
+
|
|
395
|
+
let entry = opcode::Send::new(types::Fd(fd), ptr.cast(), len)
|
|
396
|
+
.flags(flags as i32)
|
|
397
|
+
.build()
|
|
398
|
+
.user_data(user_data);
|
|
399
|
+
|
|
400
|
+
self.pinned_immutable_buffers
|
|
401
|
+
.insert(user_data, buf.unbind());
|
|
402
|
+
self.push_entry(entry)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// Preps to bind to a socket.
|
|
406
|
+
fn prep_socket_bind(
|
|
407
|
+
&mut self,
|
|
408
|
+
_py: Python<'_>,
|
|
409
|
+
user_data: u64,
|
|
410
|
+
fd: RawFd,
|
|
411
|
+
sock_addr: SockAddr,
|
|
412
|
+
) -> PyResult<()> {
|
|
413
|
+
self.pinned_sockaddr.insert(user_data, sock_addr.inner);
|
|
414
|
+
|
|
415
|
+
let stored = self.pinned_sockaddr.get(&user_data).unwrap();
|
|
416
|
+
let (ptr, len) = stored.as_ptr_and_len();
|
|
417
|
+
|
|
418
|
+
let entry = opcode::Bind::new(types::Fd(fd), ptr, len)
|
|
419
|
+
.build()
|
|
420
|
+
.user_data(user_data);
|
|
421
|
+
|
|
422
|
+
self.push_entry(entry)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// Prepares a socket to listen. Marks it as passive.
|
|
426
|
+
fn prep_socket_listen(
|
|
427
|
+
&mut self,
|
|
428
|
+
_py: Python<'_>,
|
|
429
|
+
user_data: u64,
|
|
430
|
+
fd: RawFd,
|
|
431
|
+
backlog: i32,
|
|
432
|
+
) -> PyResult<()> {
|
|
433
|
+
let entry = opcode::Listen::new(types::Fd(fd), backlog)
|
|
434
|
+
.build()
|
|
435
|
+
.user_data(user_data);
|
|
436
|
+
|
|
437
|
+
self.push_entry(entry)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/// Prepares a socket to accept an incoming connection.
|
|
441
|
+
/// TODO: Add sockaddr for kernel to fill, for logging who connected.
|
|
442
|
+
fn prep_socket_accept(&mut self, _py: Python<'_>, user_data: u64, fd: RawFd) -> PyResult<()> {
|
|
443
|
+
let entry = opcode::Accept::new(types::Fd(fd), std::ptr::null_mut(), std::ptr::null_mut())
|
|
444
|
+
.build()
|
|
445
|
+
.user_data(user_data);
|
|
446
|
+
|
|
447
|
+
self.push_entry(entry)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/// Connects to a socket from a client.
|
|
451
|
+
fn prep_socket_connect(
|
|
452
|
+
&mut self,
|
|
453
|
+
_py: Python<'_>,
|
|
454
|
+
user_data: u64,
|
|
455
|
+
fd: RawFd,
|
|
456
|
+
sock_addr: SockAddr,
|
|
457
|
+
) -> PyResult<()> {
|
|
458
|
+
self.pinned_sockaddr.insert(user_data, sock_addr.inner);
|
|
459
|
+
|
|
460
|
+
let stored = self.pinned_sockaddr.get(&user_data).unwrap();
|
|
461
|
+
let (ptr, len) = stored.as_ptr_and_len();
|
|
462
|
+
|
|
463
|
+
let entry = opcode::Connect::new(types::Fd(fd), ptr, len)
|
|
464
|
+
.build()
|
|
465
|
+
.user_data(user_data);
|
|
466
|
+
|
|
467
|
+
self.push_entry(entry)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// Set socket options.
|
|
471
|
+
fn prep_socket_setopt(&mut self, user_data: u64, fd: RawFd) -> PyResult<()> {
|
|
472
|
+
// TODO: Hardcoded for now.
|
|
473
|
+
let optval = Box::new(1i32); // SO_REUSEADDR value
|
|
474
|
+
self.pinned_sockopts.insert(user_data, optval);
|
|
475
|
+
let pinned = self.pinned_sockopts.get(&user_data).unwrap();
|
|
476
|
+
|
|
477
|
+
let entry = opcode::SetSockOpt::new(
|
|
478
|
+
types::Fd(fd),
|
|
479
|
+
libc::SOL_SOCKET as u32,
|
|
480
|
+
libc::SO_REUSEADDR as u32,
|
|
481
|
+
pinned.as_ref() as *const i32 as *const libc::c_void,
|
|
482
|
+
std::mem::size_of::<i32>() as u32,
|
|
483
|
+
)
|
|
484
|
+
.build()
|
|
485
|
+
.user_data(user_data);
|
|
486
|
+
|
|
487
|
+
self.push_entry(entry)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//TODO: Move to another module.
|
|
492
|
+
#[derive(Clone)]
|
|
493
|
+
enum SockAddrInner {
|
|
494
|
+
V4(libc::sockaddr_in),
|
|
495
|
+
V6(libc::sockaddr_in6),
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
impl SockAddrInner {
|
|
499
|
+
/// Returns address as pointer as well as its length.
|
|
500
|
+
fn as_ptr_and_len(&self) -> (*const libc::sockaddr, u32) {
|
|
501
|
+
match &self {
|
|
502
|
+
SockAddrInner::V4(addr) => (
|
|
503
|
+
addr as *const libc::sockaddr_in as *const libc::sockaddr,
|
|
504
|
+
std::mem::size_of::<libc::sockaddr_in>() as u32,
|
|
505
|
+
),
|
|
506
|
+
SockAddrInner::V6(addr) => (
|
|
507
|
+
addr as *const libc::sockaddr_in6 as *const libc::sockaddr,
|
|
508
|
+
std::mem::size_of::<libc::sockaddr_in6>() as u32,
|
|
509
|
+
),
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[pyclass]
|
|
515
|
+
#[derive(Clone)]
|
|
516
|
+
struct SockAddr {
|
|
517
|
+
inner: SockAddrInner,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
#[pymethods]
|
|
521
|
+
impl SockAddr {
|
|
522
|
+
#[staticmethod]
|
|
523
|
+
fn v4(ip: &str, port: u16) -> PyResult<Self> {
|
|
524
|
+
let addr_parsed: Ipv4Addr = ip
|
|
525
|
+
.parse()
|
|
526
|
+
.map_err(|e| PyValueError::new_err(format!("Invalid IPv4 address: {e}")))?;
|
|
527
|
+
|
|
528
|
+
let mut addr: libc::sockaddr_in = unsafe { std::mem::zeroed() };
|
|
529
|
+
addr.sin_family = libc::AF_INET as u16;
|
|
530
|
+
addr.sin_addr.s_addr = u32::from_ne_bytes(addr_parsed.octets());
|
|
531
|
+
addr.sin_port = port.to_be();
|
|
532
|
+
Ok(SockAddr {
|
|
533
|
+
inner: SockAddrInner::V4(addr),
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#[staticmethod]
|
|
538
|
+
fn v6(ip: &str, port: u16) -> PyResult<Self> {
|
|
539
|
+
let addr_parsed: Ipv6Addr = ip
|
|
540
|
+
.parse()
|
|
541
|
+
.map_err(|e| PyValueError::new_err(format!("Invalid IPv6 address: {e}")))?;
|
|
542
|
+
|
|
543
|
+
let mut addr: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
|
|
544
|
+
addr.sin6_family = libc::AF_INET6 as u16;
|
|
545
|
+
addr.sin6_addr.s6_addr = addr_parsed.octets();
|
|
546
|
+
addr.sin6_port = port.to_be();
|
|
547
|
+
Ok(SockAddr {
|
|
548
|
+
inner: SockAddrInner::V6(addr),
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[pyclass]
|
|
554
|
+
#[derive(Clone, Debug)]
|
|
555
|
+
struct StatxBuffer {
|
|
556
|
+
inner: Box<libc::statx>,
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
#[pymethods]
|
|
560
|
+
impl StatxBuffer {
|
|
561
|
+
#[new]
|
|
562
|
+
fn new() -> Self {
|
|
563
|
+
StatxBuffer {
|
|
564
|
+
inner: Box::new(unsafe { std::mem::zeroed() }),
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#[getter]
|
|
569
|
+
fn size(&self) -> u64 {
|
|
570
|
+
self.inner.stx_size
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#[getter]
|
|
574
|
+
fn mtime_sec(&self) -> i64 {
|
|
575
|
+
self.inner.stx_mtime.tv_sec
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#[getter]
|
|
579
|
+
fn ino(&self) -> u64 {
|
|
580
|
+
self.inner.stx_ino
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
#[getter]
|
|
584
|
+
fn mode(&self) -> u32 {
|
|
585
|
+
self.inner.stx_mode as u32
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
fn register_constants(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
590
|
+
// File open flags
|
|
591
|
+
m.add("O_RDONLY", libc::O_RDONLY)?;
|
|
592
|
+
m.add("O_WRONLY", libc::O_WRONLY)?;
|
|
593
|
+
m.add("O_RDWR", libc::O_RDWR)?;
|
|
594
|
+
m.add("O_CREAT", libc::O_CREAT)?;
|
|
595
|
+
m.add("O_TRUNC", libc::O_TRUNC)?;
|
|
596
|
+
m.add("O_APPEND", libc::O_APPEND)?;
|
|
597
|
+
m.add("O_NONBLOCK", libc::O_NONBLOCK)?;
|
|
598
|
+
m.add("O_CLOEXEC", libc::O_CLOEXEC)?;
|
|
599
|
+
|
|
600
|
+
// File mode bits (permissions)
|
|
601
|
+
m.add("S_IRUSR", libc::S_IRUSR)?;
|
|
602
|
+
m.add("S_IWUSR", libc::S_IWUSR)?;
|
|
603
|
+
m.add("S_IXUSR", libc::S_IXUSR)?;
|
|
604
|
+
m.add("S_IRGRP", libc::S_IRGRP)?;
|
|
605
|
+
m.add("S_IWGRP", libc::S_IWGRP)?;
|
|
606
|
+
m.add("S_IXGRP", libc::S_IXGRP)?;
|
|
607
|
+
m.add("S_IROTH", libc::S_IROTH)?;
|
|
608
|
+
m.add("S_IWOTH", libc::S_IWOTH)?;
|
|
609
|
+
m.add("S_IXOTH", libc::S_IXOTH)?;
|
|
610
|
+
|
|
611
|
+
// File type bits (from st_mode / stx_mode)
|
|
612
|
+
m.add("S_IFREG", libc::S_IFREG)?;
|
|
613
|
+
m.add("S_IFDIR", libc::S_IFDIR)?;
|
|
614
|
+
m.add("S_IFLNK", libc::S_IFLNK)?;
|
|
615
|
+
m.add("S_IFSOCK", libc::S_IFSOCK)?;
|
|
616
|
+
m.add("S_IFIFO", libc::S_IFIFO)?;
|
|
617
|
+
m.add("S_IFMT", libc::S_IFMT)?;
|
|
618
|
+
|
|
619
|
+
// AT flags (path resolution)
|
|
620
|
+
m.add("AT_FDCWD", libc::AT_FDCWD)?;
|
|
621
|
+
m.add("AT_EMPTY_PATH", libc::AT_EMPTY_PATH)?;
|
|
622
|
+
m.add("AT_SYMLINK_NOFOLLOW", libc::AT_SYMLINK_NOFOLLOW)?;
|
|
623
|
+
|
|
624
|
+
// Statx mask (which fields to populate)
|
|
625
|
+
m.add("STATX_TYPE", libc::STATX_TYPE)?;
|
|
626
|
+
m.add("STATX_MODE", libc::STATX_MODE)?;
|
|
627
|
+
m.add("STATX_INO", libc::STATX_INO)?;
|
|
628
|
+
m.add("STATX_SIZE", libc::STATX_SIZE)?;
|
|
629
|
+
m.add("STATX_MTIME", libc::STATX_MTIME)?;
|
|
630
|
+
m.add("STATX_ATIME", libc::STATX_ATIME)?;
|
|
631
|
+
m.add("STATX_CTIME", libc::STATX_CTIME)?;
|
|
632
|
+
m.add("STATX_ALL", libc::STATX_ALL)?;
|
|
633
|
+
|
|
634
|
+
// Socket: address families
|
|
635
|
+
m.add("AF_INET", libc::AF_INET)?;
|
|
636
|
+
m.add("AF_INET6", libc::AF_INET6)?;
|
|
637
|
+
m.add("AF_UNIX", libc::AF_UNIX)?;
|
|
638
|
+
|
|
639
|
+
// Socket: types
|
|
640
|
+
m.add("SOCK_STREAM", libc::SOCK_STREAM)?;
|
|
641
|
+
m.add("SOCK_DGRAM", libc::SOCK_DGRAM)?;
|
|
642
|
+
m.add("SOCK_NONBLOCK", libc::SOCK_NONBLOCK)?;
|
|
643
|
+
m.add("SOCK_CLOEXEC", libc::SOCK_CLOEXEC)?;
|
|
644
|
+
|
|
645
|
+
// Socket: protocol levels and options
|
|
646
|
+
m.add("SOL_SOCKET", libc::SOL_SOCKET)?;
|
|
647
|
+
m.add("SO_REUSEADDR", libc::SO_REUSEADDR)?;
|
|
648
|
+
m.add("SO_REUSEPORT", libc::SO_REUSEPORT)?;
|
|
649
|
+
m.add("SO_KEEPALIVE", libc::SO_KEEPALIVE)?;
|
|
650
|
+
m.add("IPPROTO_TCP", libc::IPPROTO_TCP)?;
|
|
651
|
+
m.add("TCP_NODELAY", libc::TCP_NODELAY)?;
|
|
652
|
+
|
|
653
|
+
// Socket: send/recv flags
|
|
654
|
+
m.add("MSG_NOSIGNAL", libc::MSG_NOSIGNAL)?;
|
|
655
|
+
m.add("MSG_DONTWAIT", libc::MSG_DONTWAIT)?;
|
|
656
|
+
|
|
657
|
+
// Signals
|
|
658
|
+
m.add("SIGINT", libc::SIGINT)?;
|
|
659
|
+
m.add("SIGTERM", libc::SIGTERM)?;
|
|
660
|
+
m.add("SIGHUP", libc::SIGHUP)?;
|
|
661
|
+
|
|
662
|
+
// Signalfd flags
|
|
663
|
+
m.add("SFD_NONBLOCK", libc::SFD_NONBLOCK)?;
|
|
664
|
+
m.add("SFD_CLOEXEC", libc::SFD_CLOEXEC)?;
|
|
665
|
+
|
|
666
|
+
Ok(())
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
#[pymodule]
|
|
670
|
+
fn _rusty_ring(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
671
|
+
m.add_class::<Ring>()?;
|
|
672
|
+
m.add_class::<CompletionEvent>()?;
|
|
673
|
+
m.add_class::<SockAddr>()?;
|
|
674
|
+
m.add_class::<StatxBuffer>()?;
|
|
675
|
+
|
|
676
|
+
register_constants(m)?;
|
|
677
|
+
Ok(())
|
|
678
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from one_ring_loop.log import get_logger
|
|
7
|
+
from rusty_ring import Ring
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
SERVER_MESSAGE = b"A new client connected!"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestRing:
|
|
18
|
+
def test_ring_context_manager(self) -> None:
|
|
19
|
+
with Ring(32) as ring:
|
|
20
|
+
assert isinstance(ring, Ring)
|
|
21
|
+
|
|
22
|
+
def test_file_open_write_read(self, tmp_file_path: Path) -> None:
|
|
23
|
+
file_content = b"Hello! :)"
|
|
24
|
+
|
|
25
|
+
with Ring(32) as ring:
|
|
26
|
+
ring.prep_openat(
|
|
27
|
+
user_data=0, path=str(tmp_file_path), flags=66, mode=432, dir_fd=-100
|
|
28
|
+
)
|
|
29
|
+
ring.submit()
|
|
30
|
+
open_event = ring.wait()
|
|
31
|
+
logger.info("Got open event", io_event=open_event)
|
|
32
|
+
fd = open_event.res
|
|
33
|
+
|
|
34
|
+
write_buf = bytes(file_content)
|
|
35
|
+
ring.prep_write(0, fd=fd, buf=write_buf, offset=0)
|
|
36
|
+
ring.submit()
|
|
37
|
+
write_event = ring.wait()
|
|
38
|
+
logger.info("Got write event", io_event=write_event)
|
|
39
|
+
|
|
40
|
+
read_buf = bytearray(9)
|
|
41
|
+
ring.prep_read(0, fd=fd, buf=read_buf, nbytes=9, offset=0)
|
|
42
|
+
ring.submit()
|
|
43
|
+
read_event = ring.wait()
|
|
44
|
+
logger.info("Got read event", io_event=read_event)
|
|
45
|
+
assert bytes(read_buf) == file_content
|
|
46
|
+
|
|
47
|
+
def test_timeout(self, timing) -> None:
|
|
48
|
+
with Ring(32) as ring:
|
|
49
|
+
sleep_for_sec = 1
|
|
50
|
+
sleep_for_nsec = int(5e8) # 0.5 seconds.
|
|
51
|
+
ring.prep_timeout(0, sleep_for_sec, sleep_for_nsec)
|
|
52
|
+
ring.submit()
|
|
53
|
+
|
|
54
|
+
timing.start()
|
|
55
|
+
ring.wait()
|
|
56
|
+
timing.assert_elapsed_between(1.5, 1.6, msg="Should sleep for 2 secounds")
|
|
57
|
+
|
|
58
|
+
def test_wait_does_not_hold_gil(self) -> None:
|
|
59
|
+
flag = threading.Event()
|
|
60
|
+
|
|
61
|
+
def background() -> None:
|
|
62
|
+
logger.info("Sleeping for 0.5 seconds")
|
|
63
|
+
time.sleep(0.5)
|
|
64
|
+
logger.info("Setting flag")
|
|
65
|
+
flag.set()
|
|
66
|
+
|
|
67
|
+
with Ring(32) as ring, ThreadPoolExecutor(max_workers=2) as executor:
|
|
68
|
+
ring.prep_timeout(0, sec=1, nsec=0)
|
|
69
|
+
ring.submit()
|
|
70
|
+
|
|
71
|
+
logger.info("Submitting background function")
|
|
72
|
+
executor.submit(background)
|
|
73
|
+
logger.info("Waiting on ring timeout")
|
|
74
|
+
ring.wait()
|
|
75
|
+
logger.info("Finished waiting")
|
|
76
|
+
|
|
77
|
+
assert flag.is_set()
|