• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-phorce / dolly / 4269215184

25 Feb 2023 08:53AM UTC coverage: 85.275% (-0.009%) from 85.284%
4269215184

Pull #226

github

GitHub
Bump golang.org/x/sys from 0.0.0-20210615035016-665e8c7367d1 to 0.1.0
Pull Request #226: Bump golang.org/x/sys from 0.0.0-20210615035016-665e8c7367d1 to 0.1.0

9492 of 11131 relevant lines covered (85.28%)

7585.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.1
/tasks/task.go
1
package tasks
2

3
import (
4
        "fmt"
5
        "path/filepath"
6
        "reflect"
7
        "runtime"
8
        "strconv"
9
        "strings"
10
        "sync/atomic"
11
        "time"
12

13
        "github.com/pkg/errors"
14
)
15

16
// TimeUnit specifies the time unit: 'minutes', 'hours'...
17
type TimeUnit uint
18

19
const (
20
        // Never specifies the time unit to never run a task
21
        Never TimeUnit = iota
22
        // Seconds specifies the time unit in seconds
23
        Seconds
24
        // Minutes specifies the time unit in minutes
25
        Minutes
26
        // Hours specifies the time unit in hours
27
        Hours
28
        // Days specifies the time unit in days
29
        Days
30
        // Weeks specifies the time unit in weeks
31
        Weeks
32
)
33

34
// Task defines task interface
35
type Task interface {
36
        // Name returns a name of the task
37
        Name() string
38
        // RunCount species the number of times the task executed
39
        RunCount() uint32
40
        // NextScheduledTime returns the time of when this task is to run next
41
        NextScheduledTime() time.Time
42
        // LastRunTime returns the time of last run
43
        LastRunTime() time.Time
44
        // Duration returns interval between runs
45
        Duration() time.Duration
46

47
        // ShouldRun returns true if the task should be run now
48
        ShouldRun() bool
49

50
        // Run will try to run the task, if it's not already running
51
        // and immediately reschedule it after run
52
        Run() bool
53

54
        // Do accepts a function that should be called every time the task runs
55
        Do(taskName string, task interface{}, params ...interface{}) Task
56
}
57

58
// task describes a task schedule
59
type task struct {
60
        // pause interval * unit bettween runs
61
        interval uint64
62
        // time units, ,e.g. 'minutes', 'hours'...
63
        unit TimeUnit
64
        // number of runs
65
        count uint32
66
        // datetime of last run
67
        lastRunAt *time.Time
68
        // datetime of next run
69
        nextRunAt time.Time
70
        // cache the period between last an next run
71
        period time.Duration
72
        // Specific day of the week to start on
73
        startDay time.Weekday
74

75
        // the task name
76
        name string
77
        // callback is the function to execute
78
        callback reflect.Value
79
        // params for the callback functions
80
        params []reflect.Value
81

82
        runLock chan struct{}
83
        running bool
84
}
85

86
// NewTaskAtIntervals creates a new task with the time interval.
87
func NewTaskAtIntervals(interval uint64, unit TimeUnit) Task {
16✔
88
        return &task{
16✔
89
                interval:  interval,
16✔
90
                unit:      unit,
16✔
91
                lastRunAt: nil,
16✔
92
                nextRunAt: time.Unix(0, 0),
16✔
93
                period:    0,
16✔
94
                startDay:  time.Sunday,
16✔
95
                runLock:   make(chan struct{}, 1),
16✔
96
                count:     0,
16✔
97
        }
16✔
98
}
16✔
99

100
// NewTaskOnWeekday creates a new task to execute on specific day of the week.
101
func NewTaskOnWeekday(startDay time.Weekday, hour, minute int) Task {
14✔
102
        if hour < 0 || hour > 23 || minute < 0 || minute > 59 {
16✔
103
                logger.Panicf("reason='invalid time value', time='%d:%d'", hour, minute)
2✔
104
        }
2✔
105
        j := &task{
12✔
106
                interval:  1,
12✔
107
                unit:      Weeks,
12✔
108
                lastRunAt: nil,
12✔
109
                nextRunAt: time.Unix(0, 0),
12✔
110
                period:    0,
12✔
111
                startDay:  startDay,
12✔
112
                runLock:   make(chan struct{}, 1),
12✔
113
                count:     0,
12✔
114
        }
12✔
115
        return j.at(hour, minute)
12✔
116
}
117

