├── .gitignore ├── readme.md ├── tests └── turn.rs ├── Cargo.toml └── src ├── async_await.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # turn me 2 | this is a minimal tokio runtime that can be executed step by step 3 | 4 | it's useful when you need to synchronize objects of an external application when use tokio features -------------------------------------------------------------------------------- /tests/turn.rs: -------------------------------------------------------------------------------- 1 | use futures::prelude::*; 2 | use futures::sync::oneshot; 3 | 4 | #[test] 5 | fn turn_once() { 6 | let (tx, rx) = oneshot::channel(); 7 | let rx = rx.map_err(|_| ()); 8 | 9 | turn_me::init_with(rx); 10 | 11 | let _ = tx.send(()); 12 | 13 | let polled = turn_me::turn(); 14 | assert_eq!(polled, true); 15 | } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "turn-me" 3 | version = "0.1.0" 4 | authors = ["ZOTTCE "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | futures = "0.1.25" 9 | tokio-current-thread = "0.1.6" 10 | tokio-executor = "0.1.7" 11 | tokio-timer = "0.2.10" 12 | tokio-reactor = "0.1.9" 13 | tokio-threadpool = "0.1.13" 14 | tokio-async-await = { version = "0.1.6", features = ["async-await-preview"] } -------------------------------------------------------------------------------- /src/async_await.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future as StdFuture; 2 | use std::pin::Pin; 3 | use std::task::{Poll, Waker}; 4 | 5 | fn map_ok(future: T) -> impl StdFuture> { 6 | MapOk(future) 7 | } 8 | 9 | struct MapOk(T); 10 | 11 | impl MapOk { 12 | fn future<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut T> { 13 | unsafe { Pin::map_unchecked_mut(self, |x| &mut x.0) } 14 | } 15 | } 16 | 17 | impl StdFuture for MapOk { 18 | type Output = Result<(), ()>; 19 | 20 | fn poll(self: Pin<&mut Self>, waker: &Waker) -> Poll { 21 | match self.future().poll(waker) { 22 | Poll::Ready(_) => Poll::Ready(Ok(())), 23 | Poll::Pending => Poll::Pending, 24 | } 25 | } 26 | } 27 | 28 | /// Like `tokio::run`, but takes an `async` block 29 | pub fn run_async(future: F) 30 | where 31 | F: StdFuture + Send + 'static, 32 | { 33 | use tokio_async_await::compat::backward; 34 | let future = backward::Compat::new(map_ok(future)); 35 | 36 | crate::init_with(future); 37 | } 38 | 39 | /// Like `tokio::spawn`, but takes an `async` block 40 | pub fn spawn_async(future: F) 41 | where 42 | F: StdFuture + Send + 'static, 43 | { 44 | use tokio_async_await::compat::backward; 45 | let future = backward::Compat::new(map_ok(future)); 46 | 47 | crate::spawn(future); 48 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(futures_api)] 2 | 3 | use tokio_current_thread::CurrentThread; 4 | use tokio_reactor::{self, Reactor}; 5 | use tokio_threadpool::{blocking, ThreadPool}; 6 | use tokio_timer::clock::{self, Clock}; 7 | use tokio_timer::timer::{self, Timer}; 8 | 9 | use std::cell::RefCell; 10 | use std::time::Duration; 11 | 12 | use futures::future::poll_fn; 13 | use futures::sync::oneshot; 14 | use futures::Future; 15 | 16 | pub mod async_await; 17 | 18 | thread_local!(static RT: RefCell = RefCell::new(Runtime::new())); 19 | thread_local!(static POOL: RefCell = RefCell::new(ThreadPool::new())); 20 | 21 | // re-exports 22 | pub use tokio_current_thread::{spawn, RunError}; 23 | pub use crate::async_await::*; 24 | 25 | /// Minimal signle-threaded tokio runtime that runs by calling [`turn`] or [`turn_with_timeout`]. 26 | /// 27 | /// It's useful when you work with external applications and they require to export some tick function. 28 | /// 29 | /// [`turn`]: ./fn.turn.html 30 | /// [`turn_with_timeout`]: ./fn.turn_with_timeout.html 31 | pub struct Runtime { 32 | reactor_handle: tokio_reactor::Handle, 33 | timer_handle: timer::Handle, 34 | clock: Clock, 35 | executor: CurrentThread>, 36 | } 37 | 38 | impl Runtime { 39 | fn new() -> Runtime { 40 | let clock = Clock::system(); 41 | let reactor = Reactor::new().expect("Couldn't create runtime"); 42 | let reactor_handle = reactor.handle(); 43 | 44 | let timer = Timer::new_with_now(reactor, clock.clone()); 45 | let timer_handle = timer.handle(); 46 | 47 | let executor = CurrentThread::new_with_park(timer); 48 | 49 | Runtime { 50 | reactor_handle, 51 | timer_handle, 52 | clock, 53 | executor, 54 | } 55 | } 56 | 57 | fn turn(&mut self, max_wait: Option) -> bool { 58 | self.enter(|executor| executor.turn(max_wait)) 59 | .map(|turn| turn.has_polled()) 60 | .unwrap_or(false) 61 | } 62 | 63 | fn run(&mut self) -> Result<(), RunError> { 64 | self.enter(|executor| executor.run()) 65 | } 66 | 67 | fn enter(&mut self, f: F) -> R 68 | where 69 | F: FnOnce(&mut tokio_current_thread::Entered>) -> R, 70 | { 71 | let Runtime { 72 | ref reactor_handle, 73 | ref timer_handle, 74 | ref clock, 75 | ref mut executor, 76 | .. 77 | } = *self; 78 | 79 | let mut enter = tokio_executor::enter().expect("multiple instances"); 80 | 81 | tokio_reactor::with_default(&reactor_handle, &mut enter, |enter| { 82 | clock::with_default(&clock, enter, |enter| { 83 | timer::with_default(&timer_handle, enter, |enter| { 84 | let mut default_executor = tokio_current_thread::TaskExecutor::current(); 85 | tokio_executor::with_default(&mut default_executor, enter, |enter| { 86 | let mut exectutor = executor.enter(enter); 87 | f(&mut exectutor) 88 | }) 89 | }) 90 | }) 91 | }) 92 | } 93 | } 94 | 95 | /// Initialize a [`Runtime`] and spawn (like [`tokio::run`]) a future. 96 | /// 97 | /// It does: 98 | /// * Creates a thread local [`Runtime`] instance. 99 | /// * Spawns the passed future into the executor of the [`Runtime`]. 100 | /// * Returns flow control back to you. 101 | /// 102 | /// [`tokio::run`]: https://docs.rs/tokio/0.1/tokio/runtime/fn.run.html 103 | /// [`Runtime`]: ./struct.Runtime.html 104 | pub fn init_with(future: F) 105 | where 106 | F: Future + 'static, 107 | { 108 | RT.with(|rt| { 109 | rt.borrow_mut().executor.spawn(future); 110 | }); 111 | } 112 | 113 | /// Manually turns the [`Runtime`] with 100 ms timeout. 114 | /// 115 | /// [`Runtime`]: ./struct.Runtime.html 116 | pub fn turn() -> bool { 117 | RT.with(|rt| { 118 | let mut rt = rt.borrow_mut(); 119 | rt.turn(Some(Duration::from_millis(100))) 120 | }) 121 | } 122 | 123 | /// Same as [`turn`] but with explict timeout. 124 | /// 125 | /// [`turn`]: ./fn.turn.html 126 | pub fn turn_with_timeout(max_wait: Option) -> bool { 127 | RT.with(|rt| { 128 | let mut rt = rt.borrow_mut(); 129 | rt.turn(max_wait) 130 | }) 131 | } 132 | 133 | /// Creates a thread local [`ThreadPool`] and spawns an expensive task on it. 134 | /// 135 | /// [`ThreadPool`]: https://docs.rs/tokio-threadpool/0.1.13/tokio_threadpool/struct.ThreadPool.html 136 | pub fn expensive_task(mut task: F) -> impl Future 137 | where 138 | F: FnMut() -> R + Send + Sync, 139 | F: 'static, 140 | R: Send + 'static, 141 | { 142 | let (tx, rx) = oneshot::channel(); 143 | 144 | POOL.with(move |thread_pool| { 145 | let task = poll_fn(move || blocking(|| task())) 146 | .map_err(|_| ()) 147 | .map(|response| { 148 | let _ = tx.send(response); 149 | }); 150 | 151 | thread_pool.borrow_mut().spawn(task); 152 | }); 153 | 154 | rx.map_err(|_| ()) 155 | } 156 | 157 | /// Run the executor to completion, blocking the thread until all spawned futures have completed. 158 | pub fn run() -> Result<(), RunError> { 159 | RT.with(|rt| { 160 | let mut rt = rt.borrow_mut(); 161 | rt.run() 162 | }) 163 | } 164 | --------------------------------------------------------------------------------