Skip to content

Commit 0341bcc

Browse files
committed
Implement service startup/shutdown timeout (#33)
- Add hardcoded timeout of 10 seconds on service startup - Add hardcoded timeout of 10 seconds on service shutdown - Remove timeout implementation of Discord service, as it is now handled by the Service Manager.
1 parent 844b681 commit 0341bcc

File tree

4 files changed

+63
-49
lines changed

4 files changed

+63
-49
lines changed

src/config.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::{
44
fmt::{Display, Formatter},
55
fs, io,
66
path::PathBuf,
7-
time::Duration,
87
};
98
use thiserror::Error;
109

@@ -38,23 +37,16 @@ fn discord_token_default() -> String {
3837
String::from("Please provide a token")
3938
}
4039

41-
fn discord_timeout_default() -> Duration {
42-
Duration::from_secs(10)
43-
}
44-
4540
#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize, Clone)]
4641
pub struct Config {
4742
#[serde(rename = "discordToken", default = "discord_token_default")]
4843
pub discord_token: String,
49-
#[serde(rename = "discordTimeout", default = "discord_timeout_default")]
50-
pub discord_timeout: Duration,
5144
}
5245

5346
impl Default for Config {
5447
fn default() -> Self {
5548
Config {
5649
discord_token: discord_token_default(),
57-
discord_timeout: discord_timeout_default(),
5850
}
5951
}
6052
}

src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ fn initialize_services(config: &Config) -> Vec<Arc<RwLock<dyn Service>>> {
5454
//TODO: Add services
5555
//...
5656

57-
let discord_service =
58-
DiscordService::new(config.discord_token.as_str(), config.discord_timeout);
57+
let discord_service = DiscordService::new(config.discord_token.as_str());
5958

6059
vec![Arc::new(RwLock::new(discord_service))]
6160
}

src/service.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use std::{
1010
mem,
1111
pin::Pin,
1212
sync::Arc,
13+
time::Duration,
1314
};
14-
use tokio::sync::RwLock;
15+
use tokio::{sync::RwLock, time::timeout};
1516

1617
use crate::setlock::SetLock;
1718

@@ -290,16 +291,35 @@ impl ServiceManager {
290291
drop(status);
291292

292293
let service_manager = Arc::clone(self.arc.read().await.unwrap());
293-
match service.start(service_manager).await {
294-
Ok(()) => {
295-
info!("Started service: {}", service.info().name);
296-
service.info().set_status(Status::Started).await;
297-
}
294+
295+
let start = service.start(service_manager);
296+
297+
let duration = Duration::from_secs(10); //TODO: Add to config instead of hardcoding
298+
let timeout_result = timeout(duration, start);
299+
300+
match timeout_result.await {
301+
Ok(start_result) => match start_result {
302+
Ok(()) => {
303+
info!("Started service: {}", service.info().name);
304+
service.info().set_status(Status::Started).await;
305+
}
306+
Err(error) => {
307+
error!("Failed to start service {}: {}", service.info().name, error);
308+
service
309+
.info()
310+
.set_status(Status::FailedToStart(error))
311+
.await;
312+
}
313+
},
298314
Err(error) => {
299-
error!("Failed to start service {}: {}", service.info().name, error);
315+
error!(
316+
"Failed to start service {}: Timeout of {} seconds reached.",
317+
service.info().name,
318+
duration.as_secs()
319+
);
300320
service
301321
.info()
302-
.set_status(Status::FailedToStart(error))
322+
.set_status(Status::FailedToStart(Box::new(error)))
303323
.await;
304324
}
305325
}
@@ -329,14 +349,32 @@ impl ServiceManager {
329349
*status = Status::Stopping;
330350
drop(status);
331351

332-
match service.stop().await {
333-
Ok(()) => {
334-
info!("Stopped service: {}", service.info().name);
335-
service.info().set_status(Status::Stopped).await;
336-
}
352+
let stop = service.stop();
353+
354+
let duration = Duration::from_secs(10); //TODO: Add to config instead of hardcoding
355+
let timeout_result = timeout(duration, stop);
356+
357+
match timeout_result.await {
358+
Ok(stop_result) => match stop_result {
359+
Ok(()) => {
360+
info!("Stopped service: {}", service.info().name);
361+
service.info().set_status(Status::Stopped).await;
362+
}
363+
Err(error) => {
364+
error!("Failed to stop service {}: {}", service.info().name, error);
365+
service.info().set_status(Status::FailedToStop(error)).await;
366+
}
367+
},
337368
Err(error) => {
338-
error!("Failed to stop service {}: {}", service.info().name, error);
339-
service.info().set_status(Status::FailedToStop(error)).await;
369+
error!(
370+
"Failed to stop service {}: Timeout of {} seconds reached.",
371+
service.info().name,
372+
duration.as_secs()
373+
);
374+
service
375+
.info()
376+
.set_status(Status::FailedToStop(Box::new(error)))
377+
.await;
340378
}
341379
}
342380
}

src/service/discord.rs

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ use serenity::{
1414
};
1515
use std::{sync::Arc, time::Duration};
1616
use tokio::{
17-
spawn,
17+
select, spawn,
1818
sync::{Mutex, Notify, RwLock},
1919
task::JoinHandle,
20-
time::{sleep, timeout},
20+
time::sleep,
2121
};
2222

2323
pub struct DiscordService {
2424
info: ServiceInfo,
2525
discord_token: String,
26-
connection_timeout: Duration,
2726
pub ready: Arc<RwLock<SetLock<Ready>>>,
2827
client_handle: Option<JoinHandle<Result<(), Error>>>,
2928
pub cache: SetLock<Arc<Cache>>,
@@ -35,11 +34,10 @@ pub struct DiscordService {
3534
}
3635

3736
impl DiscordService {
38-
pub fn new(discord_token: &str, connection_timeout: Duration) -> Self {
37+
pub fn new(discord_token: &str) -> Self {
3938
Self {
4039
info: ServiceInfo::new("lum_builtin_discord", "Discord", Priority::Essential),
4140
discord_token: discord_token.to_string(),
42-
connection_timeout,
4341
ready: Arc::new(RwLock::new(SetLock::new())),
4442
client_handle: None,
4543
cache: SetLock::new(),
@@ -101,29 +99,16 @@ impl Service for DiscordService {
10199
info!("Connecting to Discord");
102100
let client_handle = spawn(async move { client.start().await });
103101

104-
// This prevents waiting for the timeout if the client fails immediately
105-
// TODO: Optimize this, as it will currently add 1000mqs to the startup time
106-
sleep(Duration::from_secs(1)).await;
102+
select! {
103+
_ = client_ready_notify.notified() => {},
104+
_ = sleep(Duration::from_secs(2)) => {},
105+
}
106+
107107
if client_handle.is_finished() {
108108
client_handle.await??;
109-
return Err("Discord client stopped unexpectedly and with no error".into());
109+
return Err("Discord client stopped unexpectedly".into());
110110
}
111111

112-
if timeout(self.connection_timeout, client_ready_notify.notified())
113-
.await
114-
.is_err()
115-
{
116-
client_handle.abort();
117-
let result = convert_thread_result(client_handle).await;
118-
result?;
119-
120-
return Err(format!(
121-
"Discord client failed to connect within {} seconds",
122-
self.connection_timeout.as_secs()
123-
)
124-
.into());
125-
};
126-
127112
self.client_handle = Some(client_handle);
128113
Ok(())
129114
})

0 commit comments

Comments
 (0)