Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ Nio uses multiple worker threads to execute tasks.
| `nio::spawn_pinned_at` | Only captured variables | Pinned to a specific worker thread (by index) |
| `nio::spawn` | `Send` required | Not pinned, may move between threads at `.await` points |

Note: `nio::spawn_pinned`, `nio::spawn_pinned_at` accept async closures, Only captured variables required to be `Send`, task itself is `!Send`.
Note: `nio::spawn_pinned`, `nio::spawn_pinned_at` accept async closure, Only captured variables required to be `Send`, task itself is `!Send`.

## Example

```toml
[dependencies]
nio = { version = "0.1", features = ["tokio-io"] }
nio = { version = "0.1.3", features = ["tokio-io"] }
```

By default, Nio implements async traits from [futures-io](https://docs.rs/futures-io/latest/futures_io/). But the optional "tokio-io" feature implements async traits from [tokio::io](https://docs.rs/tokio/latest/tokio/io/).
Expand Down
2 changes: 1 addition & 1 deletion libs/nio-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nio-macros"
version = "0.2.0"
version = "0.3.0"
edition = "2024"

license = "Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions libs/nio-macros/src/expend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ pub fn nio_main(
#crate_path::RuntimeBuilder::new()
#test_config
#config
.rt()
.build()
.unwrap()
.block_on(|| #async_keyword move #body)
.block_on(#async_keyword move #body)
}
});
out
Expand Down
4 changes: 2 additions & 2 deletions libs/nio-rt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nio"
version = "0.1.2"
version = "0.1.3"
edition = "2024"

license = "Apache-2.0"
Expand All @@ -13,7 +13,7 @@ description = "Async runtime for Rust"
nio-task = { path = "../nio-task", version = "0.2" }
nio-future = { path = "../nio-future", version = "0.0.1" }
nio-threadpool = { path = "../nio-threadpool", version = "0.1" }
nio-macros = { path = "../nio-macros", version = "0.2" }
nio-macros = { path = "../nio-macros", version = "0.3" }
nio-metrics = { path = "../nio-metrics", version = "0.0.0" }

mio = { version = "1", features = ["os-poll", "net"] }
Expand Down
7 changes: 4 additions & 3 deletions libs/nio-rt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Nio

