Initial import

This commit is contained in:
2022-09-01 21:44:10 +02:00
commit fb61897881
7 changed files with 212 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/Cargo.lock

21
Cargo.toml Normal file
View File

@@ -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",
]

13
LICENSE-APACHE Normal file
View File

@@ -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.

21
LICENSE-MIT Normal file
View File

@@ -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.

40
README.md Normal file
View File

@@ -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

61
src/lib.rs Normal file
View File

@@ -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>) {
self.0.notify_one()
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.notify_one()
}
}
/// Block on specified [`Future`].
pub fn execute<T>(f: impl Future<Output = T>) -> 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),
}
}
}

54
tests/futures.rs Normal file
View File

@@ -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<AtomicBool>, usize);
impl Future for ThreadedFuture {
type Output = usize;
fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// 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);
}