diff --git a/src/main.rs b/src/main.rs index a1cf2dd..2c0fcbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -282,10 +282,39 @@ async fn main() -> Result<(), Box> { let listener = TcpListener::bind(&bind_address).await?; info!(address = %bind_address, "sustenance listening"); - axum::serve(listener, app).await?; + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await?; + info!("shutdown complete; closing database"); Ok(()) } +async fn shutdown_signal() { + let ctrl_c = async { + tokio::signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + + info!("signal received; starting graceful shutdown"); +} + async fn home() -> Redirect { Redirect::to("/lists") } @@ -431,10 +460,7 @@ async fn lists_page( State(state): State, user: CurrentUser, ) -> Result { - let lists = state - .db - .list_summaries() - .await?; + let lists = state.db.list_summaries().await?; Ok(html_response(views::lists_page( &user.session.user, &lists, @@ -498,10 +524,7 @@ async fn add_item( .db .add_item(list_id, name, quantity, note, category_id) .await?; - state - .hub - .publish_list_changed(list_id, revision) - .await; + state.hub.publish_list_changed(list_id, revision).await; list_fragment_response(&state, &user, list_id).await } @@ -518,14 +541,8 @@ async fn check_item( "0" | "false" => false, _ => return Err(AppError::BadRequest("Invalid checked value.".into())), }; - let revision = state - .db - .set_item_checked(list_id, item_id, checked) - .await?; - state - .hub - .publish_list_changed(list_id, revision) - .await; + let revision = state.db.set_item_checked(list_id, item_id, checked).await?; + state.hub.publish_list_changed(list_id, revision).await; list_fragment_response(&state, &user, list_id).await } @@ -554,10 +571,7 @@ async fn edit_item( parse_category_id(form.category_id), ) .await?; - state - .hub - .publish_list_changed(list_id, revision) - .await; + state.hub.publish_list_changed(list_id, revision).await; list_fragment_response(&state, &user, list_id).await } @@ -570,10 +584,7 @@ async fn delete_item( verify_csrf(&user, &form.csrf)?; require_access(&state, list_id).await?; let revision = state.db.delete_item(list_id, item_id).await?; - state - .hub - .publish_list_changed(list_id, revision) - .await; + state.hub.publish_list_changed(list_id, revision).await; list_fragment_response(&state, &user, list_id).await } @@ -592,10 +603,7 @@ async fn create_category( )); } let revision = state.db.create_category(list_id, name).await?; - state - .hub - .publish_list_changed(list_id, revision) - .await; + state.hub.publish_list_changed(list_id, revision).await; let access = require_access(&state, list_id).await?; let items = state.db.items(list_id).await?; @@ -631,10 +639,7 @@ async fn invitation_page( Path(token): Path, headers: HeaderMap, ) -> Result { - let info = state - .db - .invitation(token.clone()) - .await?; + let info = state.db.invitation(token.clone()).await?; if !info { return Err(AppError::NotFound); } @@ -655,10 +660,7 @@ async fn accept_invitation( LoggedForm(form): LoggedForm, ) -> Result { verify_csrf(&user, &form.csrf)?; - state - .db - .accept_invitation(token) - .await?; + state.db.accept_invitation(token).await?; Ok(Redirect::to("/lists").into_response()) } @@ -817,10 +819,7 @@ async fn list_fragment_response( ))) } -async fn require_access( - state: &AppState, - list_id: i64, -) -> Result { +async fn require_access(state: &AppState, list_id: i64) -> Result { state .db .list_access(list_id)