rloop 0.1.3__tar.gz → 0.1.5__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 (36) hide show
  1. {rloop-0.1.3 → rloop-0.1.5}/Cargo.lock +28 -28
  2. {rloop-0.1.3 → rloop-0.1.5}/Cargo.toml +4 -4
  3. {rloop-0.1.3 → rloop-0.1.5}/PKG-INFO +2 -9
  4. {rloop-0.1.3 → rloop-0.1.5}/pyproject.toml +15 -6
  5. {rloop-0.1.3 → rloop-0.1.5}/rloop/__init__.py +1 -2
  6. rloop-0.1.5/rloop/_compat.py +11 -0
  7. {rloop-0.1.3 → rloop-0.1.5}/rloop/loop.py +1 -1
  8. {rloop-0.1.3 → rloop-0.1.5}/src/event_loop.rs +82 -113
  9. {rloop-0.1.3 → rloop-0.1.5}/src/handles.rs +2 -2
  10. {rloop-0.1.3 → rloop-0.1.5}/src/io.rs +1 -1
  11. {rloop-0.1.3 → rloop-0.1.5}/src/lib.rs +1 -1
  12. {rloop-0.1.3 → rloop-0.1.5}/src/tcp.rs +2 -2
  13. rloop-0.1.5/src/utils.rs +12 -0
  14. {rloop-0.1.3 → rloop-0.1.5}/tests/test_handles.py +39 -0
  15. rloop-0.1.3/rloop/_compat.py +0 -5
  16. rloop-0.1.3/src/utils.rs +0 -27
  17. {rloop-0.1.3 → rloop-0.1.5}/LICENSE +0 -0
  18. {rloop-0.1.3 → rloop-0.1.5}/README.md +0 -0
  19. {rloop-0.1.3 → rloop-0.1.5}/rloop/_rloop.pyi +0 -0
  20. {rloop-0.1.3 → rloop-0.1.5}/rloop/exc.py +0 -0
  21. {rloop-0.1.3 → rloop-0.1.5}/rloop/futures.py +0 -0
  22. {rloop-0.1.3 → rloop-0.1.5}/rloop/server.py +0 -0
  23. {rloop-0.1.3 → rloop-0.1.5}/rloop/subprocess.py +0 -0
  24. {rloop-0.1.3 → rloop-0.1.5}/rloop/transports.py +0 -0
  25. {rloop-0.1.3 → rloop-0.1.5}/rloop/utils.py +0 -0
  26. {rloop-0.1.3 → rloop-0.1.5}/src/log.rs +0 -0
  27. {rloop-0.1.3 → rloop-0.1.5}/src/py.rs +0 -0
  28. {rloop-0.1.3 → rloop-0.1.5}/src/server.rs +0 -0
  29. {rloop-0.1.3 → rloop-0.1.5}/src/sock.rs +0 -0
  30. {rloop-0.1.3 → rloop-0.1.5}/src/time.rs +0 -0
  31. {rloop-0.1.3 → rloop-0.1.5}/tests/conftest.py +0 -0
  32. {rloop-0.1.3 → rloop-0.1.5}/tests/tcp/__init__.py +0 -0
  33. {rloop-0.1.3 → rloop-0.1.5}/tests/tcp/test_tcp_conn.py +0 -0
  34. {rloop-0.1.3 → rloop-0.1.5}/tests/tcp/test_tcp_server.py +0 -0
  35. {rloop-0.1.3 → rloop-0.1.5}/tests/test_sockets.py +0 -0
  36. {rloop-0.1.3 → rloop-0.1.5}/tests/udp/test_udp.py +0 -0
@@ -10,15 +10,15 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
10
10
 
11
11
  [[package]]
12
12
  name = "autocfg"
13
- version = "1.4.0"
13
+ version = "1.5.0"
14
14
  source = "registry+https://github.com/rust-lang/crates.io-index"
15
- checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
15
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
16
16
 
17
17
  [[package]]
18
18
  name = "cc"
19
- version = "1.2.24"
19
+ version = "1.2.32"
20
20
  source = "registry+https://github.com/rust-lang/crates.io-index"
21
- checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
21
+ checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
22
22
  dependencies = [
23
23
  "shlex",
24
24
  ]
@@ -43,9 +43,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
43
43
 
44
44
  [[package]]
45
45
  name = "libc"
46
- version = "0.2.172"
46
+ version = "0.2.174"
47
47
  source = "registry+https://github.com/rust-lang/crates.io-index"
48
- checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
48
+ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
49
49
 
50
50
  [[package]]
51
51
  name = "log"
