mirror of
https://github.com/Xevion/banner.git
synced 2025-12-06 07:14:21 -06:00
feat: use anyhow, refactor services & coordinator out of main.rs
This commit is contained in:
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -47,6 +47,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -161,6 +167,7 @@ dependencies = [
|
|||||||
name = "banner"
|
name = "banner"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"diesel",
|
"diesel",
|
||||||
@@ -173,6 +180,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
|
"thiserror 2.0.16",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -2560,7 +2568,16 @@ version = "1.0.69"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 2.0.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2574,6 +2591,17 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
@@ -2902,7 +2930,7 @@ dependencies = [
|
|||||||
"rustls 0.22.4",
|
"rustls 0.22.4",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"url",
|
"url",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -19,3 +19,5 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
|||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
anyhow = "1.0.99"
|
||||||
|
thiserror = "2.0.16"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub bot_token: String,
|
||||||
|
pub database_url: String,
|
||||||
|
pub redis_url: String,
|
||||||
|
pub banner_base_url: String,
|
||||||
|
pub bot_target_guild: u64,
|
||||||
|
pub bot_app_id: u64,
|
||||||
|
}
|
||||||
|
|||||||
192
src/main.rs
192
src/main.rs
@@ -1,187 +1,19 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
use serenity::all::{ClientBuilder, GatewayIntents};
|
use serenity::all::{ClientBuilder, GatewayIntents};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::{signal, sync::broadcast, task::JoinSet};
|
use tokio::{signal, task::JoinSet};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
use crate::bot::{Data, age};
|
use crate::bot::{Data, age};
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::services::{ServiceResult, bot::BotService, dummy::DummyService, run_service};
|
||||||
|
use crate::shutdown::ShutdownCoordinator;
|
||||||
use figment::{Figment, providers::Env};
|
use figment::{Figment, providers::Env};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
bot_token: String,
|
|
||||||
database_url: String,
|
|
||||||
redis_url: String,
|
|
||||||
banner_base_url: String,
|
|
||||||
bot_target_guild: u64,
|
|
||||||
bot_app_id: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod bot;
|
mod bot;
|
||||||
|
mod config;
|
||||||
#[derive(Debug)]
|
mod services;
|
||||||
enum ServiceResult {
|
mod shutdown;
|
||||||
GracefulShutdown,
|
|
||||||
NormalCompletion,
|
|
||||||
Error(Box<dyn std::error::Error + Send + Sync>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Common trait for all services in the application
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
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<(), Box<dyn std::error::Error + Send + Sync>>;
|
|
||||||
|
|
||||||
/// Gracefully shutdown the service
|
|
||||||
async fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic service runner that handles the lifecycle
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shutdown coordinator for managing graceful shutdown of multiple services
|
|
||||||
struct ShutdownCoordinator {
|
|
||||||
shutdown_tx: broadcast::Sender<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShutdownCoordinator {
|
|
||||||
fn new() -> Self {
|
|
||||||
let (shutdown_tx, _) = broadcast::channel(1);
|
|
||||||
Self { shutdown_tx }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe(&self) -> broadcast::Receiver<()> {
|
|
||||||
self.shutdown_tx.subscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shutdown(&self) {
|
|
||||||
let _ = self.shutdown_tx.send(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discord bot service implementation
|
|
||||||
struct BotService {
|
|
||||||
client: serenity::Client,
|
|
||||||
shard_manager: std::sync::Arc<serenity::gateway::ShardManager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BotService {
|
|
||||||
fn new(client: serenity::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<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
match self.client.start().await {
|
|
||||||
Ok(()) => {
|
|
||||||
warn!(service = "bot", "Stopped early.");
|
|
||||||
Err("bot stopped early".into())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(service = "bot", "Error: {e:?}");
|
|
||||||
Err(e.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
self.shard_manager.shutdown_all().await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dummy service implementation for demonstration
|
|
||||||
struct DummyService {
|
|
||||||
name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DummyService {
|
|
||||||
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<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
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("Service error".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
// Simulate cleanup work
|
|
||||||
tokio::time::sleep(Duration::from_millis(3500)).await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -238,13 +70,13 @@ async fn main() {
|
|||||||
|
|
||||||
// Set up signal handling
|
// Set up signal handling
|
||||||
let signal_handle = {
|
let signal_handle = {
|
||||||
let coordinator = shutdown_coordinator.shutdown_tx.clone();
|
let shutdown_tx = shutdown_coordinator.shutdown_tx();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
signal::ctrl_c()
|
signal::ctrl_c()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to install CTRL+C signal handler");
|
.expect("Failed to install CTRL+C signal handler");
|
||||||
info!("Received CTRL+C, initiating shutdown...");
|
info!("Received CTRL+C, initiating shutdown...");
|
||||||
let _ = coordinator.send(());
|
let _ = shutdown_tx.send(());
|
||||||
ServiceResult::GracefulShutdown
|
ServiceResult::GracefulShutdown
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -259,7 +91,7 @@ async fn main() {
|
|||||||
let mut exit_code = 0;
|
let mut exit_code = 0;
|
||||||
let first_completion = services.join_next().await;
|
let first_completion = services.join_next().await;
|
||||||
|
|
||||||
let service_result = match first_completion {
|
match first_completion {
|
||||||
Some(Ok(Ok(service_result))) => {
|
Some(Ok(Ok(service_result))) => {
|
||||||
// A service completed successfully
|
// A service completed successfully
|
||||||
match &service_result {
|
match &service_result {
|
||||||
@@ -275,22 +107,18 @@ async fn main() {
|
|||||||
exit_code = 1;
|
exit_code = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
service_result
|
|
||||||
}
|
}
|
||||||
Some(Ok(Err(e))) => {
|
Some(Ok(Err(e))) => {
|
||||||
error!("Service task panicked: {e}");
|
error!("Service task panicked: {e}");
|
||||||
exit_code = 1;
|
exit_code = 1;
|
||||||
ServiceResult::Error("Task panic".into())
|
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
error!("JoinSet error: {e}");
|
error!("JoinSet error: {e}");
|
||||||
exit_code = 1;
|
exit_code = 1;
|
||||||
ServiceResult::Error("JoinSet error".into())
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
warn!("No services running");
|
warn!("No services running");
|
||||||
exit_code = 1;
|
exit_code = 1;
|
||||||
ServiceResult::Error("No services".into())
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/shutdown.rs
Normal file
25
src/shutdown.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
/// Shutdown coordinator for managing graceful shutdown of multiple services
|
||||||
|
pub struct ShutdownCoordinator {
|
||||||
|
shutdown_tx: broadcast::Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShutdownCoordinator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (shutdown_tx, _) = broadcast::channel(1);
|
||||||
|
Self { shutdown_tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&self) -> broadcast::Receiver<()> {
|
||||||
|
self.shutdown_tx.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(&self) {
|
||||||
|
let _ = self.shutdown_tx.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown_tx(&self) -> broadcast::Sender<()> {
|
||||||
|
self.shutdown_tx.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user