Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/sigma-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ thiserror.workspace = true
tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
2 changes: 1 addition & 1 deletion crates/sigma-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl CurrentHeight {
}

/// Converts the current height and another height into a [`Height`] [`Range`].
/// Not the range is exclusive but the other height is included.
/// Note: the range is exclusive but the other height is included.
pub fn to_range(&self, other: &Self) -> Range<Height> {
match (self, other) {
(CurrentHeight::Empty, CurrentHeight::Empty) => 0..0,
Expand Down
60 changes: 50 additions & 10 deletions crates/sigma-api/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ pub type ServeConnError = Box<dyn std::error::Error + Send + Sync>;
/// This constructs a new `JoinSet` to use for limiting connections and then
/// calls [`serve_next_conn`] in a loop. Any outstanding connections will not be
/// counted toward the connection limit.
#[tracing::instrument(
name = "sigma-api::serve",
level = "info",
skip(router, listener, conn_limit)
)]
pub async fn serve(router: &Router, listener: &TcpListener, conn_limit: usize) {
let mut conn_set = JoinSet::new();
loop {
tracing::info!("Waiting for next connection");
serve_next_conn(router, listener, conn_limit, &mut conn_set).await;
}
}
Expand All @@ -38,23 +44,44 @@ pub async fn serve(router: &Router, listener: &TcpListener, conn_limit: usize) {
///
/// If we're at the connection limit, this first awaits for a connection task to
/// become available.

#[tracing::instrument(
name = "sigma_api::serve_next_conn",
level = "info",
skip(router, listener, conn_set),
fields(conn_limit)
)]
pub async fn serve_next_conn(
router: &Router,
listener: &TcpListener,
conn_limit: usize,
conn_set: &mut JoinSet<()>,
) {
tracing::debug!("Waiting for next connection");

// Await the next connection.
let stream = match next_conn(listener, conn_limit, conn_set).await {
Ok((stream, _remote_addr)) => stream,
Err(_err) => {
Ok((stream, remote_addr)) => {
tracing::info!(%remote_addr, "Connection accepted");
stream
}
Err(err) => {
tracing::error!(%err, "Failed to accept connection");
return;
}
};

// Serve the acquired connection.
let router = router.clone();
conn_set.spawn(async move { if let Err(_err) = serve_conn(&router, stream).await {} });
conn_set.spawn(async move {
if let Err(err) = serve_conn(&router, stream).await {
tracing::error!(%err, "Connection handler exited with error");
} else {
tracing::debug!("Connection handler completed successfully");
}
});

tracing::debug!("Connection task spawned");
}

/// Accept and return the next TCP stream connection.
Expand All @@ -68,31 +95,44 @@ pub async fn next_conn(
) -> io::Result<(TcpStream, SocketAddr)> {
// If the `conn_set` size currently exceeds the limit, wait for the next to join.
if conn_set.len() >= conn_limit {
tracing::debug!("Connection limit reached, waiting for a connection to finish");
conn_set.join_next().await.expect("set cannot be empty")?;
}
// Await another connection.
listener.accept().await
}

/// Serve a newly accepted TCP stream.
#[tracing::instrument(
name = "sigma_api::serve_conn",
level = "info",
skip(router, stream),
fields(peer_addr = ?stream.peer_addr().ok())
)]
pub async fn serve_conn(router: &Router, stream: TcpStream) -> Result<(), ServeConnError> {
// Hyper has its own `AsyncRead` and `AsyncWrite` traits and doesn't use
// tokio. `TokioIo` converts between them.
tracing::info!("Starting to serve connection");

let stream = hyper_util::rt::TokioIo::new(stream);

// Hyper also has its own `Service` trait and doesn't use tower. We can use
// `hyper::service::service_fn` to create a hyper `Service` that calls our
// app through `tower::Service::call`.
let hyper_service = hyper::service::service_fn(
move |request: axum::extract::Request<hyper::body::Incoming>| {
tower::Service::call(&mut router.clone(), request)
},
);

// `TokioExecutor` tells hyper to use `tokio::spawn` to spawn tasks.
let executor = hyper_util::rt::TokioExecutor::new();
let conn = hyper_util::server::conn::auto::Builder::new(executor).http2_only();
conn.serve_connection(stream, hyper_service).await

match conn.serve_connection(stream, hyper_service).await {
Ok(()) => {
tracing::info!("Connection served successfully");
Ok(())
}
Err(e) => {
tracing::error!(error = %e, "Failed to serve connection");
Err(e)
}
}
}

/// Construct the endpoint router with the node [`endpoint`]s, CORS layer and DB
Expand Down
Loading