mirror of
https://github.com/Xevion/banner.git
synced 2025-12-16 18:11:12 -06:00
feat: use anyhow, refactor services & coordinator out of main.rs
This commit is contained in:
45
src/services/bot.rs
Normal file
45
src/services/bot.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use super::{Service, ServiceResult};
|
||||
use serenity::Client;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, warn};
|
||||
|
||||
/// Discord bot service implementation
|
||||
pub struct BotService {
|
||||
client: Client,
|
||||
shard_manager: Arc<serenity::gateway::ShardManager>,
|
||||
}
|
||||
|
||||
impl BotService {
|
||||
pub fn new(client: Client) -> Self {
|
||||
let shard_manager = client.shard_manager.clone();
|
||||
Self {
|
||||
client,
|
||||
shard_manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Service for BotService {
|
||||
fn name(&self) -> &'static str {
|
||||
"bot"
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), anyhow::Error> {
|
||||
match self.client.start().await {
|
||||
Ok(()) => {
|
||||
warn!(service = "bot", "Stopped early.");
|
||||
Err(anyhow::anyhow!("bot stopped early"))
|
||||
}
|
||||
Err(e) => {
|
||||
error!(service = "bot", "Error: {e:?}");
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn shutdown(&mut self) -> Result<(), anyhow::Error> {
|
||||
self.shard_manager.shutdown_all().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
42
src/services/dummy.rs
Normal file
42
src/services/dummy.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use super::Service;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Dummy service implementation for demonstration
|
||||
pub struct DummyService {
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl DummyService {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Service for DummyService {
|
||||
fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), anyhow::Error> {
|
||||
let mut counter = 0;
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
counter += 1;
|
||||
info!(service = self.name, "Service heartbeat ({counter})");
|
||||
|
||||
// Simulate service failure after 60 seconds for demo
|
||||
if counter >= 6 {
|
||||
error!(service = self.name, "Service encountered an error");
|
||||
return Err(anyhow::anyhow!("Service error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn shutdown(&mut self) -> Result<(), anyhow::Error> {
|
||||
// Simulate cleanup work
|
||||
tokio::time::sleep(Duration::from_millis(6000)).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
69
src/services/mod.rs
Normal file
69
src/services/mod.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use std::time::Duration;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
pub mod bot;
|
||||
pub mod dummy;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServiceResult {
|
||||
GracefulShutdown,
|
||||
NormalCompletion,
|
||||
Error(anyhow::Error),
|
||||
}
|
||||
|
||||
/// Common trait for all services in the application
|
||||
#[async_trait::async_trait]
|
||||
pub trait Service: Send + Sync {
|
||||
/// The name of the service for logging
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Run the service's main work loop
|
||||
async fn run(&mut self) -> Result<(), anyhow::Error>;
|
||||
|
||||
/// Gracefully shutdown the service
|
||||
async fn shutdown(&mut self) -> Result<(), anyhow::Error>;
|
||||
}
|
||||
|
||||
/// Generic service runner that handles the lifecycle
|
||||
pub async fn run_service(
|
||||
mut service: Box<dyn Service>,
|
||||
mut shutdown_rx: broadcast::Receiver<()>,
|
||||
) -> ServiceResult {
|
||||
let name = service.name();
|
||||
info!(service = name, "Service started");
|
||||
|
||||
let work = async {
|
||||
match service.run().await {
|
||||
Ok(()) => {
|
||||
warn!(service = name, "Service completed unexpectedly");
|
||||
ServiceResult::NormalCompletion
|
||||
}
|
||||
Err(e) => {
|
||||
error!(service = name, "Service failed: {e}");
|
||||
ServiceResult::Error(e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
result = work => result,
|
||||
_ = shutdown_rx.recv() => {
|
||||
info!(service = name, "Shutting down...");
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
match service.shutdown().await {
|
||||
Ok(()) => {
|
||||
let elapsed = start_time.elapsed();
|
||||
info!(service = name, "Shutdown completed in {elapsed:.2?}");
|
||||
ServiceResult::GracefulShutdown
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed = start_time.elapsed();
|
||||
error!(service = name, "Shutdown failed after {elapsed:.2?}: {e}");
|
||||
ServiceResult::Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user