If my code has:
#[tokio::main]
async fn main() {
// mutates a global read-only variable "unsafe"
}
Will that mutation on the global read-only variable happen before or after Tokio sets up its thread pool?
From the tokio documentation:
#[tokio::main]
async fn main() {
println!("Hello world");
}
Equivalent code not using #[tokio::main]
fn main() {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
println!("Hello world");
})
}
So the code in async fn main() is run by the executor, after its been started. If you want to run code before the executor has started, you can simply modify the generated code above or use the attribute on a different function:
#[tokio::main]
async fn start() {
println!("Hello world");
}
fn main() {
// do your thing
start();
}
Related
I want to be able to modify a variable from within two separate co-routines but I can't pass mutable references to both co-routines at the same time.
Here's an example of what I'm trying to do:
struct something
{
a: i8,
}
impl something
{
async fn method1(&mut self)
{
loop
{
sleep(Duration::from_millis(500));
self.a += 1;
}
}
async fn method2(&mut self, s: WebSocketStream)
{
s.try_for_each(|msg|
// Do stuff with (self)
future::ok(())
);
}
}
async fn f() -> Select<(), ()>
{
let t = something
{
a: 5,
};
let c1 = something.method1();
let c2 = something.method2(stream); // let's assume we had stream from before.
future::select(c1, c2)
}
#[tokio::main]
async fn main()
{
f();
}
Is there a way to accomplish this?
// src/server.rs
use axum::{
extract::Path,
response::{IntoResponse, Response},
routing::get,
};
pub struct Server {}
impl Server {
pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
let axum_http_make_service = axum::Router::new()
.route("/:sec", get(wait_sec_event))
.into_make_service();
let http_server =
axum::Server::bind(&"0.0.0.0:4000".parse().unwrap()).serve(axum_http_make_service);
let http_handle = tokio::spawn(http_server);
let _ = tokio::try_join!(http_handle)?;
Ok(())
}
}
async fn wait_sec_event(Path(sec): Path<String>) -> Response {
let a = std::time::Duration::from_secs(sec.parse::<u64>().unwrap());
std::thread::sleep(a);
"yo".into_response()
}
// src/app.rs
use std::net::SocketAddr;
use crate::server;
pub struct App {
port: SocketAddr,
}
impl App {
pub fn new(p: SocketAddr) -> Self {
Self { port: p }
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
server::Server::run().await
}
}
// src/main.rs
use std::net::SocketAddr;
use app::App;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// build our application with a single route
let app = App::new(SocketAddr::from(([0, 0, 0, 0], 4000)));
app.run().await
}
pub mod app;
pub mod server;
When I tried to implement a axum server I found that if I put axum::Server::bind(&"0.0.0.0:4000".parse().unwrap()).serve(axum_http_make_service); into tokio::spawn instead of just await.unwrap() it
the server just can't accept requests in parallel.
means if I do curl 127.0.0.1:4000/10 then curl 127.0.0.1:4000/3 ,
the later request won't execute until the first one is finished This won't happen if I just await.unwrap() it.
Any idea where I might make a mistake?
You use std::thread::sleep, blocking the thread, which you shouldn't do in an async environment because it can prevent other tasks on the same thread to run like you experienced.
Use tokio::time::sleep instead:
async fn wait_sec_event(Path(sec): Path<String>) -> Response {
let a = std::time::Duration::from_secs(sec.parse::<u64>().unwrap());
tokio::time::sleep(a).await;
"yo".into_response()
}
I believe the difference in behaviour is because more or less by chance the tasks get spawned on different threads in your directly awaiting scenario while they get spawned on the same thread when using tokio::spawn.
I want to add sleep to avoid busy loop in Future::poll(), expect the finally effect is that runtime call poll() every second, the fifth second will return Poll::Ready(()).
According to my understanding, poll() is be called in first, this.sleeper.poll(cx) will return Poll::Pending immediately, cx.waker().wake_by_ref() will make current task to execute again immediately.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use pin_project_lite::pin_project;
use std::time::{Duration, Instant};
pin_project! {
struct Delay {
when: Instant,
#[pin]
sleeper: tokio::time::Sleep,
}
}
impl Delay {
pub fn new(when: Instant) -> Self {
Self {
when,
sleeper: tokio::time::sleep(Duration::from_secs(1))
}
}
}
impl Future for Delay {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.when < Instant::now() {
Poll::Ready(())
} else {
let this = self.project();
// I want to add sleep to avoid busy loop.
this.sleeper.poll(cx);
// here will make CPU busy loop, uninterrupted call poll()
cx.waker().wake_by_ref();
// expect five "1", but will get many "1"
print!("1");
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_secs(5);
let delay1 = Delay::new(when);
delay1.await;
println!("Hello, world!");
}
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=030fbade7a2f1e5df50c4774fc06fdf1
There are two problems here. First and simplest, when your future is delegating to another future in poll(), you do not need to call the waker at all — the other future will do that. In this case, the Sleeper takes care of getting the waker called at the right time, causing your future to be polled again.
Second, you're ignoring the return value of the sleeper.poll(). If it returns Poll::Ready, you need to make sure you don't poll it again — either do something else, or replace self.sleeper with a new sleep(). Polling a future again after it returned Poll::Ready is not meaningful, and has consequences left up to the future (often a panic).
finally, I resoved this problem by changing Sleep to Interval and call poll_tick().
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use pin_project_lite::pin_project;
use std::time::{Duration, Instant};
use tokio::time::Sleep;
pin_project! {
struct Delay {
when: Instant,
#[pin]
sleeper: tokio::time::Interval,
}
}
impl Delay {
pub fn new(when: Instant) -> Self {
Self {
when,
sleeper: tokio::time::interval(Duration::from_secs(1))
}
}
}
impl Future for Delay {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
println!("0");
if self.when < Instant::now() {
println!("1");
Poll::Ready(())
} else {
println!("2");
let this = unsafe {self.get_unchecked_mut()};
//let this = self.project();
this.sleeper.poll_tick(cx);
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_secs(5);
let delay1 = Delay::new(when);
delay1.await;
}
A subset of my tests need a resource that is only allowed to be created once.
This resource also has to be dropped at some point (not before the last test needing it finished).
Currently I use a static OnceCell:
struct MyResource {}
impl Drop for MyResource {
fn drop(&mut self) {
println!("Dropped MyResource");
}
}
impl MyResource {
fn new() -> MyResource {
println!("Created MyResource");
MyResource {}
}
}
fn main() {
let _my_resource = MyResource::new();
println!("Hello, world!");
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::OnceCell;
static GET_RESOURCE: OnceCell<MyResource> = OnceCell::const_new();
async fn init_my_resource() {
GET_RESOURCE.get_or_init(|| async {
MyResource::new()
}).await;
}
#[tokio::test]
async fn some_test() {
init_my_resource().await;
assert_eq!("some assert", "some assert");
}
}
This works fine except that it never drops the resource.
Is there a better way to do this that also drops the resource?
The answer to How to run setup code before any tests run in Rust? does not answer this question as it only answers how to initialize something once, but not how to drop something after the tests ran.
The crate ctor provides the dtor macro, which allows you to manually drop the resources once all your tests are done.
The most straight-forward solution I found is offered by the crate static_init:
struct MyResource {}
impl Drop for MyResource {
fn drop(&mut self) {
println!("Dropped my rescource");
}
}
impl MyResource {
fn new() -> MyResource {
println!("Created MyResource");
MyResource {}
}
}
fn main() {
let _my_resource = MyResource::new();
println!("Hello, world!");
}
#[cfg(test)]
mod tests {
use super::*;
use static_init::dynamic;
#[dynamic(drop)]
static mut RES: MyResource = MyResource::new();
#[tokio::test]
async fn some_test() {
println!("Running test");
assert_eq!("some assert", "some assert");
}
}
Another proposed solution was using ctor and dtor of the crate ctor. Though using this with a plain static mut RES: MyResource results in unsafe code as we need to modify a mutable static.
How do I inject dependencies into my route handlers in Warp? A trivial example is as follows. I have a route that I want to serve a static value that is determined at startup time, but the filter is what passes values into the final handler. How do I pass additional data without creating global variables? This would be useful for dependency injection.
pub fn root_route() -> BoxedFilter<()> {
warp::get().and(warp::path::end()).boxed()
}
pub async fn root_handler(git_sha: String) -> Result<impl warp::Reply, warp::Rejection> {
Ok(warp::reply::json(
json!({
"sha": git_sha
})
.as_object()
.unwrap(),
))
}
#[tokio::main]
async fn main() {
let git_sha = "1234567890".to_string();
let api = root_route().and_then(root_handler);
warp::serve(api).run(([0,0,0,0], 8080)).await;
}
Here is a simple example. By using the .and() in conjunction with .map(move ||)
you can add parameters to the tuple that will be passed into the final handler function.
use warp::filters::BoxedFilter;
use warp::Filter;
#[macro_use]
extern crate serde_json;
pub fn root_route() -> BoxedFilter<()> {
warp::get().and(warp::path::end()).boxed()
}
pub async fn root_handler(git_sha: String) -> Result<impl warp::Reply, warp::Rejection> {
Ok(warp::reply::json(
json!({
"sha": git_sha
})
.as_object()
.unwrap(),
))
}
pub fn with_sha(git_sha: String) -> impl Filter<Extract = (String,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || git_sha.clone())
}
#[tokio::main]
async fn main() {
let git_sha = "1234567890".to_string();
let api = root_route().and(with_sha(git_sha)).and_then(root_handler);
warp::serve(api).run(([0,0,0,0], 8080)).await;
}