cron with recover

WithChain

Job 包装器可以在执行实际的Job前后添加一些逻辑:

  • 捕获panic
  • 如果Job上次运行还未结束,推迟本次执行;
  • 如果Job上次运行还未介绍,跳过本次执行;
  • 记录每个Job的执行情况。

可以将Chain类比为 Web 处理器的中间件。实际上就是在Job的执行逻辑外在封装一层逻辑。我们的封装逻辑需要写成一个函数,传入一个Job类型,返回封装后的Jobcron为这种函数定义了一个类型JobWrapper


// chain.go
type JobWrapper func(Job) Job

然后使用一个Chain对象将这些JobWrapper组合到一起:

type Chain struct {
  wrappers []JobWrapper
}

func NewChain(c ...JobWrapper) Chain {
  return Chain{c}
}

调用Chain对象的Then(job)方法应用这些JobWrapper,返回最终的`Job:

// Then decorates the given job with all JobWrappers in the chain.
//
// This:
//     NewChain(m1, m2, m3).Then(job)
// is equivalent to:
//     m1(m2(m3(job)))
func (c Chain) Then(j Job) Job {
	for i := range c.wrappers {
		j = c.wrappers[len(c.wrappers)-i-1](j)
	}
	return j
}
注意应用JobWrapper的顺序。

内置JobWrapper

cron内置了 3 个用得比较多的JobWrapper

  • Recover:捕获内部Job产生的 panic;
  • DelayIfStillRunning:触发时,如果上一次任务还未执行完成(耗时太长),则等待上一次任务完成之后再执行;
  • SkipIfStillRunning:触发时,如果上一次任务还未完成,则跳过此次执行。

目前一般都自己实现一个Recover job

比如:


func CronRecover(j cron.Job) cron.Job {
	return cron.FuncJob(func() {
		defer func() {
			if r := recover(); r != nil {
				log.Errorf("panic in job on: %v, stack: %s", r, string(debug.Stack()))
			}
		}()
		j.Run()
	})
}

 

package main

import (
    "fmt"
    "github.com/robfig/cron/v3"
    "runtime"
)

func Stack() []byte {
    buf := make([]byte, 1024)
    for {
        n := runtime.Stack(buf, false)
        if n < len(buf) {
            return buf[:n]
        }
        buf = make([]byte, 2*len(buf))
    }
}

func CronRecover(j cron.Job) cron.Job {
    return cron.FuncJob(func() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("panic in job on: %v, stack: %s", r, string(Stack()))
            }
        }()
        fmt.Printf("Executing recover---------\n")
        j.Run()
    })
}
func list(j cron.Job) cron.Job {
    return cron.FuncJob(func() {
        fmt.Printf("Executing func list \n")
        j.Run()
    })
}
func func1() {
    fmt.Printf("Executing func1 for job ID \n")
}
func func2() {
    fmt.Printf("Executing func2 for job ID \n")
}

func main() {

    c := cron.New(cron.WithChain(CronRecover,list))
    _, _ = c.AddFunc("@every 5s", func1)
    _, _ = c.AddFunc("@every 5s", func2)
    c.Start()
    select {}
}

结果是:

Executing recover---------
Executing func list 
Executing func2 for job ID 
Executing recover---------
Executing func list 
Executing func1 for job ID 



Executing recover---------
Executing func list 
Executing func2 for job ID 
Executing recover---------
Executing func list 
Executing func1 for job ID 
^Csignal: interrupt

目前cron自带的recover 调用方式为:

type panicJob struct {
  count int
}

func (p *panicJob) Run() {
  p.count++
  if p.count == 1 {
    panic("oooooooooooooops!!!")
  }

  fmt.Println("hello world")
}

func main() {
  c := cron.New()
  c.AddJob("@every 1s", cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(&panicJob{}))
  c.Start()

  time.Sleep(5 * time.Second)
}

 chain.go

// Recover panics in wrapped jobs and log them with the provided logger.
func Recover(logger Logger) JobWrapper {
	return func(j Job) Job {
		return FuncJob(func() {
			defer func() {
				if r := recover(); r != nil {
					const size = 64 << 10
					buf := make([]byte, size)
					buf = buf[:runtime.Stack(buf, false)]
					err, ok := r.(error)
					if !ok {
						err = fmt.Errorf("%v", r)
					}
					logger.Error(err, "panic", "stack", "...\n"+string(buf))
				}
			}()
			j.Run()
		})
	}
}

 

 

Job接口

除了直接将无参函数作为回调外,cron还支持Job接口:

 

// cron.go
type Job interface {
  Run()
}

我们定义一个实现接口Job的结构:

 

type GreetingJob struct {
  Name string
}

func (g GreetingJob) Run() {
  fmt.Println("Hello ", g.Name)
}

调用cron对象的AddJob()方法将GreetingJob对象添加到定时管理器中:

 

func main() {
  c := cron.New()
  c.AddJob("@every 1s", GreetingJob{"dj"})
  c.Start()

  time.Sleep(5 * time.Second)
}

运行效果:

 

$ go run main.go 
Hello  dj
Hello  dj
Hello  dj
Hello  dj
Hello  dj

使用自定义的结构可以让任务携带状态(Name字段)。

实际上AddFunc()方法内部也调用了AddJob()方法。首先,cron基于func()类型定义一个新的类型FuncJob

 

// cron.go
type FuncJob func()

然后让FuncJob实现Job接口:

 

// cron.go
func (f FuncJob) Run() {
  f()
}

AddFunc()方法中,将传入的回调转为FuncJob类型,然后调用AddJob()方法:

 

func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
  return c.AddJob(spec, FuncJob(cmd))

 

posted @   codestacklinuxer  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
历史上的今天:
2021-05-30 转载 virtio 网络的演化
2021-05-30 转载 Linux虚拟化KVM-Qemu分析virtqueue
2021-05-30 转载 虚拟化KVM-Qemu分析 之virtio设备
点击右上角即可分享
微信分享提示