rloop 0.1.4__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 (34) hide show
  1. {rloop-0.1.4 → rloop-0.1.5}/Cargo.lock +3 -3
  2. {rloop-0.1.4 → rloop-0.1.5}/Cargo.toml +1 -1
  3. {rloop-0.1.4 → rloop-0.1.5}/PKG-INFO +2 -2
  4. {rloop-0.1.4 → rloop-0.1.5}/pyproject.toml +1 -0
  5. {rloop-0.1.4 → rloop-0.1.5}/src/event_loop.rs +44 -111
  6. {rloop-0.1.4 → rloop-0.1.5}/tests/test_handles.py +39 -0
  7. {rloop-0.1.4 → rloop-0.1.5}/LICENSE +0 -0
  8. {rloop-0.1.4 → rloop-0.1.5}/README.md +0 -0
  9. {rloop-0.1.4 → rloop-0.1.5}/rloop/__init__.py +0 -0
  10. {rloop-0.1.4 → rloop-0.1.5}/rloop/_compat.py +0 -0
  11. {rloop-0.1.4 → rloop-0.1.5}/rloop/_rloop.pyi +0 -0
  12. {rloop-0.1.4 → rloop-0.1.5}/rloop/exc.py +0 -0
  13. {rloop-0.1.4 → rloop-0.1.5}/rloop/futures.py +0 -0
  14. {rloop-0.1.4 → rloop-0.1.5}/rloop/loop.py +0 -0
  15. {rloop-0.1.4 → rloop-0.1.5}/rloop/server.py +0 -0
  16. {rloop-0.1.4 → rloop-0.1.5}/rloop/subprocess.py +0 -0
  17. {rloop-0.1.4 → rloop-0.1.5}/rloop/transports.py +0 -0
  18. {rloop-0.1.4 → rloop-0.1.5}/rloop/utils.py +0 -0
  19. {rloop-0.1.4 → rloop-0.1.5}/src/handles.rs +0 -0
  20. {rloop-0.1.4 → rloop-0.1.5}/src/io.rs +0 -0
  21. {rloop-0.1.4 → rloop-0.1.5}/src/lib.rs +0 -0
  22. {rloop-0.1.4 → rloop-0.1.5}/src/log.rs +0 -0
  23. {rloop-0.1.4 → rloop-0.1.5}/src/py.rs +0 -0
  24. {rloop-0.1.4 → rloop-0.1.5}/src/server.rs +0 -0
  25. {rloop-0.1.4 → rloop-0.1.5}/src/sock.rs +0 -0
  26. {rloop-0.1.4 → rloop-0.1.5}/src/tcp.rs +0 -0
  27. {rloop-0.1.4 → rloop-0.1.5}/src/time.rs +0 -0
  28. {rloop-0.1.4 → rloop-0.1.5}/src/utils.rs +0 -0
  29. {rloop-0.1.4 → rloop-0.1.5}/tests/conftest.py +0 -0
  30. {rloop-0.1.4 → rloop-0.1.5}/tests/tcp/__init__.py +0 -0
  31. {rloop-0.1.4 → rloop-0.1.5}/tests/tcp/test_tcp_conn.py +0 -0
  32. {rloop-0.1.4 → rloop-0.1.5}/tests/tcp/test_tcp_server.py +0 -0
  33. {rloop-0.1.4 → rloop-0.1.5}/tests/test_sockets.py +0 -0
  34. {rloop-0.1.4 → rloop-0.1.5}/tests/udp/test_udp.py +0 -0
@@ -16,9 +16,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
16
16
 
17
17
  [[package]]
18
18
  name = "cc"
19
- version = "1.2.29"
19
+ version = "1.2.32"
20
20
  source = "registry+https://github.com/rust-lang/crates.io-index"
21
- checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
21
+ checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
22
22
  dependencies = [
23
23
  "shlex",
24
24
  ]
