Skip to content

Commit 196201c

Browse files
aster-voidclaude
andcommitted
treewide: fix error handling and dropped handles
- Replace `.unwrap()` with `.context()` for UTF-8 path conversion - Store and properly manage auto-sync task handle with graceful shutdown - Store tick timer handle in Scheduler actor, abort on stop - Add error logging for JobCompleted message send failures - Add cleanup of all running job handles on actor shutdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent d7eacdc commit 196201c

File tree

2 files changed

+35
-10
lines changed

2 files changed

+35
-10
lines changed

src/main.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod config;
22
mod git;
33
mod scheduler;
44

5-
use anyhow::Result;
5+
use anyhow::{Context, Result};
66
use clap::Parser;
77
use scheduler::{ConfigUpdate, Scheduler, SyncRequest};
88
use std::path::PathBuf;
@@ -32,7 +32,7 @@ async fn main() -> Result<()> {
3232
PathBuf::from(&args.repo)
3333
.canonicalize()?
3434
.to_str()
35-
.unwrap()
35+
.context("Path contains invalid UTF-8")?
3636
.to_string()
3737
} else {
3838
args.repo.clone()
@@ -63,7 +63,7 @@ async fn main() -> Result<()> {
6363
let source_clone = source.clone();
6464
let pull_interval = args.pull_interval;
6565
let scheduler_clone = scheduler.clone();
66-
tokio::spawn(async move {
66+
let sync_handle = tokio::spawn(async move {
6767
let mut ticker = interval(Duration::from_secs(pull_interval));
6868
loop {
6969
ticker.tick().await;
@@ -106,9 +106,18 @@ async fn main() -> Result<()> {
106106
}
107107
});
108108

109-
// Keep main alive while scheduler runs
110-
// The scheduler actor runs indefinitely with its internal ticker
111-
std::future::pending::<()>().await;
109+
// Wait for shutdown signal or task panic
110+
tokio::select! {
111+
_ = tokio::signal::ctrl_c() => {
112+
println!("[rollcron] Shutting down...");
113+
}
114+
result = sync_handle => {
115+
match result {
116+
Ok(()) => eprintln!("[rollcron] Sync task exited unexpectedly"),
117+
Err(e) => eprintln!("[rollcron] Sync task panicked: {}", e),
118+
}
119+
}
120+
}
112121

113122
Ok(())
114123
}

src/scheduler/mod.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Scheduler {
2020
runner: RunnerConfig,
2121
pending_syncs: HashSet<String>,
2222
job_handles: HashMap<String, Vec<JoinHandle<()>>>,
23+
tick_handle: Option<JoinHandle<()>>,
2324
}
2425

2526
impl Scheduler {
@@ -30,6 +31,7 @@ impl Scheduler {
3031
runner,
3132
pending_syncs: HashSet::new(),
3233
job_handles: HashMap::new(),
34+
tick_handle: None,
3335
}
3436
}
3537

@@ -63,19 +65,30 @@ impl Actor for Scheduler {
6365

6466
async fn started(&mut self, mailbox: &Mailbox<Self>) -> Result<(), Self::Stop> {
6567
let addr = mailbox.address();
66-
tokio::spawn(async move {
68+
self.tick_handle = Some(tokio::spawn(async move {
6769
let mut interval = tokio::time::interval(Duration::from_secs(1));
6870
loop {
6971
interval.tick().await;
7072
if addr.send(Tick).await.is_err() {
7173
break;
7274
}
7375
}
74-
});
76+
}));
7577
Ok(())
7678
}
7779

78-
async fn stopped(self) -> Self::Stop {}
80+
async fn stopped(mut self) -> Self::Stop {
81+
// Abort tick timer
82+
if let Some(handle) = self.tick_handle.take() {
83+
handle.abort();
84+
}
85+
// Abort all running jobs
86+
for (_, handles) in self.job_handles.drain() {
87+
for handle in handles {
88+
handle.abort();
89+
}
90+
}
91+
}
7992
}
8093

8194
// === Messages ===
@@ -205,11 +218,14 @@ impl Scheduler {
205218

206219
fn spawn_job(&mut self, job: Job, addr: Address<Scheduler, Weak>) {
207220
let job_id = job.id.clone();
221+
let job_id_for_log = job.id.clone();
208222
let work_dir = resolve_work_dir(&self.sot_path, &job.id, &job.working_dir);
209223

210224
let handle = tokio::spawn(async move {
211225
execute_job(&job, &work_dir).await;
212-
let _ = addr.send(JobCompleted).await;
226+
if let Err(e) = addr.send(JobCompleted).await {
227+
eprintln!("[job:{}] Failed to notify completion: {}", job_id_for_log, e);
228+
}
213229
});
214230

215231
self.job_handles.entry(job_id).or_default().push(handle);

0 commit comments

Comments
 (0)