Nio is an async runtime for Rust.
Nio is a [Thread-Per-Core](https://nurmohammed840.github.io/posts/embracing-thread-per-core-architecture/) async runtime for Rust.

## Task spawning APIs

Expand All @@ -18,12 +18,13 @@ Nio uses multiple worker threads to execute tasks.
| `nio::spawn_pinned_at` | Only captured variables | Pinned to a specific worker thread (by index) |
| `nio::spawn` | `Send` required | Not pinned, may move between threads at `.await` points |

Note: `nio::spawn_pinned`, `nio::spawn_pinned_at` accept async closure, Only captured variables required to be `Send`, task itself is `!Send`.

## Example

```toml
[dependencies]
nio = { version = "0.1", features = ["tokio-io"] }
nio = { version = "0.1.3", features = ["tokio-io"] }
```

By default, Nio implements async traits from [futures-io](https://docs.rs/futures-io/latest/futures_io/). But the optional "tokio-io" feature implements async traits from [tokio::io](https://docs.rs/tokio/latest/tokio/io/).
Expand Down Expand Up @@ -59,4 +60,4 @@ async fn main() -> Result<()> {
nio::spawn_pinned(accept);
}
}
```
```
62 changes: 58 additions & 4 deletions libs/nio-rt/src/rt/event_loop.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use crate::{
LocalContext, RuntimeContext,
driver::{self, Driver},
rt::{context::NioContext, task_queue::TaskQueue},
rt::{context::NioContext, task::LocalScheduler, task_queue::TaskQueue},
};
use nio_task::Status;
use std::{io, ops::ControlFlow, rc::Rc, sync::Arc, time::Duration};
use std::{
io,
ops::ControlFlow,
rc::Rc,
sync::Arc,
task::{Context, Poll, Waker},
time::Duration,
};

pub struct EventLoop {
tick: u32,
driver: Driver,
local_ctx: Rc<LocalContext>,
pub local_ctx: Rc<LocalContext>,
}

impl EventLoop {
Expand All @@ -31,6 +38,53 @@ impl EventLoop {
}
}

pub fn run_until<Fut: Future>(&mut self, fut: Fut) -> Fut::Output {
let (task, jh) = unsafe {
LocalScheduler::spawn(
self.local_ctx.worker_id,
self.local_ctx.runtime_ctx.clone(),
fut,
)
};

let task_id = task.id();
self.local_ctx.add_task_to_local_queue(task);

self.run_with(|this, task_queue| {
for _ in 0..this.tick {
let Some(task) = (unsafe { this.local_ctx.local_queue(|q| q.pop_front()) }) else {
break;
};
match task.poll() {
Status::Yielded(task) => {
unsafe { this.local_ctx.local_queue(|q| q.push_back(task)) };
}
Status::Pending => {
let counter = task_queue.decrease_local();
this.local_ctx
.move_tasks_from_shared_to_local_queue(counter);
}
Status::Complete(meta) => {
let counter = task_queue.decrease_local();
this.local_ctx
.move_tasks_from_shared_to_local_queue(counter);

if meta.id() == task_id {
return ControlFlow::Break(());
}
}
}
}
ControlFlow::Continue(())
});

let jh = std::pin::pin!(jh);
match jh.poll(&mut Context::from_waker(Waker::noop())) {
Poll::Ready(result) => result.unwrap(),
Poll::Pending => unreachable!(),
}
}

pub fn run(&mut self) {
self.run_with(Self::execute_tasks);
}
Expand All @@ -54,7 +108,7 @@ impl EventLoop {
ControlFlow::Continue(())
}

pub fn run_with(&mut self, process_tasks: fn(&Self, &TaskQueue) -> ControlFlow<(), ()>) {
pub fn run_with(&mut self, process_tasks: impl Fn(&Self, &TaskQueue) -> ControlFlow<(), ()>) {
let task_queue = self.local_ctx.task_queue();

loop {
Expand Down
74 changes: 72 additions & 2 deletions libs/nio-rt/src/rt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ pub mod task;
mod task_queue;
mod worker;

use crate::{RuntimeBuilder, driver, rt::event_loop::EventLoop};
use std::{io, sync::Arc, thread};
use crate::{LocalContext, RuntimeBuilder, driver, rt::event_loop::EventLoop};
use std::{io, rc::Rc, sync::Arc, thread};

use nio_threadpool::ThreadPool;

Expand Down Expand Up @@ -56,6 +56,76 @@ impl RuntimeBuilder {

Ok(Runtime { context })
}

pub fn build(mut self) -> io::Result<LocalRuntime> {
let min_tasks_per_worker = match self.min_tasks_per_worker {
Some(count) => count.get(),
None => (self.worker_threads as u64 / 2).max(1),
};

let (workers, drivers) = Workers::new(self.worker_threads, min_tasks_per_worker)?;
let runtime_ctx = Arc::new(RuntimeContext {
workers,
#[cfg(feature = "metrics")]
measurement: {
let mut metrics = self.measurement.take().unwrap();
metrics.init(self.worker_threads.into());
metrics
},
threadpool: ThreadPool::new()
.max_threads_limit(self.max_blocking_threads)
.load_factor(self.threadpool_load_factor)
.stack_size(self.thread_stack_size)
.timeout(self.thread_timeout)
.name(self.thread_name.take().unwrap()),
});

let tick = self.event_interval;
let mut drivers = drivers.into_iter().enumerate();

let (id, driver) = drivers.next().unwrap();
let main_event_loop =
EventLoop::new(id as u8, driver, runtime_ctx.clone(), tick, LOCAL_QUEUE_CAP);

for (id, driver) in drivers {
let id = id as u8;
let runtime_ctx = runtime_ctx.clone();

self.create_thread(id)
.spawn(move || {
EventLoop::new(id, driver, runtime_ctx, tick, LOCAL_QUEUE_CAP).run();
})
.unwrap_or_else(|err| panic!("failed to spawn worker thread {id}; {err}"));
}

Ok(LocalRuntime { main_event_loop })
}
}

pub struct LocalRuntime {
main_event_loop: EventLoop,
}

impl LocalRuntime {
pub fn local_context(&self) -> Rc<LocalContext> {
self.main_event_loop.local_ctx.clone()
}

pub fn runtime_context(&self) -> Arc<RuntimeContext> {
self.main_event_loop.local_ctx.runtime_ctx.clone()
}

pub fn block_on<Fut: Future>(&mut self, fut: Fut) -> Fut::Output {
self.main_event_loop.run_until(fut)
}
}

impl std::ops::Deref for LocalRuntime {
type Target = LocalContext;
#[inline]
fn deref(&self) -> &Self::Target {
&self.main_event_loop.local_ctx
}
}

pub struct Runtime {
Expand Down
10 changes: 5 additions & 5 deletions libs/nio-rt/tests/macros_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ async fn test_macro_can_be_used_via_use() {
nio::spawn(async {}).await.unwrap();
}

#[nio::test]
async fn test_macro_is_resilient_to_shadowing() {
nio::spawn(async {}).await.unwrap();
with_arg(42);
}
// #[nio::test]
// async fn test_macro_is_resilient_to_shadowing() {
// nio::spawn(async {}).await.unwrap();
// with_arg(42);
// }

// https://github.com/tokio-rs/tokio/issues/3403
#[rustfmt::skip] // this `rustfmt::skip` is necessary because unused_braces does not warn if the block contains newline.
Expand Down