@@ -56,6 +56,8 @@ type executor struct {
5656 monitor Monitor
5757 // monitorStatus for reporting metrics
5858 monitorStatus MonitorStatus
59+ // reference to parent scheduler for lifecycle notifications
60+ scheduler * scheduler
5961}
6062
6163type jobIn struct {
@@ -155,6 +157,15 @@ func (e *executor) start() {
155157 // all runners are busy, reschedule the work for later
156158 // which means we just skip it here and do nothing
157159 // TODO when metrics are added, this should increment a rescheduled metric
160+ // Notify concurrency limit reached if monitor is configured
161+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
162+ ctx2 , cancel2 := context .WithCancel (executorCtx )
163+ job := requestJobCtx (ctx2 , jIn .id , e .jobOutRequest )
164+ cancel2 ()
165+ if job != nil {
166+ e .scheduler .notifyConcurrencyLimitReached ("limit" , e .scheduler .jobFromInternalJob (* job ))
167+ }
168+ }
158169 e .sendOutForRescheduling (& jIn )
159170 }
160171 } else {
@@ -209,6 +220,10 @@ func (e *executor) start() {
209220 // which means we just skip it here and do nothing
210221 e .incrementJobCounter (* j , SingletonRescheduled )
211222 e .sendOutForRescheduling (& jIn )
223+ // Notify concurrency limit reached if monitor is configured
224+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
225+ e .scheduler .notifyConcurrencyLimitReached ("singleton" , e .scheduler .jobFromInternalJob (* j ))
226+ }
212227 }
213228 } else {
214229 // wait mode, fill up that queue (buffered channel, so it's ok)
@@ -416,18 +431,36 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
416431
417432 _ = callJobFuncWithParams (j .beforeJobRuns , j .id , j .name )
418433
434+ // Notify job started
435+ actualStartTime := time .Now ()
436+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
437+ jobObj := e .scheduler .jobFromInternalJob (j )
438+ e .scheduler .notifyJobStarted (jobObj )
439+ // Notify scheduling delay if job had a scheduled time
440+ if len (j .nextScheduled ) > 0 {
441+ e .scheduler .notifyJobSchedulingDelay (jobObj , j .nextScheduled [0 ], actualStartTime )
442+ }
443+ }
444+
419445 err := callJobFuncWithParams (j .beforeJobRunsSkipIfBeforeFuncErrors , j .id , j .name )
420446 if err != nil {
421447 e .sendOutForRescheduling (& jIn )
422-
423448 select {
424449 case e .jobsOutCompleted <- j .id :
425450 case <- e .ctx .Done ():
426451 }
427-
452+ // Notify job failed (before actual run)
453+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
454+ e .scheduler .notifyJobFailed (e .scheduler .jobFromInternalJob (j ), err )
455+ }
428456 return
429457 }
430458
459+ // Notify job running
460+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
461+ e .scheduler .notifyJobRunning (e .scheduler .jobFromInternalJob (j ))
462+ }
463+
431464 // For intervalFromCompletion, we need to reschedule AFTER the job completes,
432465 // not before. For regular jobs, we reschedule before execution (existing behavior).
433466 if ! j .intervalFromCompletion {
@@ -448,11 +481,25 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
448481 if err != nil {
449482 _ = callJobFuncWithParams (j .afterJobRunsWithError , j .id , j .name , err )
450483 e .incrementJobCounter (j , Fail )
451- e .recordJobTimingWithStatus (startTime , time .Now (), j , Fail , err )
484+ endTime := time .Now ()
485+ e .recordJobTimingWithStatus (startTime , endTime , j , Fail , err )
486+ // Notify job failed
487+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
488+ jobObj := e .scheduler .jobFromInternalJob (j )
489+ e .scheduler .notifyJobFailed (jobObj , err )
490+ e .scheduler .notifyJobExecutionTime (jobObj , endTime .Sub (startTime ))
491+ }
452492 } else {
453493 _ = callJobFuncWithParams (j .afterJobRuns , j .id , j .name )
454494 e .incrementJobCounter (j , Success )
455- e .recordJobTimingWithStatus (startTime , time .Now (), j , Success , nil )
495+ endTime := time .Now ()
496+ e .recordJobTimingWithStatus (startTime , endTime , j , Success , nil )
497+ // Notify job completed
498+ if e .scheduler != nil && e .scheduler .schedulerMonitor != nil {
499+ jobObj := e .scheduler .jobFromInternalJob (j )
500+ e .scheduler .notifyJobCompleted (jobObj )
501+ e .scheduler .notifyJobExecutionTime (jobObj , endTime .Sub (startTime ))
502+ }
456503 }
457504
458505 // For intervalFromCompletion, reschedule AFTER the job completes
0 commit comments