commit fb618978817b017c185323940202a62fe7be191e Author: Bert Peters Date: Thu Sep 1 21:44:10 2022 +0200 Initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..348d760 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "beul" +version = "0.1.0" +# Edition 2021 is not available in MSRV +edition = "2018" +license = "MIT OR Apache-2.0" +description = "It executes futures" +repository = "https://github.com/bertptrs/beul/" +authors = [ + "Bert Peters", +] +categories = [ + "asynchronous", + "development-tools", +] +keywords = [ + "futures", + "async", + "executor", + "runtime", +] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..38e57f7 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2022 Bert Peters + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..1385de5 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Bert Peters + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3b0e85 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Beul + +Beul is a minimalistic futures executor. No dependencies, no unsafe rust. + +## Usage + +Simply call `execute` with your future: + +```rust +beul::execute(async {}); +``` + +### Backwards compatibility + +This crate requires at least Rust 1.51, due to its reliance on [Wake]. Increases in this version +will be considered breaking changes. This crate follows semantic versioning. + +### Limitations + +Beul is a single-threaded executor and will not provide anything but execution. Futures that depend +on runtime features, as present for example in [Tokio], will not work. + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](./LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. + +[Tokio]: https://tokio.rs/ +[Wake]: https://doc.rust-lang.org/std/task/trait.Wake.html diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6e0695d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,61 @@ +//! Beul - It executes futures. +//! +//! This crate offers a single function: [`execute`], which will run a single future on the current +//! thread. No fancy executor, no future primitives, just a simple executor. No dependencies, no +//! unsafe rust. +//! +//! The design is based on the example `ThreadWaker` from the [`Wake`] documentation, which the +//! reentrancy issues resolved by using a [`Condvar`]. +//! +//! Beul is Dutch for executioner. +//! +//! # Usage +//! +//! ``` +//! beul::execute(async {}); +//! ``` +#![forbid(unsafe_code)] +use std::future::Future; +use std::sync::Arc; +use std::sync::Condvar; +use std::sync::Mutex; +use std::sync::PoisonError; +use std::task::Context; +use std::task::Poll; +use std::task::Wake; +use std::task::Waker; + +#[derive(Default)] +struct CondvarWake(Condvar); + +impl Wake for CondvarWake { + fn wake(self: Arc) { + self.0.notify_one() + } + + fn wake_by_ref(self: &Arc) { + self.0.notify_one() + } +} + +/// Block on specified [`Future`]. +pub fn execute(f: impl Future) -> T { + // TODO: replace with std::pin::pin once it gets stabilized + let mut pinned = Box::pin(f); + + let wake = Arc::new(CondvarWake::default()); + let waker = Waker::from(Arc::clone(&wake)); + + let mut context = Context::from_waker(&waker); + + let mutex = Mutex::new(()); + // Cannot panic but avoids generating the unwrap code + let mut guard = mutex.lock().unwrap_or_else(PoisonError::into_inner); + + loop { + match pinned.as_mut().poll(&mut context) { + Poll::Ready(value) => return value, + Poll::Pending => guard = wake.0.wait(guard).unwrap_or_else(PoisonError::into_inner), + } + } +} diff --git a/tests/futures.rs b/tests/futures.rs new file mode 100644 index 0000000..5124e78 --- /dev/null +++ b/tests/futures.rs @@ -0,0 +1,54 @@ +use std::future::Future; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::task::Context; +use std::task::Poll; +use std::thread; + +#[test] +fn test_simple() { + // Sanity check + assert_eq!(42, beul::execute(async { 42 })); + + // Some more esoteric futures + beul::execute(async { async {}.await }); + beul::execute(async { beul::execute(async {}) }); +} + +#[test] +fn test_threaded_future() { + /// Dummy future that sleeps until a separate thread wakes it + /// + /// It returns the number of times it has been awoken + struct ThreadedFuture(Arc, usize); + + impl Future for ThreadedFuture { + type Output = usize; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Note down that we've been polled + self.1 += 1; + + if self.0.load(Ordering::Acquire) { + Poll::Ready(self.1) + } else { + if self.1 == 1 { + let completer = Arc::clone(&self.0); + let waker = cx.waker().clone(); + thread::spawn(move || { + completer.store(true, Ordering::Release); + waker.wake(); + }); + } + + Poll::Pending + } + } + } + + let future = ThreadedFuture(Arc::new(AtomicBool::new(false)), 0); + + // Future should be polled twice, once initially and once after the wake-up + assert_eq!(beul::execute(future), 2); +}