@@ -82,9 +82,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
82
82
 
83
83
  [[package]]
84
84
  name = "papaya"
85
- version = "0.2.1"
85
+ version = "0.2.3"
86
86
  source = "registry+https://github.com/rust-lang/crates.io-index"
87
- checksum = "6827e3fc394523c21d4464d02c0bb1c19966ea4a58a9844ad6d746214179d2bc"
87
+ checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f"
88
88
  dependencies = [
89
89
  "equivalent",
90
90
  "seize",
@@ -92,9 +92,9 @@ dependencies = [
92
92
 
93
93
  [[package]]
94
94
  name = "portable-atomic"
95
- version = "1.11.0"
95
+ version = "1.11.1"
96
96
  source = "registry+https://github.com/rust-lang/crates.io-index"
97
- checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
97
+ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
98
98
 
99
99
  [[package]]
100
100
  name = "proc-macro2"
@@ -107,9 +107,9 @@ dependencies = [
107
107
 
108
108
  [[package]]
109
109
  name = "pyo3"
110
- version = "0.25.0"
110
+ version = "0.25.1"
111
111
  source = "registry+https://github.com/rust-lang/crates.io-index"
112
- checksum = "f239d656363bcee73afef85277f1b281e8ac6212a1d42aa90e55b90ed43c47a4"
112
+ checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
113
113
  dependencies = [
114
114
  "anyhow",
115
115
  "indoc",
@@ -125,9 +125,9 @@ dependencies = [
125
125
 
126
126
  [[package]]
127
127
  name = "pyo3-build-config"
128
- version = "0.25.0"
128
+ version = "0.25.1"
129
129
  source = "registry+https://github.com/rust-lang/crates.io-index"
130
- checksum = "755ea671a1c34044fa165247aaf6f419ca39caa6003aee791a0df2713d8f1b6d"
130
+ checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
131
131
  dependencies = [
132
132
  "once_cell",
133
133
  "python3-dll-a",
@@ -136,9 +136,9 @@ dependencies = [
136
136
 
137
137
  [[package]]
138
138
  name = "pyo3-ffi"
139
- version = "0.25.0"
139
+ version = "0.25.1"
140
140
  source = "registry+https://github.com/rust-lang/crates.io-index"
141
- checksum = "fc95a2e67091e44791d4ea300ff744be5293f394f1bafd9f78c080814d35956e"
141
+ checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
142
142
  dependencies = [
143
143
  "libc",
144
144
  "pyo3-build-config",
@@ -146,9 +146,9 @@ dependencies = [
146
146
 
147
147
  [[package]]
148
148
  name = "pyo3-macros"
149
- version = "0.25.0"
149
+ version = "0.25.1"
150
150
  source = "registry+https://github.com/rust-lang/crates.io-index"
151
- checksum = "a179641d1b93920829a62f15e87c0ed791b6c8db2271ba0fd7c2686090510214"
151
+ checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
152
152
  dependencies = [
153
153
  "proc-macro2",
154
154
  "pyo3-macros-backend",
@@ -158,9 +158,9 @@ dependencies = [
158
158
 
159
159
  [[package]]
160
160
  name = "pyo3-macros-backend"
161
- version = "0.25.0"
161
+ version = "0.25.1"
162
162
  source = "registry+https://github.com/rust-lang/crates.io-index"
163
- checksum = "9dff85ebcaab8c441b0e3f7ae40a6963ecea8a9f5e74f647e33fcf5ec9a1e89e"
163
+ checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
164
164
  dependencies = [
165
165
  "heck",
166
166
  "proc-macro2",
@@ -189,7 +189,7 @@ dependencies = [
189
189
 
190
190
  [[package]]
191
191
  name = "rloop"
192
- version = "0.1.3"
192
+ version = "0.1.5"
193
193
  dependencies = [
194
194
  "anyhow",
195
195
  "libc",
@@ -218,19 +218,19 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
218
218
 
219
219
  [[package]]
220
220
  name = "socket2"
221
- version = "0.5.10"
221
+ version = "0.6.0"
222
222
  source = "registry+https://github.com/rust-lang/crates.io-index"
223
- checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
223
+ checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
224
224
  dependencies = [
225
225
  "libc",
226
- "windows-sys 0.52.0",
226
+ "windows-sys 0.59.0",
227
227
  ]
228
228
 
229
229
  [[package]]
230
230
  name = "syn"
231
- version = "2.0.101"
231
+ version = "2.0.104"
232
232
  source = "registry+https://github.com/rust-lang/crates.io-index"
233
- checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
233
+ checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
234
234
  dependencies = [
235
235
  "proc-macro2",
236
236
  "quote",
@@ -257,9 +257,9 @@ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
257
257
 
258
258
  [[package]]
259
259
  name = "wasi"
260
- version = "0.11.0+wasi-snapshot-preview1"
260
+ version = "0.11.1+wasi-snapshot-preview1"
261
261
  source = "registry+https://github.com/rust-lang/crates.io-index"
262
- checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
262
+ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
263
263
 
264
264
  [[package]]
265
265
  name = "windows-sys"
@@ -1,10 +1,10 @@
1
1
  [package]
2
2
  name = "rloop"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  description = "An asyncio event loop implemented in Rust"
5
5
  authors = ["Giovanni Barillari <g@baro.dev>"]
6
6
  license = "BSD-3-Clause"
7
- edition = "2021"
7
+ edition = "2024"
8
8
 
9
9
  keywords = ["asyncio"]
10
10
 
@@ -27,14 +27,14 @@ include = [
27
27
 
28
28
  [lib]
29
29
  name = "_rloop"
30
- crate-type = ["cdylib"]
30
+ crate-type = ["cdylib", "rlib"]
31
31
 
32
32
  [dependencies]
33
33
  anyhow = "=1.0"
34
34
  mio = { version = "=1.0", features = ["net", "os-ext", "os-poll"] }
35
35
  papaya = "=0.2"
36
36
  pyo3 = { version = "=0.25", features = ["anyhow", "extension-module", "generate-import-lib"] }
37
- socket2 = { version = "=0.5", features = ["all"] }
37
+ socket2 = { version = "=0.6", features = ["all"] }
38
38
 
39
39
  [target.'cfg(unix)'.dependencies]
40
40
  libc = "0.2.159"
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rloop
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: BSD License
7
7
  Classifier: Operating System :: MacOS
8
8
  Classifier: Operating System :: POSIX :: Linux
9
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: Programming Language :: Python :: 3.9
11
12
  Classifier: Programming Language :: Python :: 3.10
@@ -16,18 +17,10 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
16
17
  Classifier: Programming Language :: Python :: Implementation :: PyPy
17
18
  Classifier: Programming Language :: Python
18
19
  Classifier: Programming Language :: Rust
19
- Requires-Dist: ruff~=0.5.0 ; extra == 'lint'
20
- Requires-Dist: pytest~=7.4.2 ; extra == 'test'
21
- Requires-Dist: pytest-asyncio~=0.21.1 ; extra == 'test'
22
- Requires-Dist: rloop[lint,test] ; extra == 'dev'
23
- Provides-Extra: lint
24
- Provides-Extra: test
25
- Provides-Extra: dev
26
20
  License-File: LICENSE
27
21
  Summary: An asyncio event loop implemented in Rust
28
22
  Keywords: asyncio
29
23
  Home-Page: https://github.com/gi0baro/rloop
30
- Author: Giovanni Barillari <g@baro.dev>
31
24
  Author-email: Giovanni Barillari <g@baro.dev>
32
25
  License: BSD-3-Clause
33
26
  Requires-Python: >=3.9
@@ -9,6 +9,7 @@ classifiers = [
9
9
  'License :: OSI Approved :: BSD License',
10
10
  'Operating System :: MacOS',
11
11
  'Operating System :: POSIX :: Linux',
12
+ 'Programming Language :: Python :: Free Threading :: 2 - Beta',
12
13
  'Programming Language :: Python :: 3',
13
14
  'Programming Language :: Python :: 3.9',
14
15
  'Programming Language :: Python :: 3.10',
@@ -32,15 +33,23 @@ dynamic = [
32
33
  requires-python = '>=3.9'
33
34
  dependencies = []
34
35
 
35
- [project.optional-dependencies]
36
+ [dependency-groups]
37
+ build = [
38
+ 'maturin~=1.8',
39
+ ]
36
40
  lint = [
37
- 'ruff~=0.5.0',
41
+ 'ruff~=0.11',
38
42
  ]
39
43
  test = [
40
- 'pytest~=7.4.2',
41
- 'pytest-asyncio~=0.21.1',
44
+ 'pytest~=8.3',
45
+ 'pytest-asyncio~=0.26',
46
+ ]
47
+
48
+ all = [
49
+ { include-group = 'build' },
50
+ { include-group = 'lint' },
51
+ { include-group = 'test' },
42
52
  ]
43
- dev = ['rloop[lint,test]']
44
53
 
45
54
  [project.urls]
46
55
  Homepage = 'https://github.com/gi0baro/rloop'
@@ -48,7 +57,7 @@ Funding = 'https://github.com/sponsors/gi0baro'
48
57
  Source = 'https://github.com/gi0baro/rloop'
49
58
 
50
59
  [build-system]
51
- requires = ['maturin>=1.1.0,<2']
60
+ requires = ['maturin>=1.8.0,<2']
52
61
  build-backend = 'maturin'
53
62
 
54
63
  [tool.maturin]
@@ -1,5 +1,4 @@
1
- from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy
2
-
1
+ from ._compat import _BaseEventLoopPolicy as __BasePolicy
3
2
  from ._rloop import __version__ as __version__
4
3
  from .loop import RLoop
5
4
 
@@ -0,0 +1,11 @@
1
+ import sys
2
+
3
+
4
+ _PYV = int(sys.version_info.major * 100 + sys.version_info.minor)
5
+ _PY_311 = 311
6
+ _PY_314 = 314
7
+
8
+ if _PYV < 314:
9
+ from asyncio.events import BaseDefaultEventLoopPolicy as _BaseEventLoopPolicy
10
+ else:
11
+ from asyncio.events import _BaseDefaultEventLoopPolicy as _BaseEventLoopPolicy # noqa
@@ -391,7 +391,7 @@ class RLoop(__BaseLoop, __asyncio.AbstractEventLoop):
391
391
  exceptions = [exc for sub in exceptions for exc in sub]
392
392
  try:
393
393
  if all_errors:
394
- raise ExceptionGroup('create_connection failed', exceptions)
394
+ raise ExceptionGroup('create_connection failed', exceptions) # noqa: F821
395
395
  if len(exceptions) == 1:
396
396
  raise exceptions[0]
397
397
  else:
@@ -3,29 +3,25 @@ use std::{
3
3
  io::Read,
4
4
  mem,
5
5
  os::fd::{AsRawFd, FromRawFd},
6
- sync::{atomic, Mutex, RwLock},
6
+ sync::{Arc, Mutex, RwLock, atomic},
7
7
  time::{Duration, Instant},
8
8
  };
9
9
 
10
10
  use anyhow::Result;
11
- use mio::{event, net::TcpListener, Interest, Poll, Token};
11
+ use mio::{Interest, Poll, Token, Waker, event, net::TcpListener};
12
12
  use pyo3::prelude::*;
13
13
 
14
14
  use crate::{
15
15
  handles::{BoxedHandle, CBHandle, Handle, TimerHandle},
16
16
  io::Source,
17
- log::{log_exc_to_py_ctx, LogExc},
17
+ log::{LogExc, log_exc_to_py_ctx},
18
18
  py::{copy_context, weakset},
19
19
  server::Server,
20
20
  tcp::{TCPReadHandle, TCPServer, TCPServerRef, TCPTransport, TCPWriteHandle},
21
21
  time::Timer,
22
- utils::{py_allow_threads, syscall},
23
22
  };
24
23
 
25
- const WAKEB: &[u8; 1] = b"\0";
26
-
27
24
  enum IOHandle {
28
- Internal,
29
25
  Py(PyHandleData),
30
26
  Signals,
31
27
  TCPListener(TCPListenerHandleData),
@@ -43,11 +39,10 @@ struct TCPListenerHandleData {
43
39
  server: TCPServerRef,
44
40
  }
45
41
 
46
- pub(crate) struct EventLoopRunState {
42
+ pub struct EventLoopRunState {
47
43
  buf: Box<[u8]>,
48
44
  events: event::Events,
49
45
  pub read_buf: Box<[u8]>,
50
- sock: socket2::Socket,
51
46
  tick_last: u128,
52
47
  }
53
48
 
@@ -55,14 +50,13 @@ pub(crate) struct EventLoopRunState {
55
50
  pub struct EventLoop {
56
51
  idle: atomic::AtomicBool,
57
52
  io: Mutex<Poll>,
53
+ waker: Arc<Waker>,
58
54
  handles_io: papaya::HashMap<Token, IOHandle>,
59
55
  handles_ready: Mutex<VecDeque<BoxedHandle>>,
60
56
  handles_sched: Mutex<BinaryHeap<Timer>>,
61
57
  epoch: Instant,
62
58
  counter_ready: atomic::AtomicUsize,
63
59
  ssock: RwLock<Option<(socket2::Socket, socket2::Socket)>>,
64
- wsock: Mutex<Option<socket2::Socket>>,
65
- wsock_fd: atomic::AtomicI32,
66
60
  closed: atomic::AtomicBool,
67
61
  exc_handler: RwLock<PyObject>,
68
62
  exception_handler: RwLock<PyObject>,
@@ -88,46 +82,6 @@ pub struct EventLoop {
88
82
  }
89
83
 
90
84
  impl EventLoop {
91
- fn run_pre(&self) -> Result<EventLoopRunState> {
92
- // wake sockets
93
- let (sock_r, sock_w) = socket2::Socket::pair(socket2::Domain::UNIX, socket2::Type::STREAM, None)?;
94
- sock_r.set_nonblocking(true)?;
95
- sock_w.set_nonblocking(true)?;
96
- let token = Token(sock_r.as_raw_fd() as usize);
97
- let mut source = Source::FD(sock_r.as_raw_fd());
98
- {
99
- let guard = self.io.lock().unwrap();
100
- guard.registry().register(&mut source, token, Interest::READABLE)?;
101
- }
102
- self.handles_io.pin().insert(token, IOHandle::Internal);
103
- {
104
- let mut guard = self.wsock.lock().unwrap();
105
- self.wsock_fd.store(sock_w.as_raw_fd(), atomic::Ordering::Relaxed);
106
- *guard = Some(sock_w);
107
- }
108
-
109
- Ok(EventLoopRunState {
110
- buf: vec![0; 4096].into_boxed_slice(),
111
- events: event::Events::with_capacity(128),
112
- read_buf: vec![0; 262_144].into_boxed_slice(),
113
- tick_last: 0,
114
- sock: sock_r,
115
- })
116
- }
117
-
118
- fn run_post(&self, state: &mut EventLoopRunState) {
119
- // cleanup wake sockets
120
- self.wsock.lock().unwrap().take();
121
- self.wsock_fd.store(-1, atomic::Ordering::Relaxed);
122
- let token = Token(state.sock.as_raw_fd() as usize);
123
- let mut source = Source::FD(state.sock.as_raw_fd());
124
- {
125
- let guard = self.io.lock().unwrap();
126
- _ = guard.registry().deregister(&mut source);
127
- }
128
- self.handles_io.pin().remove(&token);
129
- }
130
-
131
85
  #[inline]
132
86
  fn step(&self, py: Python, state: &mut EventLoopRunState) -> std::result::Result<(), std::io::Error> {
133
87
  let mut sched_time: Option<u64> = None;
@@ -154,33 +108,29 @@ impl EventLoop {
154
108
  }
155
109
 
156
110
  // I/O
157
- let poll_result = match skip_poll {
158
- true => {
159
- state.events.clear();
160
- Ok(())
111
+ let poll_result = if skip_poll {
112
+ Ok(())
113
+ } else {
114
+ let idle_swap = !matches!(sched_time, Some(0));
115
+ if idle_swap {
116
+ self.idle.store(true, atomic::Ordering::Release);
161
117
  }
162
- false => {
163
- let idle_swap = !matches!(sched_time, Some(0));
118
+ let res = py.allow_threads(|| {
119
+ let mut io = self.io.lock().unwrap();
120
+ let res = io.poll(&mut state.events, sched_time.map(Duration::from_micros));
164
121
  if idle_swap {
165
- self.idle.store(true, atomic::Ordering::Release);
122
+ self.idle.store(false, atomic::Ordering::Release);
166
123
  }
167
- let res = py_allow_threads!(py, {
168
- let mut io = self.io.lock().unwrap();
169
- let res = io.poll(&mut state.events, sched_time.map(Duration::from_micros));
170
- if idle_swap {
171
- self.idle.store(false, atomic::Ordering::Release);
172
- }
173
- if let Err(ref err) = res {
174
- if err.kind() == std::io::ErrorKind::Interrupted {
175
- // if we got an interrupt, we retry ready events (as we might need to process signals)
176
- let _ = io.poll(&mut state.events, Some(Duration::from_millis(0)));
177
- }
124
+ if let Err(ref err) = res {
125
+ if err.kind() == std::io::ErrorKind::Interrupted {
126
+ // if we got an interrupt, we retry ready events (as we might need to process signals)
127
+ let _ = io.poll(&mut state.events, Some(Duration::from_millis(0)));
178
128
  }
179
- res
180
- });
181
- state.tick_last = Instant::now().duration_since(self.epoch).as_micros();
129
+ }
182
130
  res
183
- }
131
+ });
132
+ state.tick_last = Instant::now().duration_since(self.epoch).as_micros();
133
+ res
184
134
  };
185
135
 
186
136
  let mut cb_handles = {
@@ -190,7 +140,7 @@ impl EventLoop {
190
140
  self.counter_ready
191
141
  .fetch_sub(cb_handles.len(), atomic::Ordering::Release);
192
142
 
193
- {
143
+ if !skip_poll {
194
144
  let io_handles = self.handles_io.pin();
195
145
  for event in &state.events {
196
146
  // NOTE: cancellation is not necessary as we have custom futures
@@ -199,7 +149,6 @@ impl EventLoop {
199
149
  IOHandle::Py(handle) => self.handle_io_py(py, event, handle, &mut cb_handles),
200
150
  IOHandle::TCPListener(handle) => self.handle_io_tcpl(py, handle, &io_handles, &mut cb_handles),
201
151
  IOHandle::TCPStream(_) => self.handle_io_tcps(event, &mut cb_handles),
202
- IOHandle::Internal => self.handle_io_internal(&mut state.sock, &mut state.buf),
203
152
  IOHandle::Signals => self.handle_io_signals(py, &mut state.buf, &mut cb_handles),
204
153
  }
205
154
  }
@@ -245,11 +194,6 @@ impl EventLoop {
245
194
  len
246
195
  }
247
196
 
248
- #[inline]
249
- fn handle_io_internal(&self, socket: &mut socket2::Socket, buf: &mut [u8]) {
250
- self.read_from_sock(socket, buf);
251
- }
252
-
253
197
  #[inline]
254
198
  fn handle_io_py(
255
199
  &self,
@@ -338,8 +282,9 @@ impl EventLoop {
338
282
 
339
283
  #[inline(always)]
340
284
  fn wake(&self) {
341
- let fd = self.wsock_fd.load(atomic::Ordering::Relaxed);
342
- _ = syscall!(write(fd, WAKEB.as_ptr().cast(), 1));
285
+ if self.idle.load(atomic::Ordering::Acquire) {
286
+ _ = self.waker.wake();
287
+ }
343
288
  }
344
289
 
345
290
  pub(crate) fn tcp_listener_add(&self, listener: TcpListener, server: TCPServerRef) {
@@ -486,9 +431,7 @@ impl EventLoop {
486
431
  guard.push_back(Box::new(handle));
487
432
  }
488
433
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
489
- if self.idle.load(atomic::Ordering::Acquire) {
490
- self.wake();
491
- }
434
+ self.wake();
492
435
 
493
436
  Ok(())
494
437
  }
@@ -509,9 +452,7 @@ impl EventLoop {
509
452
  guard.push_back(Box::new(handle));
510
453
  }
511
454
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
512
- if self.idle.load(atomic::Ordering::Acquire) {
513
- self.wake();
514
- }
455
+ self.wake();
515
456
 
516
457
  Ok(())
517
458
  }
@@ -532,9 +473,7 @@ impl EventLoop {
532
473
  guard.push_back(Box::new(handle));
533
474
  }
534
475
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
535
- if self.idle.load(atomic::Ordering::Acquire) {
536
- self.wake();
537
- }
476
+ self.wake();
538
477
 
539
478
  Ok(())
540
479
  }
@@ -559,9 +498,7 @@ impl EventLoop {
559
498
  .map_err(|_| anyhow::anyhow!("lock acquisition failed"))?;
560
499
  guard.push(timer);
561
500
  }
562
- if self.idle.load(atomic::Ordering::Acquire) {
563
- self.wake();
564
- }
501
+ self.wake();
565
502
 
566
503
  Ok(())
567
504
  }
@@ -592,9 +529,7 @@ impl EventLoop {
592
529
  .map_err(|_| anyhow::anyhow!("lock acquisition failed"))?;
593
530
  guard.push(timer);
594
531
  }
595
- if self.idle.load(atomic::Ordering::Acquire) {
596
- self.wake();
597
- }
532
+ self.wake();
598
533
 
599
534
  Ok(())
600
535
  }
@@ -625,9 +560,40 @@ impl EventLoop {
625
560
  .map_err(|_| anyhow::anyhow!("lock acquisition failed"))?;
626
561
  guard.push(timer);
627
562
  }
628
- if self.idle.load(atomic::Ordering::Acquire) {
629
- self.wake();
563
+ self.wake();
564
+
565
+ Ok(())
566
+ }
567
+
568
+ #[allow(clippy::missing_errors_doc)]
569
+ pub fn schedule_handle(&self, handle: impl Handle + Send + 'static, delay: Option<Duration>) -> Result<()> {
570
+ match delay {
571
+ Some(delay) => {
572
+ let when = (Instant::now().duration_since(self.epoch) + delay).as_micros();
573
+ let timer = Timer {
574
+ handle: Box::new(handle),
575
+ when,
576
+ };
577
+ {
578
+ let mut guard = self
579
+ .handles_sched
580
+ .lock()
581
+ .map_err(|_| anyhow::anyhow!("lock acquisition failed"))?;
582
+ guard.push(timer);
583
+ }
584
+ }
585
+ None => {
586
+ {
587
+ let mut guard = self
588
+ .handles_ready
589
+ .lock()
590
+ .map_err(|_| anyhow::anyhow!("lock acquisition failed"))?;
591
+ guard.push_back(Box::new(handle));
592
+ }
593
+ self.counter_ready.fetch_add(1, atomic::Ordering::Release);
594
+ }
630
595
  }
596
+ self.wake();
631
597
 
632
598
  Ok(())
633
599
  }
@@ -637,17 +603,19 @@ impl EventLoop {
637
603
  impl EventLoop {
638
604
  #[new]
639
605
  fn new(py: Python) -> PyResult<Self> {
606
+ let poll = Poll::new()?;
607
+ let waker = Waker::new(poll.registry(), Token(0))?;
608
+
640
609
  Ok(Self {
641
610
  idle: atomic::AtomicBool::new(false),
642
- io: Mutex::new(Poll::new()?),
611
+ io: Mutex::new(poll),
612
+ waker: Arc::new(waker),
643
613
  handles_io: papaya::HashMap::with_capacity(128),
644
614
  handles_ready: Mutex::new(VecDeque::with_capacity(128)),
645
615
  handles_sched: Mutex::new(BinaryHeap::with_capacity(32)),
646
616
  epoch: Instant::now(),
647
617
  counter_ready: atomic::AtomicUsize::new(0),
648
618
  ssock: RwLock::new(None),
649
- wsock: Mutex::new(None),
650
- wsock_fd: atomic::AtomicI32::new(-1),
651
619
  closed: atomic::AtomicBool::new(false),
652
620
  exc_handler: RwLock::new(py.None()),
653
621
  exception_handler: RwLock::new(py.None()),
@@ -900,10 +868,7 @@ impl EventLoop {
900
868
  guard.push_back(bhandle);
901
869
  }
902
870
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
903
- // wake when necessary
904
- if self.idle.load(atomic::Ordering::Acquire) {
905
- self.wake();
906
- }
871
+ self.wake();
907
872
 
908
873
  handle
909
874
  }
@@ -1160,24 +1125,28 @@ impl EventLoop {
1160
1125
  }
1161
1126
 
1162
1127
  fn _run(&self, py: Python) -> PyResult<()> {
1163
- let mut state = self.run_pre()?;
1128
+ let mut state = EventLoopRunState {
1129
+ buf: vec![0; 4096].into_boxed_slice(),
1130
+ events: event::Events::with_capacity(128),
1131
+ read_buf: vec![0; 262_144].into_boxed_slice(),
1132
+ tick_last: 0,
1133
+ };
1164
1134
 
1165
1135
  loop {
1166
1136
  if self.stopping.load(atomic::Ordering::Acquire) {
1167
1137
  break;
1168
1138
  }
1169
1139
  if let Err(err) = self.step(py, &mut state) {
1170
- if err.kind() == std::io::ErrorKind::Interrupted
1171
- && self.sig_loop_handled.swap(false, atomic::Ordering::Relaxed)
1172
- {
1173
- continue;
1140
+ if err.kind() == std::io::ErrorKind::Interrupted {
1141
+ if self.sig_loop_handled.swap(false, atomic::Ordering::Relaxed) {
1142
+ continue;
1143
+ }
1144
+ break;
1174
1145
  }
1175
- self.run_post(&mut state);
1176
1146
  return Err(err.into());
1177
1147
  }
1178
1148
  }
1179
1149
 
1180
- self.run_post(&mut state);
1181
1150
  Ok(())
1182
1151
  }
1183
1152
  }
@@ -1,4 +1,4 @@
1
- use pyo3::{prelude::*, IntoPyObjectExt};
1
+ use pyo3::{IntoPyObjectExt, prelude::*};
2
2
  use std::sync::atomic;
3
3
 
4
4
  use crate::{
@@ -10,7 +10,7 @@ use crate::{
10
10
  #[cfg(not(PyPy))]
11
11
  use crate::py::run_in_ctx1;
12
12
 
13
- pub(crate) trait Handle {
13
+ pub trait Handle {
14
14
  fn run(&self, py: Python, event_loop: &EventLoop, state: &mut EventLoopRunState);
15
15
  fn cancelled(&self) -> bool {
16
16
  false
@@ -5,7 +5,7 @@ use std::os::fd::RawFd;
5
5
  #[cfg(windows)]
6
6
  use std::os::windows::io::RawSocket;
7
7
 
8
- use mio::{event::Source as MioSource, net::TcpListener, Interest, Registry, Token};
8
+ use mio::{Interest, Registry, Token, event::Source as MioSource, net::TcpListener};
9
9
 
10
10
  pub(crate) enum Source {
11
11
  TCPListener(TcpListener),
@@ -2,7 +2,7 @@ use pyo3::prelude::*;
2
2
  use std::sync::OnceLock;
3
3
 
4
4
  pub mod event_loop;
5
- mod handles;
5
+ pub mod handles;
6
6
  mod io;
7
7
  mod log;
8
8
  mod py;
@@ -3,10 +3,10 @@ use std::os::fd::{AsRawFd, FromRawFd};
3
3
 
4
4
  use anyhow::Result;
5
5
  use mio::{
6
- net::{TcpListener, TcpStream},
7
6
  Interest,
7
+ net::{TcpListener, TcpStream},
8
8
  };
9
- use pyo3::{buffer::PyBuffer, prelude::*, types::PyBytes, IntoPyObjectExt};
9
+ use pyo3::{IntoPyObjectExt, buffer::PyBuffer, prelude::*, types::PyBytes};
10
10
  use std::{
11
11
  borrow::Cow,
12
12
  cell::RefCell,
@@ -0,0 +1,12 @@
1
+ macro_rules! syscall {
2
+ ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{
3
+ let res = unsafe { libc::$fn($($arg, )*) };
4
+ if res < 0 {
5
+ Err(std::io::Error::last_os_error())
6
+ } else {
7
+ Ok(res)
8
+ }
9
+ }};
10
+ }
11
+
12
+ pub(crate) use syscall;
@@ -1,3 +1,6 @@
1
+ import threading
2
+
3
+
1
4
  def run_loop(loop):
2
5
  async def run():
3
6
  loop.stop()
@@ -57,3 +60,39 @@ def test_call_at(loop):
57
60
  dt = loop.time() - t0
58
61
 
59
62
  assert dt >= delay
63
+
64
+
65
+ def test_call_soon_threadsafe(loop):
66
+ calls = []
67
+
68
+ def cb(arg):
69
+ calls.append(arg)
70
+
71
+ def wake(cond):
72
+ with cond:
73
+ cond.notify_all()
74
+
75
+ def stop():
76
+ loop.stop()
77
+
78
+ def trun(cond1, cond2, loop, cb):
79
+ with cond1:
80
+ cond1.wait()
81
+ loop.call_soon_threadsafe(cb, 2)
82
+ with cond2:
83
+ cond2.wait()
84
+ loop.call_soon_threadsafe(cb, 4)
85
+
86
+ cond1 = threading.Condition()
87
+ cond2 = threading.Condition()
88
+ t = threading.Thread(target=trun, args=(cond1, cond2, loop, cb))
89
+ t.start()
90
+
91
+ loop.call_soon(cb, 1)
92
+ loop.call_soon(wake, cond1)
93
+ loop.call_later(0.5, cb, 3)
94
+ loop.call_later(0.6, wake, cond2)
95
+ loop.call_later(1.0, stop)
96
+ loop.run_forever()
97
+
98
+ assert calls == [1, 2, 3, 4]
@@ -1,5 +0,0 @@
1
- import sys
2
-
3
-
4
- _PYV = int(sys.version_info.major * 100 + sys.version_info.minor)
5
- _PY_311 = 311
rloop-0.1.3/src/utils.rs DELETED
@@ -1,27 +0,0 @@
1
- #[cfg(not(Py_GIL_DISABLED))]
2
- macro_rules! py_allow_threads {
3
- ($py:expr, $func:tt) => {
4
- $py.allow_threads(|| $func)
5
- };
6
- }
7
-
8
- #[cfg(Py_GIL_DISABLED)]
9
- macro_rules! py_allow_threads {
10
- ($py:expr, $func:tt) => {
11
- $func
12
- };
13
- }
14
-
15
- macro_rules! syscall {
16
- ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{
17
- let res = unsafe { libc::$fn($($arg, )*) };
18
- if res < 0 {
19
- Err(std::io::Error::last_os_error())
20
- } else {
21
- Ok(res)
22
- }
23
- }};
24
- }
25
-
26
- pub(super) use py_allow_threads;
27
- pub(crate) use syscall;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes