Skip to content

Instantly share code, notes, and snippets.

@win-t
Last active April 8, 2020 00:25
Show Gist options
  • Save win-t/5dea92c7c6c2e4f663f5ac2d14b4aa1e to your computer and use it in GitHub Desktop.
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
/*
# 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)
}
@win-t
Copy link
Author

win-t commented Apr 8, 2020

Can you spot what is the problem?

Can you solve it?

to reproduce the problem

Open 3 terminal

on the first terminal run:

docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:alpine

on the second terminal run:

while :; do curl localhost:8080/epochtime; sleep 1; done

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 diesel

the correct behavior is request to /epochtime must not be delayed regardless of IO from service to another service (the app to postgres via diesel)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment