@@ -243,6 +243,53 @@ impl CronStore {
243243 pub fn get_enabled_jobs ( & self ) -> Vec < & CronJob > {
244244 self . jobs . values ( ) . filter ( |j| j. enabled ) . collect ( )
245245 }
246+
247+ /// Reset any jobs stuck in Running state (e.g., after a crash).
248+ /// Recalculates next_run_at so they get scheduled again.
249+ pub fn recover_stuck_jobs ( & mut self , now_ms : u64 ) -> usize {
250+ let stuck_ids: Vec < JobId > = self
251+ . jobs
252+ . values ( )
253+ . filter ( |j| j. state . last_status == JobStatus :: Running )
254+ . map ( |j| j. id . clone ( ) )
255+ . collect ( ) ;
256+
257+ let count = stuck_ids. len ( ) ;
258+ for id in & stuck_ids {
259+ if let Some ( job) = self . jobs . get_mut ( id) {
260+ job. state . last_status = JobStatus :: Success ;
261+ job. update_next_run ( now_ms) ;
262+ }
263+ }
264+
265+ count
266+ }
267+
268+ /// Merge disk state into the current store, preserving in-flight job states.
269+ /// Jobs currently marked as Running in memory keep their in-memory state
270+ /// to avoid losing completion updates from concurrent tasks.
271+ pub fn merge_from_disk ( & mut self , disk : CronStore ) {
272+ let running_ids: std:: collections:: HashSet < String > = self
273+ . jobs
274+ . values ( )
275+ . filter ( |j| j. state . last_status == JobStatus :: Running )
276+ . map ( |j| j. id . clone ( ) )
277+ . collect ( ) ;
278+
279+ let disk_ids: std:: collections:: HashSet < String > =
280+ disk. jobs . keys ( ) . cloned ( ) . collect ( ) ;
281+
282+ for ( id, disk_job) in disk. jobs {
283+ if running_ids. contains ( & id) {
284+ continue ;
285+ }
286+ self . jobs . insert ( id, disk_job) ;
287+ }
288+
289+ self . jobs . retain ( |id, _| {
290+ disk_ids. contains ( id) || running_ids. contains ( id)
291+ } ) ;
292+ }
246293}
247294
248295/// Generate a unique job ID.
0 commit comments