118
// NewTaskDaily creates a new task to execute daily at specific time
119
func NewTaskDaily(hour, minute int) Task {
5✔
120
        if hour < 0 || hour > 23 || minute < 0 || minute > 59 {
6✔
121
                logger.Panicf("reason='invalid time value', time='%d:%d'", hour, minute)
1✔
122
        }
1✔
123
        j := &task{
4✔
124
                interval:  1,
4✔
125
                unit:      Days,
4✔
126
                lastRunAt: nil,
4✔
127
                nextRunAt: time.Unix(0, 0),
4✔
128
                period:    0,
4✔
129
                startDay:  time.Sunday,
4✔
130
                runLock:   make(chan struct{}, 1),
4✔
131
                count:     0,
4✔
132
        }
4✔
133
        return j.at(hour, minute)
4✔
134
}
135

136
// NewTask creates a new task from parsed format string.
137
// every %d
138
// seconds | minutes | ...
139
// Monday | .. | Sunday
140
// at %hh:mm
141
func NewTask(format string) (Task, error) {
×
142
        return parseTaskFormat(format)
×
143
}
×
144

145
// Name returns a name of the task
146
func (j *task) Name() string {
20✔
147
        return j.name
20✔
148
}
20✔
149

150
// RunCount species the number of times the task executed
151
func (j *task) RunCount() uint32 {
2✔
152
        return atomic.LoadUint32(&j.count)
2✔
153
}
2✔
154

155
// ShouldRun returns true if the task should be run now
156
func (j *task) ShouldRun() bool {
9✔
157
        return !j.running && time.Now().After(j.nextRunAt)
9✔
158
}
9✔
159

160
// NextScheduledTime returns the time of when this task is to run next
161
func (j *task) NextScheduledTime() time.Time {
63✔
162
        return j.nextRunAt
63✔
163
}
63✔
164

165
// LastRunTime returns the time of last run
166
func (j *task) LastRunTime() time.Time {
1✔
167
        if j.lastRunAt != nil {
2✔
168
                return *j.lastRunAt
1✔
169
        }
1✔
170
        return time.Unix(0, 0)
×
171
}
172

173
// // Duration returns interval between runs
174
func (j *task) Duration() time.Duration {
41✔
175
        if j.period == 0 {
73✔
176
                switch j.unit {
32✔
177
                case Seconds:
6✔
178
                        j.period = time.Duration(j.interval) * time.Second
6✔
179
                case Minutes:
3✔
180
                        j.period = time.Duration(j.interval) * time.Minute
3✔
181
                case Hours:
3✔
182
                        j.period = time.Duration(j.interval) * time.Hour
3✔
183
                case Days:
5✔
184
                        j.period = time.Duration(j.interval) * time.Hour * 24
5✔
185
                case Weeks:
15✔
186
                        j.period = time.Duration(j.interval) * time.Hour * 24 * 7
15✔
187
                }
188
        }
189
        return j.period
41✔
190
}
191

192
// Do accepts a function that should be called every time the task runs
193
func (j *task) Do(taskName string, taskFunc interface{}, params ...interface{}) Task {
13✔
194
        typ := reflect.TypeOf(taskFunc)
13✔
195
        if typ.Kind() != reflect.Func {
13✔
196
                logger.Panic("reason='only function can be schedule into the task queue'")
×
197
        }
×
198

199
        j.name = fmt.Sprintf("%s@%s", taskName, filepath.Base(getFunctionName(taskFunc)))
13✔
200
        j.callback = reflect.ValueOf(taskFunc)
13✔
201
        if len(params) != j.callback.Type().NumIn() {
13✔
202
                logger.Panicf("reason='the number of parameters does not match the function'")
×
203
        }
×
204
        j.params = make([]reflect.Value, len(params))
13✔
205
        for k, param := range params {
17✔
206
                j.params[k] = reflect.ValueOf(param)
4✔
207
        }
4✔
208

209
        //schedule the next run
210
        j.scheduleNextRun()
13✔
211

13✔
212
        return j
13✔
213
}
214

215
func (j *task) at(hour, min int) *task {
22✔
216
        y, m, d := time.Now().Date()
22✔
217

22✔
218
        // time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
22✔
219
        mock := time.Date(y, m, d, hour, min, 0, 0, loc)
22✔
220

22✔
221
        if j.unit == Days {
28✔
222
                if !time.Now().After(mock) {
11✔
223
                        // remove 1 day
5✔
224
                        mock = mock.UTC().AddDate(0, 0, -1).Local()
5✔
225
                }
5✔
226
        } else if j.unit == Weeks {
32✔
227
                if j.startDay != time.Now().Weekday() || (time.Now().After(mock) && j.startDay == time.Now().Weekday()) {
29✔
228
                        i := int(mock.Weekday() - j.startDay)
13✔
229
                        if i < 0 {
13✔
230
                                i = 7 + i
×
231
                        }
×
232
                        mock = mock.UTC().AddDate(0, 0, -i).Local()
13✔
233
                } else {
3✔
234
                        // remove 1 week
3✔
235
                        mock = mock.UTC().AddDate(0, 0, -7).Local()
3✔
236
                }
3✔
237
        }
238
        j.lastRunAt = &mock
22✔
239
        return j
22✔
240
}
241

