├── .gitignore ├── Cargo.toml ├── README.md └── src ├── io.rs ├── jthread.rs ├── lib.rs ├── main.rs └── stop_token.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jthread" 3 | version = "0.1.0" 4 | authors = ["Aleksey Kladov "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libc = "0.2.62" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jthread 2 | 3 | A proof-of-concept implementation of C++'s [jthread](https://github.com/josuttis/jthread) for Rust. 4 | 5 | The main difference is that instead of using `std::stop_callback`, `StopToken` 6 | implements `std::future::Future`. This is enough to make syscalls like `read(2)` 7 | cancellable, while maintaining a synchronous API. 8 | 9 | 10 | ```rust 11 | use jthread; 12 | 13 | // Note: this is synchronous, blocking code. 14 | fn main() { 15 | let reader = jthread::spawn(|stop_token| { 16 | let mut stdin = jthread::io::stdin(); 17 | 18 | // `stop_token` allows to break free from a blocking read call 19 | while let Ok(line) = stdin.read_line(&stop_token) { 20 | let line = line.unwrap(); 21 | println!("> {:?}", line) 22 | } 23 | println!("exiting") // this will be printed to the screen 24 | }); 25 | 26 | std::thread::sleep(std::time::Duration::from_secs(3)); 27 | drop(reader); // cancels and joins the reader thread 28 | } 29 | ``` 30 | 31 | 32 | Internally, `read_line` uses `poll` to select between reading from the file 33 | descriptor and the cancelled notification. 34 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | io, 4 | os::unix::io::{AsRawFd, RawFd}, 5 | pin::Pin, 6 | sync::Arc, 7 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 8 | }; 9 | 10 | use libc::c_int; 11 | 12 | use crate::{StopToken, StoppedError}; 13 | 14 | pub struct Stdin { 15 | fd: RawFd, 16 | buf: Vec, 17 | } 18 | 19 | pub fn stdin() -> Stdin { 20 | Stdin::new() 21 | } 22 | 23 | impl Stdin { 24 | pub(crate) fn new() -> Stdin { 25 | let fd = io::stdin().as_raw_fd(); 26 | unsafe { 27 | let mut flags = libc::fcntl(fd, libc::F_GETFL); 28 | assert!(flags != -1); 29 | flags |= libc::O_NONBLOCK; 30 | libc::fcntl(fd, libc::F_SETFL, flags); 31 | } 32 | Stdin { 33 | fd, 34 | buf: Vec::new(), 35 | } 36 | } 37 | 38 | pub fn read_line( 39 | &mut self, 40 | mut st: &StopToken, 41 | ) -> Result, StoppedError> { 42 | unsafe { 43 | let mut fds: [c_int; 2] = [0; 2]; 44 | let res = libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK); 45 | assert!(res != -1); 46 | let pipe = Arc::new(Pipe(fds)); 47 | 48 | let raw_waker = raw_waker(&pipe); 49 | let waker = Waker::from_raw(raw_waker); 50 | let mut context = Context::from_waker(&waker); 51 | 52 | loop { 53 | let fut = Pin::new(&mut st); 54 | match Future::poll(fut, &mut context) { 55 | Poll::Ready(err) => return Err(err), 56 | Poll::Pending => (), 57 | } 58 | 59 | if let Some(idx) = self.buf.iter().position(|&b| b == b'\n') { 60 | let mut buf = self.buf.split_off(idx + 1); 61 | std::mem::swap(&mut buf, &mut self.buf); 62 | let res = String::from_utf8(buf) 63 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)); 64 | return Ok(res); 65 | } 66 | 67 | let mut pollfd = [ 68 | libc::pollfd { 69 | fd: self.fd, 70 | events: libc::POLLIN, 71 | revents: 0, 72 | }, 73 | libc::pollfd { 74 | fd: fds[0], 75 | events: libc::POLLIN, 76 | revents: 0, 77 | }, 78 | ]; 79 | let res = libc::poll(pollfd.as_mut_ptr(), 2, -1); 80 | assert!(res != -1); 81 | if pollfd[0].revents & libc::POLLIN == libc::POLLIN { 82 | self.buf.reserve(4096); 83 | let buf = self.buf.as_mut_ptr().add(self.buf.len()); 84 | let res = libc::read(self.fd, buf as *mut libc::c_void, 4096); 85 | if res < 0 { 86 | return Ok(Err(io::Error::from_raw_os_error(res as i32))); 87 | } else { 88 | self.buf.set_len(self.buf.len() + res as usize); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | struct Pipe([c_int; 2]); 97 | 98 | impl Drop for Pipe { 99 | fn drop(&mut self) { 100 | unsafe { 101 | let res = libc::close(self.0[0]); 102 | assert!(res != -1); 103 | let res = libc::close(self.0[1]); 104 | assert!(res != -1); 105 | } 106 | } 107 | } 108 | 109 | fn raw_waker(pipe: &Arc) -> RawWaker { 110 | let pipe = Arc::clone(pipe); 111 | RawWaker::new(Arc::into_raw(pipe) as *const (), &VTABLE) 112 | } 113 | 114 | static VTABLE: RawWakerVTable = RawWakerVTable::new( 115 | |ptr| unsafe { 116 | let pipe = Arc::from_raw(ptr as *const Pipe); 117 | let res = raw_waker(&pipe); 118 | Arc::into_raw(pipe); 119 | res 120 | }, 121 | |ptr| unsafe { 122 | let pipe = Arc::from_raw(ptr as *const Pipe); 123 | let res = libc::write(pipe.0[1], [92u8].as_ptr() as *const libc::c_void, 1); 124 | assert!(res != -1); 125 | }, 126 | |ptr| unsafe { 127 | let pipe: &Pipe = &*(ptr as *const Pipe); 128 | let res = libc::write(pipe.0[1], [92u8].as_ptr() as *const libc::c_void, 1); 129 | assert!(res != -1); 130 | }, 131 | |_data| (), 132 | ); 133 | -------------------------------------------------------------------------------- /src/jthread.rs: -------------------------------------------------------------------------------- 1 | use crate::{StopSource, StopToken}; 2 | 3 | pub struct JoinHandle { 4 | stop_source: Option, 5 | inner: Option>, 6 | } 7 | 8 | pub fn spawn(f: F) -> JoinHandle 9 | where 10 | F: FnOnce(StopToken) -> T, 11 | F: Send + 'static, 12 | T: Send + 'static, 13 | { 14 | let inner; 15 | let stop_source = StopSource::default(); 16 | let stop_token = stop_source.new_token(); 17 | inner = std::thread::spawn(move || f(stop_token)); 18 | JoinHandle { 19 | inner: Some(inner), 20 | stop_source: Some(stop_source), 21 | } 22 | } 23 | 24 | impl JoinHandle { 25 | pub fn join(mut self) -> std::thread::Result { 26 | let _ = self.stop_source.take(); 27 | self.inner.take().unwrap().join() 28 | } 29 | } 30 | 31 | // TODO: impl Future for JointHandle ? 32 | 33 | impl Drop for JoinHandle { 34 | fn drop(&mut self) { 35 | let _ = self.stop_source.take(); 36 | self.inner.take().unwrap().join().unwrap(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod jthread; 2 | mod stop_token; 3 | pub mod io; 4 | 5 | pub use crate::{ 6 | jthread::{spawn, JoinHandle}, 7 | stop_token::{StopSource, StopToken, StoppedError}, 8 | }; 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | #[test] 13 | fn it_works() { 14 | assert_eq!(2 + 2, 4); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use jthread; 2 | 3 | fn main() { 4 | let reader = jthread::spawn(|stop_token| { 5 | let mut stdin = jthread::io::stdin(); 6 | 7 | // `stop_token` allows to break free from a blocking read call 8 | while let Ok(line) = stdin.read_line(&stop_token) { 9 | let line = line.unwrap(); 10 | println!("> {:?}", line) 11 | } 12 | println!("exiting") // this will be printed to the screen 13 | }); 14 | 15 | std::thread::sleep(std::time::Duration::from_secs(3)); 16 | drop(reader); // cancels and joins the reader thread 17 | } 18 | -------------------------------------------------------------------------------- /src/stop_token.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | sync::Arc, 5 | sync::Mutex, 6 | task::{Context, Poll, Waker}, 7 | }; 8 | 9 | pub struct StoppedError; 10 | 11 | #[derive(Default)] 12 | pub struct StopSource { 13 | state: Arc>, 14 | } 15 | 16 | pub struct StopToken { 17 | state: Arc>, 18 | } 19 | 20 | #[derive(Default)] 21 | struct StopState { 22 | stop_requested: bool, 23 | wakers: Vec, 24 | } 25 | 26 | impl StopToken { 27 | pub fn check(&self) -> Result<(), StoppedError> { 28 | if self.state.lock().unwrap().stop_requested { 29 | Err(StoppedError) 30 | } else { 31 | Ok(()) 32 | } 33 | } 34 | } 35 | 36 | impl Future for &'_ StopToken { 37 | type Output = StoppedError; 38 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 39 | let mut state = self.state.lock().unwrap(); 40 | if state.stop_requested { 41 | return Poll::Ready(StoppedError); 42 | } 43 | state.wakers.push(cx.waker().clone()); 44 | Poll::Pending 45 | } 46 | } 47 | 48 | impl StopSource { 49 | pub fn new_token(&self) -> StopToken { 50 | StopToken { 51 | state: Arc::clone(&self.state), 52 | } 53 | } 54 | } 55 | 56 | impl Drop for StopSource { 57 | fn drop(&mut self) { 58 | let mut state = self.state.lock().unwrap(); 59 | state.stop_requested = true; 60 | for waker in state.wakers.drain(..) { 61 | waker.wake() 62 | } 63 | } 64 | } 65 | --------------------------------------------------------------------------------