github.com/astaxie/beego@v1.12.3/toolbox/task.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package toolbox 16 17 import ( 18 "log" 19 "math" 20 "sort" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 ) 26 27 // bounds provides a range of acceptable values (plus a map of name to value). 28 type bounds struct { 29 min, max uint 30 names map[string]uint 31 } 32 33 // The bounds for each field. 34 var ( 35 AdminTaskList map[string]Tasker 36 taskLock sync.RWMutex 37 stop chan bool 38 changed chan bool 39 isstart bool 40 seconds = bounds{0, 59, nil} 41 minutes = bounds{0, 59, nil} 42 hours = bounds{0, 23, nil} 43 days = bounds{1, 31, nil} 44 months = bounds{1, 12, map[string]uint{ 45 "jan": 1, 46 "feb": 2, 47 "mar": 3, 48 "apr": 4, 49 "may": 5, 50 "jun": 6, 51 "jul": 7, 52 "aug": 8, 53 "sep": 9, 54 "oct": 10, 55 "nov": 11, 56 "dec": 12, 57 }} 58 weeks = bounds{0, 6, map[string]uint{ 59 "sun": 0, 60 "mon": 1, 61 "tue": 2, 62 "wed": 3, 63 "thu": 4, 64 "fri": 5, 65 "sat": 6, 66 }} 67 ) 68 69 const ( 70 // Set the top bit if a star was included in the expression. 71 starBit = 1 << 63 72 ) 73 74 // Schedule time taks schedule 75 type Schedule struct { 76 Second uint64 77 Minute uint64 78 Hour uint64 79 Day uint64 80 Month uint64 81 Week uint64 82 } 83 84 // TaskFunc task func type 85 type TaskFunc func() error 86 87 // Tasker task interface 88 type Tasker interface { 89 GetSpec() string 90 GetStatus() string 91 Run() error 92 SetNext(time.Time) 93 GetNext() time.Time 94 SetPrev(time.Time) 95 GetPrev() time.Time 96 } 97 98 // task error 99 type taskerr struct { 100 t time.Time 101 errinfo string 102 } 103 104 // Task task struct 105 type Task struct { 106 Taskname string 107 Spec *Schedule 108 SpecStr string 109 DoFunc TaskFunc 110 Prev time.Time 111 Next time.Time 112 Errlist []*taskerr // like errtime:errinfo 113 ErrLimit int // max length for the errlist, 0 stand for no limit 114 } 115 116 // NewTask add new task with name, time and func 117 func NewTask(tname string, spec string, f TaskFunc) *Task { 118 119 task := &Task{ 120 Taskname: tname, 121 DoFunc: f, 122 ErrLimit: 100, 123 SpecStr: spec, 124 } 125 task.SetCron(spec) 126 return task 127 } 128 129 // GetSpec get spec string 130 func (t *Task) GetSpec() string { 131 return t.SpecStr 132 } 133 134 // GetStatus get current task status 135 func (t *Task) GetStatus() string { 136 var str string 137 for _, v := range t.Errlist { 138 str += v.t.String() + ":" + v.errinfo + "<br>" 139 } 140 return str 141 } 142 143 // Run run all tasks 144 func (t *Task) Run() error { 145 err := t.DoFunc() 146 if err != nil { 147 if t.ErrLimit > 0 && t.ErrLimit > len(t.Errlist) { 148 t.Errlist = append(t.Errlist, &taskerr{t: t.Next, errinfo: err.Error()}) 149 } 150 } 151 return err 152 } 153 154 // SetNext set next time for this task 155 func (t *Task) SetNext(now time.Time) { 156 t.Next = t.Spec.Next(now) 157 } 158 159 // GetNext get the next call time of this task 160 func (t *Task) GetNext() time.Time { 161 return t.Next 162 } 163 164 // SetPrev set prev time of this task 165 func (t *Task) SetPrev(now time.Time) { 166 t.Prev = now 167 } 168 169 // GetPrev get prev time of this task 170 func (t *Task) GetPrev() time.Time { 171 return t.Prev 172 } 173 174 // six columns mean: 175 // second:0-59 176 // minute:0-59 177 // hour:1-23 178 // day:1-31 179 // month:1-12 180 // week:0-6(0 means Sunday) 181 182 // SetCron some signals: 183 // *: any time 184 // ,: separate signal 185 // -:duration 186 // /n : do as n times of time duration 187 ///////////////////////////////////////////////////////// 188 // 0/30 * * * * * every 30s 189 // 0 43 21 * * * 21:43 190 // 0 15 05 * * * 05:15 191 // 0 0 17 * * * 17:00 192 // 0 0 17 * * 1 17:00 in every Monday 193 // 0 0,10 17 * * 0,2,3 17:00 and 17:10 in every Sunday, Tuesday and Wednesday 194 // 0 0-10 17 1 * * 17:00 to 17:10 in 1 min duration each time on the first day of month 195 // 0 0 0 1,15 * 1 0:00 on the 1st day and 15th day of month 196 // 0 42 4 1 * * 4:42 on the 1st day of month 197 // 0 0 21 * * 1-6 21:00 from Monday to Saturday 198 // 0 0,10,20,30,40,50 * * * * every 10 min duration 199 // 0 */10 * * * * every 10 min duration 200 // 0 * 1 * * * 1:00 to 1:59 in 1 min duration each time 201 // 0 0 1 * * * 1:00 202 // 0 0 */1 * * * 0 min of hour in 1 hour duration 203 // 0 0 * * * * 0 min of hour in 1 hour duration 204 // 0 2 8-20/3 * * * 8:02, 11:02, 14:02, 17:02, 20:02 205 // 0 30 5 1,15 * * 5:30 on the 1st day and 15th day of month 206 func (t *Task) SetCron(spec string) { 207 t.Spec = t.parse(spec) 208 } 209 210 func (t *Task) parse(spec string) *Schedule { 211 if len(spec) > 0 && spec[0] == '@' { 212 return t.parseSpec(spec) 213 } 214 // Split on whitespace. We require 5 or 6 fields. 215 // (second) (minute) (hour) (day of month) (month) (day of week, optional) 216 fields := strings.Fields(spec) 217 if len(fields) != 5 && len(fields) != 6 { 218 log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec) 219 } 220 221 // If a sixth field is not provided (DayOfWeek), then it is equivalent to star. 222 if len(fields) == 5 { 223 fields = append(fields, "*") 224 } 225 226 schedule := &Schedule{ 227 Second: getField(fields[0], seconds), 228 Minute: getField(fields[1], minutes), 229 Hour: getField(fields[2], hours), 230 Day: getField(fields[3], days), 231 Month: getField(fields[4], months), 232 Week: getField(fields[5], weeks), 233 } 234 235 return schedule 236 } 237 238 func (t *Task) parseSpec(spec string) *Schedule { 239 switch spec { 240 case "@yearly", "@annually": 241 return &Schedule{ 242 Second: 1 << seconds.min, 243 Minute: 1 << minutes.min, 244 Hour: 1 << hours.min, 245 Day: 1 << days.min, 246 Month: 1 << months.min, 247 Week: all(weeks), 248 } 249 250 case "@monthly": 251 return &Schedule{ 252 Second: 1 << seconds.min, 253 Minute: 1 << minutes.min, 254 Hour: 1 << hours.min, 255 Day: 1 << days.min, 256 Month: all(months), 257 Week: all(weeks), 258 } 259 260 case "@weekly": 261 return &Schedule{ 262 Second: 1 << seconds.min, 263 Minute: 1 << minutes.min, 264 Hour: 1 << hours.min, 265 Day: all(days), 266 Month: all(months), 267 Week: 1 << weeks.min, 268 } 269 270 case "@daily", "@midnight": 271 return &Schedule{ 272 Second: 1 << seconds.min, 273 Minute: 1 << minutes.min, 274 Hour: 1 << hours.min, 275 Day: all(days), 276 Month: all(months), 277 Week: all(weeks), 278 } 279 280 case "@hourly": 281 return &Schedule{ 282 Second: 1 << seconds.min, 283 Minute: 1 << minutes.min, 284 Hour: all(hours), 285 Day: all(days), 286 Month: all(months), 287 Week: all(weeks), 288 } 289 } 290 log.Panicf("Unrecognized descriptor: %s", spec) 291 return nil 292 } 293 294 // Next set schedule to next time 295 func (s *Schedule) Next(t time.Time) time.Time { 296 297 // Start at the earliest possible time (the upcoming second). 298 t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) 299 300 // This flag indicates whether a field has been incremented. 301 added := false 302 303 // If no time is found within five years, return zero. 304 yearLimit := t.Year() + 5 305 306 WRAP: 307 if t.Year() > yearLimit { 308 return time.Time{} 309 } 310 311 // Find the first applicable month. 312 // If it's this month, then do nothing. 313 for 1<<uint(t.Month())&s.Month == 0 { 314 // If we have to add a month, reset the other parts to 0. 315 if !added { 316 added = true 317 // Otherwise, set the date at the beginning (since the current time is irrelevant). 318 t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()) 319 } 320 t = t.AddDate(0, 1, 0) 321 322 // Wrapped around. 323 if t.Month() == time.January { 324 goto WRAP 325 } 326 } 327 328 // Now get a day in that month. 329 for !dayMatches(s, t) { 330 if !added { 331 added = true 332 t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) 333 } 334 t = t.AddDate(0, 0, 1) 335 336 if t.Day() == 1 { 337 goto WRAP 338 } 339 } 340 341 for 1<<uint(t.Hour())&s.Hour == 0 { 342 if !added { 343 added = true 344 t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()) 345 } 346 t = t.Add(1 * time.Hour) 347 348 if t.Hour() == 0 { 349 goto WRAP 350 } 351 } 352 353 for 1<<uint(t.Minute())&s.Minute == 0 { 354 if !added { 355 added = true 356 t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location()) 357 } 358 t = t.Add(1 * time.Minute) 359 360 if t.Minute() == 0 { 361 goto WRAP 362 } 363 } 364 365 for 1<<uint(t.Second())&s.Second == 0 { 366 if !added { 367 added = true 368 t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()) 369 } 370 t = t.Add(1 * time.Second) 371 372 if t.Second() == 0 { 373 goto WRAP 374 } 375 } 376 377 return t 378 } 379 380 func dayMatches(s *Schedule, t time.Time) bool { 381 var ( 382 domMatch = 1<<uint(t.Day())&s.Day > 0 383 dowMatch = 1<<uint(t.Weekday())&s.Week > 0 384 ) 385 386 if s.Day&starBit > 0 || s.Week&starBit > 0 { 387 return domMatch && dowMatch 388 } 389 return domMatch || dowMatch 390 } 391 392 // StartTask start all tasks 393 func StartTask() { 394 taskLock.Lock() 395 defer taskLock.Unlock() 396 if isstart { 397 //If already started, no need to start another goroutine. 398 return 399 } 400 isstart = true 401 go run() 402 } 403 404 func run() { 405 now := time.Now().Local() 406 for _, t := range AdminTaskList { 407 t.SetNext(now) 408 } 409 410 for { 411 // we only use RLock here because NewMapSorter copy the reference, do not change any thing 412 taskLock.RLock() 413 sortList := NewMapSorter(AdminTaskList) 414 taskLock.RUnlock() 415 sortList.Sort() 416 var effective time.Time 417 if len(AdminTaskList) == 0 || sortList.Vals[0].GetNext().IsZero() { 418 // If there are no entries yet, just sleep - it still handles new entries 419 // and stop requests. 420 effective = now.AddDate(10, 0, 0) 421 } else { 422 effective = sortList.Vals[0].GetNext() 423 } 424 select { 425 case now = <-time.After(effective.Sub(now)): 426 // Run every entry whose next time was this effective time. 427 for _, e := range sortList.Vals { 428 if e.GetNext() != effective { 429 break 430 } 431 go e.Run() 432 e.SetPrev(e.GetNext()) 433 e.SetNext(effective) 434 } 435 continue 436 case <-changed: 437 now = time.Now().Local() 438 taskLock.Lock() 439 for _, t := range AdminTaskList { 440 t.SetNext(now) 441 } 442 taskLock.Unlock() 443 continue 444 case <-stop: 445 return 446 } 447 } 448 } 449 450 // StopTask stop all tasks 451 func StopTask() { 452 taskLock.Lock() 453 defer taskLock.Unlock() 454 if isstart { 455 isstart = false 456 stop <- true 457 } 458 459 } 460 461 // AddTask add task with name 462 func AddTask(taskname string, t Tasker) { 463 taskLock.Lock() 464 defer taskLock.Unlock() 465 t.SetNext(time.Now().Local()) 466 AdminTaskList[taskname] = t 467 if isstart { 468 changed <- true 469 } 470 } 471 472 // DeleteTask delete task with name 473 func DeleteTask(taskname string) { 474 taskLock.Lock() 475 defer taskLock.Unlock() 476 delete(AdminTaskList, taskname) 477 if isstart { 478 changed <- true 479 } 480 } 481 482 // MapSorter sort map for tasker 483 type MapSorter struct { 484 Keys []string 485 Vals []Tasker 486 } 487 488 // NewMapSorter create new tasker map 489 func NewMapSorter(m map[string]Tasker) *MapSorter { 490 ms := &MapSorter{ 491 Keys: make([]string, 0, len(m)), 492 Vals: make([]Tasker, 0, len(m)), 493 } 494 for k, v := range m { 495 ms.Keys = append(ms.Keys, k) 496 ms.Vals = append(ms.Vals, v) 497 } 498 return ms 499 } 500 501 // Sort sort tasker map 502 func (ms *MapSorter) Sort() { 503 sort.Sort(ms) 504 } 505 506 func (ms *MapSorter) Len() int { return len(ms.Keys) } 507 func (ms *MapSorter) Less(i, j int) bool { 508 if ms.Vals[i].GetNext().IsZero() { 509 return false 510 } 511 if ms.Vals[j].GetNext().IsZero() { 512 return true 513 } 514 return ms.Vals[i].GetNext().Before(ms.Vals[j].GetNext()) 515 } 516 func (ms *MapSorter) Swap(i, j int) { 517 ms.Vals[i], ms.Vals[j] = ms.Vals[j], ms.Vals[i] 518 ms.Keys[i], ms.Keys[j] = ms.Keys[j], ms.Keys[i] 519 } 520 521 func getField(field string, r bounds) uint64 { 522 // list = range {"," range} 523 var bits uint64 524 ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) 525 for _, expr := range ranges { 526 bits |= getRange(expr, r) 527 } 528 return bits 529 } 530 531 // getRange returns the bits indicated by the given expression: 532 // number | number "-" number [ "/" number ] 533 func getRange(expr string, r bounds) uint64 { 534 535 var ( 536 start, end, step uint 537 rangeAndStep = strings.Split(expr, "/") 538 lowAndHigh = strings.Split(rangeAndStep[0], "-") 539 singleDigit = len(lowAndHigh) == 1 540 ) 541 542 var extrastar uint64 543 if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { 544 start = r.min 545 end = r.max 546 extrastar = starBit 547 } else { 548 start = parseIntOrName(lowAndHigh[0], r.names) 549 switch len(lowAndHigh) { 550 case 1: 551 end = start 552 case 2: 553 end = parseIntOrName(lowAndHigh[1], r.names) 554 default: 555 log.Panicf("Too many hyphens: %s", expr) 556 } 557 } 558 559 switch len(rangeAndStep) { 560 case 1: 561 step = 1 562 case 2: 563 step = mustParseInt(rangeAndStep[1]) 564 565 // Special handling: "N/step" means "N-max/step". 566 if singleDigit { 567 end = r.max 568 } 569 default: 570 log.Panicf("Too many slashes: %s", expr) 571 } 572 573 if start < r.min { 574 log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr) 575 } 576 if end > r.max { 577 log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr) 578 } 579 if start > end { 580 log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr) 581 } 582 583 return getBits(start, end, step) | extrastar 584 } 585 586 // parseIntOrName returns the (possibly-named) integer contained in expr. 587 func parseIntOrName(expr string, names map[string]uint) uint { 588 if names != nil { 589 if namedInt, ok := names[strings.ToLower(expr)]; ok { 590 return namedInt 591 } 592 } 593 return mustParseInt(expr) 594 } 595 596 // mustParseInt parses the given expression as an int or panics. 597 func mustParseInt(expr string) uint { 598 num, err := strconv.Atoi(expr) 599 if err != nil { 600 log.Panicf("Failed to parse int from %s: %s", expr, err) 601 } 602 if num < 0 { 603 log.Panicf("Negative number (%d) not allowed: %s", num, expr) 604 } 605 606 return uint(num) 607 } 608 609 // getBits sets all bits in the range [min, max], modulo the given step size. 610 func getBits(min, max, step uint) uint64 { 611 var bits uint64 612 613 // If step is 1, use shifts. 614 if step == 1 { 615 return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) 616 } 617 618 // Else, use a simple loop. 619 for i := min; i <= max; i += step { 620 bits |= 1 << i 621 } 622 return bits 623 } 624 625 // all returns all bits within the given bounds. (plus the star bit) 626 func all(r bounds) uint64 { 627 return getBits(r.min, r.max, 1) | starBit 628 } 629 630 func init() { 631 AdminTaskList = make(map[string]Tasker) 632 stop = make(chan bool) 633 changed = make(chan bool) 634 }