@@ -189,7 +189,7 @@ dependencies = [
189
189
 
190
190
  [[package]]
191
191
  name = "rloop"
192
- version = "0.1.4"
192
+ version = "0.1.5"
193
193
  dependencies = [
194
194
  "anyhow",
195
195
  "libc",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rloop"
3
- version = "0.1.4"
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"
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rloop
3
- Version: 0.1.4
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
@@ -20,7 +21,6 @@ License-File: LICENSE
20
21
  Summary: An asyncio event loop implemented in Rust
21
22
  Keywords: asyncio
22
23
  Home-Page: https://github.com/gi0baro/rloop
23
- Author: Giovanni Barillari <g@baro.dev>
24
24
  Author-email: Giovanni Barillari <g@baro.dev>
25
25
  License: BSD-3-Clause
26
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',
@@ -3,12 +3,12 @@ use std::{
3
3
  io::Read,
4
4
  mem,
5
5
  os::fd::{AsRawFd, FromRawFd},
6
- sync::{Mutex, RwLock, atomic},
6
+ sync::{Arc, Mutex, RwLock, atomic},
7
7
  time::{Duration, Instant},
8
8
  };
9
9
 
10
10
  use anyhow::Result;
11
- use mio::{Interest, Poll, Token, event, net::TcpListener};
11
+ use mio::{Interest, Poll, Token, Waker, event, net::TcpListener};
12
12
  use pyo3::prelude::*;
13
13
 
14
14
  use crate::{
@@ -19,13 +19,9 @@ use crate::{
19
19
  server::Server,
20
20
  tcp::{TCPReadHandle, TCPServer, TCPServerRef, TCPTransport, TCPWriteHandle},
21
21
  time::Timer,
22
- utils::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),
@@ -47,7 +43,6 @@ 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 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(|| {
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,7 @@ 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();
630
- }
563
+ self.wake();
631
564
 
632
565
  Ok(())
633
566
  }
@@ -660,9 +593,7 @@ impl EventLoop {
660
593
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
661
594
  }
662
595
  }
663
- if self.idle.load(atomic::Ordering::Acquire) {
664
- self.wake();
665
- }
596
+ self.wake();
666
597
 
667
598
  Ok(())
668
599
  }
@@ -672,17 +603,19 @@ impl EventLoop {
672
603
  impl EventLoop {
673
604
  #[new]
674
605
  fn new(py: Python) -> PyResult<Self> {
606
+ let poll = Poll::new()?;
607
+ let waker = Waker::new(poll.registry(), Token(0))?;
608
+
675
609
  Ok(Self {
676
610
  idle: atomic::AtomicBool::new(false),
677
- io: Mutex::new(Poll::new()?),
611
+ io: Mutex::new(poll),
612
+ waker: Arc::new(waker),
678
613
  handles_io: papaya::HashMap::with_capacity(128),
679
614
  handles_ready: Mutex::new(VecDeque::with_capacity(128)),
680
615
  handles_sched: Mutex::new(BinaryHeap::with_capacity(32)),
681
616
  epoch: Instant::now(),
682
617
  counter_ready: atomic::AtomicUsize::new(0),
683
618
  ssock: RwLock::new(None),
684
- wsock: Mutex::new(None),
685
- wsock_fd: atomic::AtomicI32::new(-1),
686
619
  closed: atomic::AtomicBool::new(false),
687
620
  exc_handler: RwLock::new(py.None()),
688
621
  exception_handler: RwLock::new(py.None()),
@@ -935,10 +868,7 @@ impl EventLoop {
935
868
  guard.push_back(bhandle);
936
869
  }
937
870
  self.counter_ready.fetch_add(1, atomic::Ordering::Release);
938
- // wake when necessary
939
- if self.idle.load(atomic::Ordering::Acquire) {
940
- self.wake();
941
- }
871
+ self.wake();
942
872
 
943
873
  handle
944
874
  }
@@ -1195,7 +1125,12 @@ impl EventLoop {
1195
1125
  }
1196
1126
 
1197
1127
  fn _run(&self, py: Python) -> PyResult<()> {
1198
- 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
+ };
1199
1134
 
1200
1135
  loop {
1201
1136
  if self.stopping.load(atomic::Ordering::Acquire) {
@@ -1208,12 +1143,10 @@ impl EventLoop {
1208
1143
  }
1209
1144
  break;
1210
1145
  }
1211
- self.run_post(&mut state);
1212
1146
  return Err(err.into());
1213
1147
  }
1214
1148
  }
1215
1149
 
1216
- self.run_post(&mut state);
1217
1150
  Ok(())
1218
1151
  }
1219
1152
  }
@@ -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]
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes