Skip to content

Commit fcb62cd

Browse files
committed
Fix service framework (#31)
- Fix deadlock when accessing the ServiceManager parameter in start() method of a Service - Known bug: Deadlock still happens when a service accesses itself through the ServiceManager on start
1 parent f027bc9 commit fcb62cd

File tree

4 files changed

+27
-42
lines changed

4 files changed

+27
-42
lines changed

src/bot.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl BotBuilder {
4141

4242
pub struct Bot {
4343
pub name: String,
44-
pub service_manager: Arc<RwLock<ServiceManager>>,
44+
pub service_manager: Arc<ServiceManager>,
4545
}
4646

4747
impl Bot {
@@ -52,15 +52,15 @@ impl Bot {
5252
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
5353
pub fn start(&mut self) -> PinnedBoxedFuture<'_, ()> {
5454
Box::pin(async move {
55-
self.service_manager.write().await.start_services().await;
55+
self.service_manager.start_services().await;
5656
//TODO: Potential for further initialization here, like modules
5757
})
5858
}
5959

6060
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
6161
pub fn stop(&mut self) -> PinnedBoxedFuture<'_, ()> {
6262
Box::pin(async move {
63-
self.service_manager.write().await.stop_services().await;
63+
self.service_manager.stop_services().await;
6464
//TODO: Potential for further deinitialization here, like modules
6565
})
6666
}

src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ pub async fn run(mut bot: Bot) {
3636
}
3737
};
3838

39-
let service_manager = bot.service_manager.read().await;
40-
if service_manager.overall_status().await != OverallStatus::Healthy {
41-
let status_tree = service_manager.status_tree().await;
39+
if bot.service_manager.overall_status().await != OverallStatus::Healthy {
40+
let status_tree = bot.service_manager.status_tree().await;
4241

4342
error!("{} is not healthy! Some essential services did not start up successfully. Please check the logs.\nService status tree:\n{}\n{} will exit.",
4443
bot.name,
4544
status_tree,
4645
bot.name);
4746
return;
4847
}
49-
drop(service_manager);
5048

5149
info!("{} is alive", bot.name,);
5250

src/service.rs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use crate::setlock::SetLock;
1717

1818
pub mod discord;
1919

20-
pub type PinnedBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
20+
pub type BoxedFuture<'a, T> = Box<dyn Future<Output = T> + 'a>;
21+
pub type BoxedFutureResult<'a, T> = BoxedFuture<'a, Result<T, Box<dyn Error + Send + Sync>>>;
2122

23+
pub type PinnedBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
2224
pub type PinnedBoxedFutureResult<'a, T> =
2325
PinnedBoxedFuture<'a, Result<T, Box<dyn Error + Send + Sync>>>;
2426

@@ -28,8 +30,8 @@ pub enum Status {
2830
Stopped,
2931
Starting,
3032
Stopping,
31-
FailedStarting(Box<dyn Error + Send + Sync>),
32-
FailedStopping(Box<dyn Error + Send + Sync>),
33+
FailedToStart(Box<dyn Error + Send + Sync>), //TODO: Test out if it'd be better to use a String instead
34+
FailedToStop(Box<dyn Error + Send + Sync>),
3335
RuntimeError(Box<dyn Error + Send + Sync>),
3436
}
3537

@@ -40,8 +42,8 @@ impl Display for Status {
4042
Status::Stopped => write!(f, "Stopped"),
4143
Status::Starting => write!(f, "Starting"),
4244
Status::Stopping => write!(f, "Stopping"),
43-
Status::FailedStarting(error) => write!(f, "Failed to start: {}", error),
44-
Status::FailedStopping(error) => write!(f, "Failed to stop: {}", error),
45+
Status::FailedToStart(error) => write!(f, "Failed to start: {}", error),
46+
Status::FailedToStop(error) => write!(f, "Failed to stop: {}", error),
4547
Status::RuntimeError(error) => write!(f, "Runtime error: {}", error),
4648
}
4749
}
@@ -55,8 +57,8 @@ impl PartialEq for Status {
5557
| (Status::Stopped, Status::Stopped)
5658
| (Status::Starting, Status::Starting)
5759
| (Status::Stopping, Status::Stopping)
58-
| (Status::FailedStarting(_), Status::FailedStarting(_))
59-
| (Status::FailedStopping(_), Status::FailedStopping(_))
60+
| (Status::FailedToStart(_), Status::FailedToStart(_))
61+
| (Status::FailedToStop(_), Status::FailedToStop(_))
6062
| (Status::RuntimeError(_), Status::RuntimeError(_))
6163
)
6264
}
@@ -146,19 +148,13 @@ impl Hash for ServiceInfo {
146148
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a PinnedBoxedFutureResult
147149
pub trait Service: DowncastSync {
148150
fn info(&self) -> &ServiceInfo;
149-
fn start(
150-
&mut self,
151-
service_manager: Arc<RwLock<ServiceManager>>,
152-
) -> PinnedBoxedFutureResult<'_, ()>;
151+
fn start(&mut self, service_manager: Arc<ServiceManager>) -> PinnedBoxedFutureResult<'_, ()>;
153152
fn stop(&mut self) -> PinnedBoxedFutureResult<'_, ()>;
154153

155154
// Used for downcasting in get_service method of ServiceManager
156155
//fn as_any_arc(&self) -> Arc<dyn Any + Send + Sync>;
157156

158-
fn wrapped_start(
159-
&mut self,
160-
service_manager: Arc<RwLock<ServiceManager>>,
161-
) -> PinnedBoxedFuture<()> {
157+
fn wrapped_start(&mut self, service_manager: Arc<ServiceManager>) -> PinnedBoxedFuture<()> {
162158
Box::pin(async move {
163159
let mut status = self.info().status.write().await;
164160

@@ -181,7 +177,7 @@ pub trait Service: DowncastSync {
181177
}
182178
Err(error) => {
183179
error!("Failed to start service {}: {}", self.info().name, error);
184-
self.info().set_status(Status::FailedStarting(error)).await;
180+
self.info().set_status(Status::FailedToStart(error)).await;
185181
}
186182
}
187183
})
@@ -210,7 +206,7 @@ pub trait Service: DowncastSync {
210206
}
211207
Err(error) => {
212208
error!("Failed to stop service {}: {}", self.info().name, error);
213-
self.info().set_status(Status::FailedStopping(error)).await;
209+
self.info().set_status(Status::FailedToStop(error)).await;
214210
}
215211
}
216212
})
@@ -288,22 +284,15 @@ impl ServiceManagerBuilder {
288284
self
289285
}
290286

