From 5b5ce1995ab0efb46133a98c28698cedf1f3df8e Mon Sep 17 00:00:00 2001 From: Andy Killorin <37423245+Speedy6451@users.noreply.github.com> Date: Sat, 16 Dec 2023 20:42:47 -0600 Subject: [PATCH] serialize on quit (not happy path anymore) --- server/Cargo.toml | 2 ++ server/Makefile | 13 +++---- server/src/main.rs | 86 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index dabc152..88f40f8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,6 +11,8 @@ axum = "0.7.2" bit-struct = "0.3.2" const_format = "0.2.32" feistel_rs = "0.1.0" +hyper = "1.0.1" +hyper-util = "0.1.1" rstar = "0.11.0" rustmatica = "0.1.1" serde = "1.0.193" diff --git a/server/Makefile b/server/Makefile index d752709..47f2aaa 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,11 +1,9 @@ -GLOBAL_IP=`dig +short @dns.toys -4 ip` - .PHONY: server server-opt -server: surnames.txt names.txt - GLOBAL_IP=$(GLOBAL_IP) cargo run +server: surnames.txt names.txt ipaddr.txt + GLOBAL_IP=$(cat ipaddr.txt) cargo run -server-opt: surnames.txt names.txt - GLOBAL_IP=$(GLOBAL_IP) cargo run --release +server-opt: surnames.txt names.txt ipaddr.txt + GLOBAL_IP=$(cat ipaddr.txt) cargo run --release surnames.txt: curl https://raw.githubusercontent.com/Hyneman/moby-project/672f6bdca054c42d375f065ffee87e8ceba0c242/moby/mwords/21986na.mes |\ @@ -15,3 +13,6 @@ names.txt: curl https://raw.githubusercontent.com/Hyneman/moby-project/672f6bdca054c42d375f065ffee87e8ceba0c242/moby/mwords/3897male.nam > male.txt curl https://raw.githubusercontent.com/Hyneman/moby-project/672f6bdca054c42d375f065ffee87e8ceba0c242/moby/mwords/4946fema.len > female.txt cat male.txt female.txt | sort | uniq > names.txt + +ipaddr.txt: + dig +short @dns.toys -4 ip > ipaddr.txt diff --git a/server/src/main.rs b/server/src/main.rs index 6ff4d6b..e8cbe65 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,18 +2,21 @@ use std::{env::args, sync::Arc, fs, io::ErrorKind}; use axum::{ routing::{get, post}, - Router, extract::{State, Path}, Json, + Router, extract::{State, Path}, Json, http::{request, Request}, }; -use anyhow::Error; +use anyhow::{Error, Ok}; use rstar; use rustmatica::{BlockState, util::{UVec3, Vec3}}; mod names; use names::Name; use serde_json::Value; -use tokio::sync::{Mutex, RwLock}; +use tokio::{sync::{Mutex, RwLock, watch}, signal}; use serde::{Serialize, Deserialize}; use const_format::formatcp; +use hyper_util::rt::TokioIo; +use tower::Service; +use hyper::body::Incoming; #[derive(Serialize, Deserialize)] enum Direction { @@ -79,11 +82,12 @@ type SharedControl = Arc>; #[tokio::main] async fn main() -> Result<(), Error> { - let state = match fs::File::open("state.json") { - Ok(file) => { - serde_json::from_reader(file)? + + let state = match tokio::fs::OpenOptions::new().read(true).open("state.json").await { + tokio::io::Result::Ok(file) => { + serde_json::from_reader(file.into_std().await)? }, - Err(e) => match e.kind() { + tokio::io::Result::Err(e) => match e.kind() { ErrorKind::NotFound => { ControlState { turtles: Vec::new() } }, @@ -97,15 +101,77 @@ async fn main() -> Result<(), Error> { .route("/turtle/new", post(create_turtle)) .route("/turtle/update/:id", post(command)) .route("/turtle/client.lua", get(client)) - .with_state(state); + .with_state(state.clone()); let listener = tokio::net::TcpListener::bind("0.0.0.0:48228").await.unwrap(); - axum::serve(listener, serv).await.unwrap(); + let (close_tx, close_rx) = watch::channel(()); + + loop { + let (socket, remote_addr) = tokio::select! { + result = listener.accept() => { + result.unwrap() + } + _ = shutdown_signal() => { + println!("cancelled connection"); + break; + } + }; + + let tower = serv.clone(); + let close_rx = close_rx.clone(); + + tokio::spawn(async move { + let socket = TokioIo::new(socket); + let hyper_service = hyper::service::service_fn(move |request: Request| { + tower.clone().call(request) + }); + + let conn = hyper::server::conn::http1::Builder::new() + .serve_connection(socket, hyper_service) + .with_upgrades(); // future + + let mut conn = std::pin::pin!(conn); + + loop { + tokio::select! { + result = conn.as_mut() => { + if result.is_err() { + println!("req failed"); + } + break; + } + _ = shutdown_signal() => { + println!("starting shutdown"); + conn.as_mut().graceful_shutdown(); + } + } + } + + drop(close_rx); + }); + }; + + write_to_disk(state).await?; Ok(()) } +async fn write_to_disk(state: SharedControl) -> anyhow::Result<()> { + let json = serde_json::to_string_pretty(&(*state.read().await))?; + tokio::fs::write("state.json", json).await?; + Ok(()) +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await.unwrap(); + }; + + ctrl_c.await +} + async fn create_turtle( State(state): State, Json(req): Json, @@ -173,5 +239,5 @@ struct TurtleResponse { } async fn client() -> &'static str { - formatcp!("local ipaddr = {}\n{}", env!("GLOBAL_IP"), include_str!("../../client/client.lua")) + formatcp!("local ipaddr = {}\n{}", include_str!("../ipaddr.txt"), include_str!("../../client/client.lua")) }