153 lines
2.2 KiB
Go
153 lines
2.2 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"context"
|
|
"cycle-scheduler/internal/job"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// atomicTimer wraps a `time.Timer`.
|
|
type atomicTimer struct {
|
|
atomic.Pointer[time.Timer]
|
|
}
|
|
|
|
func (at *atomicTimer) stop() {
|
|
timer := at.Load()
|
|
if timer != nil {
|
|
timer.Stop()
|
|
}
|
|
}
|
|
|
|
// set replaces the current timer.
|
|
// It also ensures that the current timer is stopped.
|
|
func (at *atomicTimer) set(t *time.Timer) {
|
|
timer := at.Load()
|
|
if timer != nil {
|
|
timer.Stop()
|
|
at.Swap(t)
|
|
return
|
|
}
|
|
|
|
at.Swap(t)
|
|
}
|
|
|
|
type TaskDetails struct {
|
|
job.JobDetails
|
|
Attempts int `json:"attempts"`
|
|
}
|
|
|
|
type task struct {
|
|
*job.Job
|
|
attempts atomic.Uint32
|
|
timer atomicTimer
|
|
}
|
|
|
|
func newTask(f job.FnJob) *task {
|
|
j := job.NewJob(f)
|
|
t := task{
|
|
Job: &j,
|
|
timer: atomicTimer{},
|
|
}
|
|
|
|
return &t
|
|
}
|
|
|
|
func (t *task) abort() {
|
|
t.timer.stop()
|
|
t.Job.Abort()
|
|
}
|
|
|
|
func (t *task) run(ctx context.Context) {
|
|
t.attempts.Add(1)
|
|
t.Job.Run(ctx)
|
|
}
|
|
|
|
func (t *task) getDetails() TaskDetails {
|
|
return TaskDetails{
|
|
JobDetails: t.IntoDetails(),
|
|
Attempts: int(t.attempts.Load()),
|
|
}
|
|
}
|
|
|
|
type tasks struct {
|
|
l sync.RWMutex
|
|
s map[uuid.UUID]*task
|
|
}
|
|
|
|
func newTasks() tasks {
|
|
return tasks{
|
|
s: make(map[uuid.UUID]*task),
|
|
}
|
|
}
|
|
|
|
func (ts *tasks) add(t *task) {
|
|
ts.l.Lock()
|
|
defer ts.l.Unlock()
|
|
|
|
ts.s[t.GetID()] = t
|
|
}
|
|
|
|
func (ts *tasks) get(id uuid.UUID) *task {
|
|
ts.l.RLock()
|
|
defer ts.l.RUnlock()
|
|
|
|
j, ok := ts.s[id]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return j
|
|
}
|
|
|
|
func (ts *tasks) len() int {
|
|
ts.l.RLock()
|
|
defer ts.l.RUnlock()
|
|
|
|
return len(ts.s)
|
|
}
|
|
|
|
func (ts *tasks) completed() bool {
|
|
ts.l.RLock()
|
|
defer ts.l.RUnlock()
|
|
|
|
for _, t := range ts.s {
|
|
if t.GetState() == job.Pending || t.GetState() == job.Running {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (ts *tasks) getAllDetails() []TaskDetails {
|
|
ts.l.RLock()
|
|
defer ts.l.RUnlock()
|
|
|
|
details := []TaskDetails{}
|
|
for _, t := range ts.s {
|
|
details = append(details, t.getDetails())
|
|
}
|
|
|
|
return details
|
|
}
|
|
|
|
func (ts *tasks) getDetails(id uuid.UUID) TaskDetails {
|
|
ts.l.RLock()
|
|
defer ts.l.RUnlock()
|
|
|
|
t, ok := ts.s[id]
|
|
if !ok {
|
|
return TaskDetails{
|
|
JobDetails: job.JobDetails{
|
|
State: job.UnknownState,
|
|
},
|
|
}
|
|
}
|
|
|
|
return t.getDetails()
|
|
}
|