242
// scheduleNextRun computes the instant when this task should run next
243
func (j *task) scheduleNextRun() time.Time {
22✔
244
        now := time.Now()
22✔
245
        if j.lastRunAt == nil {
29✔
246
                if j.unit == Weeks {
9✔
247
                        i := now.Weekday() - j.startDay
2✔
248
                        if i < 0 {
2✔
249
                                i = 7 + i
×
250
                        }
×
251
                        y, m, d := now.Date()
2✔
252
                        now = time.Date(y, m, d-int(i), 0, 0, 0, 0, loc)
2✔
253
                }
254
                j.lastRunAt = &now
7✔
255
        }
256

257
        j.nextRunAt = j.lastRunAt.Add(j.Duration())
22✔
258
        /*
22✔
259
                logger.Tracef("lastRunAt='%v', nextRunAt='%v', task=%q",
22✔
260
                        j.lastRunAt.Format(time.RFC3339),
22✔
261
                        j.nextRunAt.Format(time.RFC3339),
22✔
262
                        j.Name())
22✔
263
        */
22✔
264
        return j.nextRunAt
22✔
265
}
266

267
// for given function fn, get the name of function.
268
func getFunctionName(fn interface{}) string {
13✔
269
        return runtime.FuncForPC(reflect.ValueOf((fn)).Pointer()).Name()
13✔
270
}
13✔
271

272
// Run will try to run the task, if it's not already running
273
// and immediately reschedule it after run
274
func (j *task) Run() bool {
9✔
275
        timeout := time.Millisecond
9✔
276
        timer := time.NewTimer(timeout)
9✔
277
        select {
9✔
278
        case j.runLock <- struct{}{}:
9✔
279
                timer.Stop()
9✔
280
                now := time.Now()
9✔
281
                j.lastRunAt = &now
9✔
282
                j.running = true
9✔
283
                count := atomic.AddUint32(&j.count, 1)
9✔
284

9✔
285
                logger.Infof("status=running, count=%d, started_at='%v', task=%q",
9✔
286
                        count,
9✔
287
                        j.lastRunAt.Format(time.RFC3339),
9✔
288
                        j.Name())
9✔
289

9✔
290
                j.callback.Call(j.params)
9✔
291
                j.running = false
9✔
292
                j.scheduleNextRun()
9✔
293
                <-j.runLock
9✔
294
                return true
9✔
295
        case <-time.After(timeout):
×
296
        }
297
        logger.Tracef("reason=already_running, count=%d, started_at='%v', task=%q",
×
298
                j.count,
×
299
                j.lastRunAt.Format(time.RFC3339),
×
300
                j.Name())
×
301

×
302
        return false
×
303
}
304

305
func parseTimeFormat(t string) (hour, min int, err error) {
15✔
306
        var errTimeFormat = errors.Errorf("time format not valid: %q", t)
15✔
307
        ts := strings.Split(t, ":")
15✔
308
        if len(ts) != 2 {
16✔
309
                err = errors.WithStack(errTimeFormat)
1✔
310
                return
1✔
311
        }
1✔
312

313
        hour, err = strconv.Atoi(ts[0])
14✔
314
        if err != nil {
15✔
315
                err = errors.WithStack(err)
1✔
316
                return
1✔
317
        }
1✔
318
        min, err = strconv.Atoi(ts[1])
13✔
319
        if err != nil {
14✔
320
                err = errors.WithStack(err)
1✔
321
                return
1✔
322
        }
1✔
323

324
        if hour < 0 || hour > 23 || min < 0 || min > 59 {
15✔
325
                err = errors.WithStack(errTimeFormat)
3✔
326
                return
3✔
327
        }
3✔
328
        return
9✔
329
}
330

