Last active
April 8, 2020 00:25
-
-
Save win-t/5dea92c7c6c2e4f663f5ac2d14b4aa1e to your computer and use it in GitHub Desktop.
Demo of bad example mixing blocking and non blocking call on rust async/await
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
# dpependencies in Cargo.toml | |
[dependencies] | |
diesel = { version = "1.4.4", features = ["postgres"] } | |
hyper = "0.13.4" | |
r2d2 = "0.8.8" | |
r2d2-diesel = "1.0.0" | |
tokio = { version = "0.2.16", features = ["full"] } | |
*/ | |
#[macro_use] | |
extern crate diesel; | |
use std::time::SystemTime; | |
use hyper::service::{make_service_fn, service_fn}; | |
use hyper::{Body, Method, Request, Response, Server, StatusCode}; | |
use diesel::pg::PgConnection; | |
use diesel::sql_query; | |
use diesel::sql_types::Integer; | |
use diesel::RunQueryDsl; | |
use r2d2_diesel::ConnectionManager; | |
type HyperRequest = Request<Body>; | |
type HyperResponse = Response<Body>; | |
type HyperResult = hyper::Result<HyperResponse>; | |
type DbPoolRef = r2d2::Pool<ConnectionManager<PgConnection>>; | |
#[tokio::main] | |
async fn main() { | |
let addr = ([127, 0, 0, 1], 8080).into(); | |
let db_pool_ref = r2d2::Pool::builder() | |
.build(ConnectionManager::<PgConnection>::new( | |
// connection string for docker container: | |
// docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:alpine | |
"postgres://postgres:password@localhost/postgres?sslmode=disable", | |
)) | |
.unwrap(); | |
let server = Server::bind(&addr).serve(make_service_fn(move |_| { | |
let db_pool_ref = db_pool_ref.clone(); | |
async move { | |
Ok::<_, hyper::Error>(service_fn(move |req| { | |
let db_pool_ref = db_pool_ref.clone(); | |
async move { do_routing(db_pool_ref, req).await } | |
})) | |
} | |
})); | |
println!("Listening on http://{}", addr); | |
server.await.unwrap(); | |
} | |
async fn do_routing(db_pool_ref: DbPoolRef, req: HyperRequest) -> HyperResult { | |
match (req.method(), req.uri().path()) { | |
(&Method::GET, "/") => resp_hello().await, | |
(&Method::GET, "/epochtime") => resp_epochtime().await, | |
(&Method::GET, "/somedata-from-db") => resp_somedata(db_pool_ref).await, | |
_ => resp_404().await, | |
} | |
} | |
async fn resp_hello() -> HyperResult { | |
Ok(Response::new(Body::from("Hello world!\n"))) | |
} | |
async fn resp_epochtime() -> HyperResult { | |
Ok(Response::new(Body::from(format!( | |
"seconds since epoch {}\n", | |
SystemTime::now() | |
.duration_since(SystemTime::UNIX_EPOCH) | |
.unwrap() | |
.as_secs() | |
)))) | |
} | |
async fn resp_somedata(db_pool_ref: DbPoolRef) -> HyperResult { | |
let data = match get_somedata_from_db(db_pool_ref) { | |
Ok(data) => data, | |
Err(msg) => return resp_500(msg).await, | |
}; | |
Ok(Response::new(Body::from(format!("somedata = {}\n", data)))) | |
} | |
async fn resp_404() -> HyperResult { | |
let mut resp = Response::default(); | |
*resp.status_mut() = StatusCode::NOT_FOUND; | |
*resp.body_mut() = Body::from("404 NOT FOUND\n"); | |
Ok(resp) | |
} | |
async fn resp_500(msg: &str) -> HyperResult { | |
eprintln!("Error: {}", msg); | |
let mut resp = Response::default(); | |
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; | |
*resp.body_mut() = Body::from("500 INTERNAL SERVER ERROR\n"); | |
Ok(resp) | |
} | |
fn get_somedata_from_db(db_pool_ref: DbPoolRef) -> Result<i32, &'static str> { | |
let dbconn = db_pool_ref | |
.get() | |
.map_err(|_| "Error on getting connection")?; | |
#[derive(QueryableByName)] | |
struct QueryResult { | |
#[sql_type = "Integer"] | |
data: i32, | |
} | |
// simulate slow query via pg_sleep 2 second | |
let results = sql_query(concat!( | |
"select data::integer ", | |
"from (values(floor(random()*10000),pg_sleep(2))) t(data, _);" | |
)) | |
.load::<QueryResult>(&dbconn as &PgConnection) | |
.map_err(|_| "Error on executing query")?; | |
Ok(results[0].data) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you spot what is the problem?
Can you solve it?
to reproduce the problem
Open 3 terminal
on the first terminal run:
on the second terminal run:
on the third terminal run:
bash -c 'for i in {1..100}; do curl -s localhost:8080/somedata-from-db > /dev/null & done; wait'
you can see that responses in the second terminal (request to
/epochtime
) is delayed because of request to/somedata-from-db
, this is happened because of all of Tokio executor thread blocked by dieselthe correct behavior is request to
/epochtime
must not be delayed regardless of IO from service to another service (the app to postgres via diesel)