[code][fix] improvements for LAN 202503

- more robust client <-> server connection
  - new client event: DownloadGameFilesFailed
  - 3 seconds to reconnect
  - retry forever if server is gone and never lose a UI request

- code cleanup here and there (mostly server)
This commit is contained in:
2025-03-20 19:39:32 +01:00
parent 19434cd1b1
commit 765447e6d1
9 changed files with 405 additions and 239 deletions
+44 -77
View File
@@ -3,12 +3,7 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
use lanspread_db::db::GameDB;
use lanspread_proto::{Message as _, Request};
use lanspread_utils::maybe_addr;
use s2n_quic::{
Connection, Server,
provider::limits::Limits,
stream::{ReceiveStream, SendStream},
};
use tokio::io::AsyncWriteExt as _;
use s2n_quic::{Connection, Server, provider::limits::Limits, stream::BidirectionalStream};
use crate::req::{RequestHandler, send_game_file_data};
@@ -21,92 +16,65 @@ struct ServerCtx {
games_folder: PathBuf,
}
#[derive(Clone, Debug)]
struct ConnectionCtx {
server_ctx: Arc<ServerCtx>,
}
async fn handle_bidi_stream(stream: BidirectionalStream, ctx: Arc<ServerCtx>) -> eyre::Result<()> {
let (mut rx, mut tx) = stream.split();
#[derive(Clone, Debug)]
struct StreamCtx {
conn_ctx: Arc<ConnectionCtx>,
}
async fn handle_bidi_stream(
mut rx: ReceiveStream,
mut tx: SendStream,
ctx: Arc<StreamCtx>,
) -> eyre::Result<()> {
let remote_addr = maybe_addr!(rx.connection().remote_addr());
tracing::trace!("{remote_addr} stream opened");
// handle streams
while let Ok(Some(data)) = rx.receive().await {
tracing::trace!(
"{remote_addr} msg: (raw): {}",
String::from_utf8_lossy(&data)
);
loop {
match rx.receive().await {
Ok(Some(data)) => {
tracing::trace!(
"{remote_addr} msg: (raw): {}",
String::from_utf8_lossy(&data)
);
let request = Request::decode(data);
tracing::debug!("{remote_addr} msg: {request:?}");
let request = Request::decode(data);
tracing::debug!("{remote_addr} msg: {request:?}");
// special case for now (send game file data to client)
if let Request::GetGameFileData(game_file_desc) = &request {
send_game_file_data(
game_file_desc,
&mut tx,
&ctx.conn_ctx.server_ctx.games_folder,
)
.await;
continue;
}
// special case for now (send game file data to client)
if let Request::GetGameFileData(game_file_desc) = &request {
send_game_file_data(game_file_desc, &mut tx, &ctx.games_folder).await;
continue;
}
let response = ctx
.conn_ctx
.server_ctx
.handler
.handle_request(request, &ctx.conn_ctx.server_ctx.games_folder)
.await;
tracing::trace!("{remote_addr} server response: {response:?}");
let raw_response = response.encode();
tracing::trace!(
"{remote_addr} server response (raw): {}",
String::from_utf8_lossy(&raw_response)
);
// write response back to client
if let Err(e) = tx.write_all(&raw_response).await {
tracing::error!(?e);
}
// close the stream
if let Err(e) = tx.close().await {
tracing::error!(?e);
// normal case (handle request)
if let Err(e) = ctx
.handler
.handle_request(request, &ctx.games_folder, &mut tx)
.await
{
tracing::error!(?e, "{remote_addr} error handling request");
}
}
Ok(None) => {
tracing::trace!("{remote_addr} stream closed");
break;
}
Err(e) => {
tracing::error!("{remote_addr} stream error: {e}");
break;
}
}
}
Ok(())
}
async fn handle_connection(
mut connection: Connection,
ctx: Arc<ConnectionCtx>,
) -> eyre::Result<()> {
async fn handle_connection(mut connection: Connection, ctx: Arc<ServerCtx>) -> eyre::Result<()> {
let remote_addr = maybe_addr!(connection.remote_addr());
tracing::info!("{remote_addr} connected");
// handle streams
while let Ok(Some(stream)) = connection.accept_bidirectional_stream().await {
let ctx = ctx.clone();
let remote_addr = remote_addr.clone();
let (rx, tx) = stream.split();
let ctx = Arc::new(StreamCtx {
conn_ctx: ctx.clone(),
});
// spawn a new task for the stream
tokio::spawn(async move {
if let Err(e) = handle_bidi_stream(rx, tx, ctx).await {
if let Err(e) = handle_bidi_stream(stream, ctx).await {
tracing::error!("{remote_addr} stream error: {e}");
}
});
@@ -121,8 +89,8 @@ pub(crate) async fn run_server(
games_folder: PathBuf,
) -> eyre::Result<()> {
let limits = Limits::default()
.with_max_idle_timeout(Duration::ZERO)?
.with_max_handshake_duration(Duration::from_secs(3))?;
.with_max_handshake_duration(Duration::from_secs(3))?
.with_max_idle_timeout(Duration::from_secs(3))?;
let mut server = Server::builder()
.with_tls((CERT_PEM, KEY_PEM))?
@@ -130,23 +98,22 @@ pub(crate) async fn run_server(
.with_limits(limits)?
.start()?;
let server_ctx = Arc::new(ServerCtx {
let ctx = Arc::new(ServerCtx {
handler: RequestHandler::new(db),
games_folder,
});
while let Some(connection) = server.accept().await {
let conn_ctx = Arc::new(ConnectionCtx {
server_ctx: server_ctx.clone(),
});
let ctx = ctx.clone();
// spawn a new task for the connection
tokio::spawn(async move {
if let Err(e) = handle_connection(connection, conn_ctx).await {
if let Err(e) = handle_connection(connection, ctx).await {
tracing::error!("Connection error: {}", e);
}
});
}
tracing::info!("Server shutting down");
Ok(())
}