331
func parseTaskFormat(format string) (*task, error) {
32✔
332
        var errTimeFormat = errors.Errorf("task format not valid: %q", format)
32✔
333

32✔
334
        j := &task{
32✔
335
                interval:  0,
32✔
336
                unit:      Never,
32✔
337
                lastRunAt: nil,
32✔
338
                nextRunAt: time.Unix(0, 0),
32✔
339
                period:    0,
32✔
340
                startDay:  time.Sunday,
32✔
341
                runLock:   make(chan struct{}, 1),
32✔
342
                count:     0,
32✔
343
        }
32✔
344

32✔
345
        ts := strings.Split(strings.ToLower(format), " ")
32✔
346
        for _, t := range ts {
98✔
347
                switch t {
66✔
348
                case "every":
15✔
349
                        if j.interval > 0 {
16✔
350
                                return nil, errors.WithStack(errTimeFormat)
1✔
351
                        }
1✔
352
                        j.interval = 1
14✔
353
                case "second", "seconds":
3✔
354
                        j.unit = Seconds
3✔
355
                case "minute", "minutes":
2✔
356
                        j.unit = Minutes
2✔
357
                case "hour", "hours":
3✔
358
                        j.unit = Hours
3✔
359
                case "day", "days":
2✔
360
                        j.unit = Days
2✔
361
                case "week", "weeks":
2✔
362
                        j.unit = Weeks
2✔
363
                case "monday":
2✔
364
                        if j.interval > 1 || j.unit != Never {
3✔
365
                                return nil, errors.WithStack(errTimeFormat)
1✔
366
                        }
1✔
367
                        j.unit = Weeks
1✔
368
                        j.startDay = time.Monday
1✔
369
                case "tuesday":
2✔
370
                        if j.interval > 1 || j.unit != Never {
3✔
371
                                return nil, errors.WithStack(errTimeFormat)
1✔
372
                        }
1✔
373
                        j.unit = Weeks
1✔
374
                        j.startDay = time.Tuesday
1✔
375
                case "wednesday":
2✔
376
                        if j.interval > 1 || j.unit != Never {
3✔
377
                                return nil, errors.WithStack(errTimeFormat)
1✔
378
                        }
1✔
379
                        j.unit = Weeks
1✔
380
                        j.startDay = time.Wednesday
1✔
381
                case "thursday":
2✔
382
                        if j.interval > 1 || j.unit != Never {
3✔
383
                                return nil, errors.WithStack(errTimeFormat)
1✔
384
                        }
1✔
385
                        j.unit = Weeks
1✔
386
                        j.startDay = time.Thursday
1✔
387
                case "friday":
2✔
388
                        if j.interval > 1 || j.unit != Never {
3✔
389
                                return nil, errors.WithStack(errTimeFormat)
1✔
390
                        }
1✔
391
                        j.unit = Weeks
1✔
392
                        j.startDay = time.Friday
1✔
393
                case "saturday":
2✔
394
                        if j.interval > 1 || j.unit != Never {
3✔
395
                                return nil, errors.WithStack(errTimeFormat)
1✔
396
                        }
1✔
397
                        j.unit = Weeks
1✔
398
                        j.startDay = time.Saturday
1✔
399
                case "sunday":
3✔
400
                        if j.interval > 1 || j.unit != Never {
4✔
401
                                return nil, errors.WithStack(errTimeFormat)
1✔
402
                        }
1✔
403
                        j.unit = Weeks
2✔
404
                        j.startDay = time.Sunday
2✔
405
                default:
24✔
406
                        if strings.Contains(t, ":") {
33✔
407
                                hour, min, err := parseTimeFormat(t)
9✔
408
                                if err != nil {
11✔
409
                                        return nil, errors.WithStack(errTimeFormat)
2✔
410
                                }
2✔
411
                                if j.unit == Never {
8✔
412
                                        j.unit = Days
1✔
413
                                } else if j.unit != Days && j.unit != Weeks {
8✔
414
                                        return nil, errors.WithStack(errTimeFormat)
1✔
415
                                }
1✔
416
                                j.at(hour, min)
6✔
417
                        } else {
15✔
418
                                if j.interval > 1 {
15✔
419
                                        return nil, errors.WithStack(errTimeFormat)
×
420
                                }
×
421
                                interval, err := strconv.ParseUint(t, 10, 0)
15✔
422
                                if err != nil || interval < 1 {
15✔
423
                                        return nil, errors.WithStack(errTimeFormat)
×
424
                                }
×
425
                                j.interval = interval
15✔
426
                        }
427
                }
428
        }
429
        if j.interval == 0 {
28✔
430
                j.interval = 1
7✔
431
        }
7✔
432
        if j.unit == Never {
23✔
433
                return nil, errors.WithStack(errTimeFormat)
2✔
434
        }
2✔
435

436
        return j, nil
19✔
437
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc