2022-07-10 17:18:21 +02:00
package sched
import (
2022-07-22 12:43:34 +02:00
"reflect"
"strconv"
"strings"
2024-09-26 21:23:41 +02:00
"sync/atomic"
2022-07-10 17:18:21 +02:00
"time"
2024-09-26 21:23:41 +02:00
"unsafe"
2022-07-10 17:18:21 +02:00
)
// Job encapsulates logic for a scheduled job to be run according
// to a set Timing, executing the job with a set panic handler, and
// holding onto a next execution time safely in a concurrent environment.
type Job struct {
id uint64
2024-09-26 21:23:41 +02:00
next unsafe . Pointer // *time.Time
2022-07-10 17:18:21 +02:00
timing Timing
call func ( time . Time )
panic func ( interface { } )
}
// NewJob returns a new Job to run given function.
func NewJob ( fn func ( now time . Time ) ) * Job {
if fn == nil {
// Ensure a function
panic ( "nil func" )
}
j := & Job { // set defaults
timing : emptytiming , // i.e. fire immediately
call : fn ,
panic : func ( i interface { } ) { panic ( i ) } ,
}
return j
}
// At sets this Job to execute at time, by passing (*sched.Once)(&at) to .With(). See .With() for details.
func ( job * Job ) At ( at time . Time ) * Job {
return job . With ( ( * Once ) ( & at ) )
}
// Every sets this Job to execute every period, by passing sched.Period(period) to .With(). See .With() for details.
func ( job * Job ) Every ( period time . Duration ) * Job {
return job . With ( Periodic ( period ) )
}
// EveryAt sets this Job to execute every period starting at time, by passing &PeriodicAt{once: Once(at), period: Periodic(period)} to .With(). See .With() for details.
func ( job * Job ) EveryAt ( at time . Time , period time . Duration ) * Job {
return job . With ( & PeriodicAt { Once : Once ( at ) , Period : Periodic ( period ) } )
}
// With sets this Job's timing to given implementation, or if already set will wrap existing using sched.TimingWrap{}.
func ( job * Job ) With ( t Timing ) * Job {
if t == nil {
// Ensure a timing
panic ( "nil Timing" )
}
2022-09-28 19:30:40 +02:00
if job . id != 0 {
// Cannot update scheduled job
panic ( "job already scheduled" )
}
2022-07-10 17:18:21 +02:00
if job . timing == emptytiming {
// Set new timing
job . timing = t
} else {
// Wrap old timing
old := job . timing
job . timing = & TimingWrap {
Outer : t ,
Inner : old ,
}
}
return job
}
2022-09-28 19:30:40 +02:00
// OnPanic specifies how this job handles panics, default is an actual panic.
func ( job * Job ) OnPanic ( fn func ( interface { } ) ) * Job {
2022-07-10 17:18:21 +02:00
if fn == nil {
// Ensure a function
panic ( "nil func" )
}
2022-09-28 19:30:40 +02:00
if job . id != 0 {
// Cannot update scheduled job
panic ( "job already scheduled" )
}
2022-07-10 17:18:21 +02:00
job . panic = fn
return job
}
// Next returns the next time this Job is expected to run.
func ( job * Job ) Next ( ) time . Time {
2024-09-26 21:23:41 +02:00
return loadTime ( & job . next )
2022-07-10 17:18:21 +02:00
}
// Run will execute this Job and pass through given now time.
func ( job * Job ) Run ( now time . Time ) {
defer func ( ) {
2024-09-26 21:23:41 +02:00
switch r := recover ( ) ; {
case r == nil :
// no panic
case job != nil &&
job . panic != nil :
2022-07-10 17:18:21 +02:00
job . panic ( r )
2024-09-26 21:23:41 +02:00
default :
panic ( r )
2022-07-10 17:18:21 +02:00
}
} ( )
job . call ( now )
}
2022-07-22 12:43:34 +02:00
// String provides a debuggable string representation of Job including ID, next time and Timing type.
func ( job * Job ) String ( ) string {
var buf strings . Builder
buf . WriteByte ( '{' )
buf . WriteString ( "id=" )
buf . WriteString ( strconv . FormatUint ( job . id , 10 ) )
buf . WriteByte ( ' ' )
buf . WriteString ( "next=" )
2024-09-26 21:23:41 +02:00
buf . WriteString ( loadTime ( & job . next ) . Format ( time . StampMicro ) )
2022-07-22 12:43:34 +02:00
buf . WriteByte ( ' ' )
buf . WriteString ( "timing=" )
buf . WriteString ( reflect . TypeOf ( job . timing ) . String ( ) )
buf . WriteByte ( '}' )
return buf . String ( )
}
2024-09-26 21:23:41 +02:00
func loadTime ( p * unsafe . Pointer ) time . Time {
if p := atomic . LoadPointer ( p ) ; p != nil {
return * ( * time . Time ) ( p )
}
return zerotime
}
func storeTime ( p * unsafe . Pointer , t time . Time ) {
atomic . StorePointer ( p , unsafe . Pointer ( & t ) )
}