291-
pub async fn build(self) -> Arc<RwLock<ServiceManager>> {
287+
pub async fn build(self) -> Arc<ServiceManager> {
292288
let service_manager = ServiceManager {
293289
services: self.services,
294290
arc: RwLock::new(SetLock::new()),
295291
};
296292

297-
let self_arc = Arc::new(RwLock::new(service_manager));
293+
let self_arc = Arc::new(service_manager);
298294

299-
match self_arc
300-
.write()
301-
.await
302-
.arc
303-
.write()
304-
.await
305-
.set(Arc::clone(&self_arc))
306-
{
295+
match self_arc.arc.write().await.set(Arc::clone(&self_arc)) {
307296
Ok(()) => {}
308297
Err(err) => {
309298
panic!(
@@ -319,7 +308,7 @@ impl ServiceManagerBuilder {
319308

320309
pub struct ServiceManager {
321310
pub services: Vec<Arc<RwLock<dyn Service>>>,
322-
arc: RwLock<SetLock<Arc<RwLock<Self>>>>,
311+
arc: RwLock<SetLock<Arc<Self>>>,
323312
}
324313

325314
impl ServiceManager {
@@ -418,8 +407,8 @@ impl ServiceManager {
418407
.push_str(&format!(" - {}: {}\n", info.name, status));
419408
}
420409
},
421-
Status::FailedStarting(_)
422-
| Status::FailedStopping(_)
410+
Status::FailedToStart(_)
411+
| Status::FailedToStop(_)
423412
| Status::RuntimeError(_) => match priority {
424413
Priority::Essential => {
425414
failed_essentials.push_str(&format!(" - {}: {}\n", info.name, status));

src/service/discord.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use serenity::{
1414
};
1515
use std::{sync::Arc, time::Duration};
1616
use tokio::{
17+
spawn,
1718
sync::{Mutex, Notify, RwLock},
1819
task::JoinHandle,
1920
time::{sleep, timeout},
@@ -56,10 +57,7 @@ impl Service for DiscordService {
5657
&self.info
5758
}
5859

59-
fn start(
60-
&mut self,
61-
_service_manager: Arc<RwLock<ServiceManager>>,
62-
) -> PinnedBoxedFutureResult<'_, ()> {
60+
fn start(&mut self, _service_manager: Arc<ServiceManager>) -> PinnedBoxedFutureResult<'_, ()> {
6361
Box::pin(async move {
6462
let framework = StandardFramework::new();
6563
framework.configure(Configuration::new().prefix("!"));
@@ -101,7 +99,7 @@ impl Service for DiscordService {
10199
}
102100

103101
info!("Connecting to Discord");
104-
let client_handle = tokio::spawn(async move { client.start().await });
102+
let client_handle = spawn(async move { client.start().await });
105103

106104
// This prevents waiting for the timeout if the client fails immediately
107105
// TODO: Optimize this, as it will currently add 1000mqs to the startup time

0 commit comments

Comments
 (0)