From b1ba23c32e27a8f7d82f62321330a4454353c291 Mon Sep 17 00:00:00 2001 From: Tim Vilgot Mikael Fredenberg Date: Tue, 13 Jan 2026 08:02:49 +0100 Subject: [PATCH 1/2] make the event handler an async fn This works so long as the handler's future does not capture the shard. --- src/dispatch.rs | 31 ++++++++----------------------- src/main.rs | 22 +++++++++++++--------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/dispatch.rs b/src/dispatch.rs index 1cdf5aa..f72cd48 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -11,7 +11,7 @@ pub enum ShardRestartResult { } impl ShardRestartResult { - pub fn is_forced(&self) -> bool { + pub fn is_forced(self) -> bool { matches!(self, Self::ForcedRestart) } } @@ -90,24 +90,12 @@ impl State { } } -pub struct Dispatcher<'a> { - #[allow(dead_code)] - pub shard: &'a mut Shard, - tracker: &'a TaskTracker, -} - -impl<'a> Dispatcher<'a> { - fn new(shard: &'a mut Shard, tracker: &'a TaskTracker) -> Self { - Self { shard, tracker } - } - - pub fn dispatch(self, future: impl Future + Send + 'static) { - self.tracker.spawn(future); - } -} - #[tracing::instrument(name = "dispatcher", fields(shard.id = shard.id().number()), skip_all)] -pub async fn run(mut shard: Shard, mut event_handler: impl FnMut(Dispatcher, Event)) -> ResumeInfo { +pub async fn run(mut shard: Shard, mut event_handler: H) -> ResumeInfo +where + H: FnMut(Event, &mut Shard) -> Fut, + Fut: Future + Send + 'static, +{ let mut receiver = ShardHandle::insert(shard.id()); let mut shutdown = pin!(signal::ctrl_c()); let tracker = TaskTracker::new(); @@ -132,11 +120,8 @@ pub async fn run(mut shard: Shard, mut event_handler: impl FnMut(Dispatcher, Eve event = shard.next_event(EVENT_TYPES) => { match event { Some(Ok(Event::GatewayClose(_))) if !state.is_active() => break, - Some(Ok(event)) => event_handler(Dispatcher::new(&mut shard, &tracker), event), - Some(Err(error)) => { - tracing::warn!(error = &error as &dyn Error, "shard failed to receive an event"); - continue; - } + Some(Ok(event)) => _ = tracker.spawn(event_handler(event, &mut shard)), + Some(Err(error)) => tracing::warn!(error = &error as &dyn Error, "shard failed to receive an event"), None => break, } } diff --git a/src/main.rs b/src/main.rs index da44a43..9234962 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,9 @@ use dashmap::DashMap; use std::{env, pin::pin, time::Duration}; use tokio::signal; use tracing::{Instrument as _, instrument::Instrumented}; -use twilight_gateway::{ConfigBuilder, Event, EventTypeFlags, Intents, queue::InMemoryQueue}; +use twilight_gateway::{ + ConfigBuilder, Event, EventTypeFlags, Intents, Shard, queue::InMemoryQueue, +}; use twilight_http::Client; use twilight_model::id::{ Id, @@ -66,7 +68,7 @@ async fn main() -> anyhow::Result<()> { let tasks = shards .into_iter() - .map(|shard| tokio::spawn(dispatch::run(shard, event_handler))) + .map(|shard| tokio::spawn(dispatch::run(shard, handler))) .collect::>(); signal::ctrl_c().await?; @@ -92,7 +94,7 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn event_handler(dispatcher: dispatch::Dispatcher, event: Event) { +fn handler(event: Event, _shard: &mut Shard) -> impl Future + use<> { async fn log_err(future: Instrumented>>) { let mut future = pin!(future); if let Err(error) = future.as_mut().await { @@ -101,12 +103,14 @@ fn event_handler(dispatcher: dispatch::Dispatcher, event: Event) { } } - #[allow(clippy::single_match)] - match event { - Event::InteractionCreate(event) => { - let span = tracing::info_span!(parent: None, "interaction", id = %event.id); - dispatcher.dispatch(log_err(command::interaction(event).instrument(span))) + async { + #[allow(clippy::single_match)] + match event { + Event::InteractionCreate(event) => { + let span = tracing::info_span!("interaction", id = %event.id); + log_err(command::interaction(event).instrument(span)).await; + } + _ => {} } - _ => {} } } From 90e451148de1134e4b551d585ddcf374525640b3 Mon Sep 17 00:00:00 2001 From: Tim Vilgot Mikael Fredenberg Date: Tue, 13 Jan 2026 09:29:16 +0100 Subject: [PATCH 2/2] make the event handler a async fn x2 It's much prettier for the event handler to be an `async fn` and, following the separation of concerns principle, have it be injected state through a separate provider. --- src/dispatch.rs | 14 +++++++------- src/main.rs | 24 ++++++++++-------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/dispatch.rs b/src/dispatch.rs index f72cd48..1b7c863 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -91,11 +91,11 @@ impl State { } #[tracing::instrument(name = "dispatcher", fields(shard.id = shard.id().number()), skip_all)] -pub async fn run(mut shard: Shard, mut event_handler: H) -> ResumeInfo -where - H: FnMut(Event, &mut Shard) -> Fut, - Fut: Future + Send + 'static, -{ +pub async fn run + Send + 'static, S>( + mut event_handler: impl FnMut(Event, S) -> Fut, + mut shard: Shard, + mut state_provider: impl FnMut(&mut Shard) -> S, +) -> ResumeInfo { let mut receiver = ShardHandle::insert(shard.id()); let mut shutdown = pin!(signal::ctrl_c()); let tracker = TaskTracker::new(); @@ -120,8 +120,8 @@ where event = shard.next_event(EVENT_TYPES) => { match event { Some(Ok(Event::GatewayClose(_))) if !state.is_active() => break, - Some(Ok(event)) => _ = tracker.spawn(event_handler(event, &mut shard)), - Some(Err(error)) => tracing::warn!(error = &error as &dyn Error, "shard failed to receive an event"), + Some(Ok(event)) => _ = tracker.spawn(event_handler(event, state_provider(&mut shard))), + Some(Err(error)) => tracing::warn!(error = &error as &dyn Error, "shard failed to receive event"), None => break, } } diff --git a/src/main.rs b/src/main.rs index 9234962..c0f1a53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,9 +14,7 @@ use dashmap::DashMap; use std::{env, pin::pin, time::Duration}; use tokio::signal; use tracing::{Instrument as _, instrument::Instrumented}; -use twilight_gateway::{ - ConfigBuilder, Event, EventTypeFlags, Intents, Shard, queue::InMemoryQueue, -}; +use twilight_gateway::{ConfigBuilder, Event, EventTypeFlags, Intents, queue::InMemoryQueue}; use twilight_http::Client; use twilight_model::id::{ Id, @@ -68,7 +66,7 @@ async fn main() -> anyhow::Result<()> { let tasks = shards .into_iter() - .map(|shard| tokio::spawn(dispatch::run(shard, handler))) + .map(|shard| tokio::spawn(dispatch::run(event_handler, shard, |_shard| ()))) .collect::>(); signal::ctrl_c().await?; @@ -94,23 +92,21 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn handler(event: Event, _shard: &mut Shard) -> impl Future + use<> { +async fn event_handler(event: Event, _state: ()) { async fn log_err(future: Instrumented>>) { let mut future = pin!(future); if let Err(error) = future.as_mut().await { let _enter = future.span().enter(); - tracing::warn!(error = &*error, "event handler failed"); + tracing::warn!(error = &*error, "failed to handle event"); } } - async { - #[allow(clippy::single_match)] - match event { - Event::InteractionCreate(event) => { - let span = tracing::info_span!("interaction", id = %event.id); - log_err(command::interaction(event).instrument(span)).await; - } - _ => {} + #[allow(clippy::single_match)] + match event { + Event::InteractionCreate(event) => { + let span = tracing::info_span!("interaction", id = %event.id); + log_err(command::interaction(event).instrument(span)).await; } + _ => {} } }