fix(os/gcron): unit testing case of package gcron occasionally failed (#4419)

fix https://github.com/gogf/gf/issues/3999

1. fix  jobWaiter   sync.WaitGroup  data race
2. fix logger      glog.ILogger data race

---------

Co-authored-by: hailaz <739476267@qq.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
613
2025-10-15 15:17:05 +08:00
committed by GitHub
parent 4226a23a39
commit 2742c98c06
6 changed files with 344 additions and 99 deletions

View File

@ -125,3 +125,9 @@ func Stop(name ...string) {
func StopGracefully() {
defaultCron.StopGracefully()
}
// StopGracefullyNonBlocking stops all running tasks gracefully without blocking,
// returning a context that callers can use to wait for completion.
func StopGracefullyNonBlocking() context.Context {
return defaultCron.StopGracefullyNonBlocking()
}

View File

@ -20,11 +20,14 @@ import (
// Cron stores all the cron job entries.
type Cron struct {
idGen *gtype.Int64 // Used for unique name generation.
status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed)
entries *gmap.StrAnyMap // All timed task entries.
logger glog.ILogger // Logger, it is nil in default.
jobWaiter sync.WaitGroup // Graceful shutdown when cron jobs are stopped.
idGen *gtype.Int64 // Used for unique name generation.
status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed)
entries *gmap.StrAnyMap // All timed task entries.
logger glog.ILogger // Logger, it is nil in default.
loggerMu sync.RWMutex
jobWaiter sync.WaitGroup // Graceful shutdown when cron jobs are stopped.
running bool
runningLock sync.Mutex
}
// New returns a new Cron object with default settings.
@ -33,16 +36,21 @@ func New() *Cron {
idGen: gtype.NewInt64(),
status: gtype.NewInt(StatusRunning),
entries: gmap.NewStrAnyMap(true),
running: true,
}
}
// SetLogger sets the logger for cron.
func (c *Cron) SetLogger(logger glog.ILogger) {
c.loggerMu.Lock()
defer c.loggerMu.Unlock()
c.logger = logger
}
// GetLogger returns the logger in the cron.
func (c *Cron) GetLogger() glog.ILogger {
c.loggerMu.RLock()
defer c.loggerMu.RUnlock()
return c.logger
}
@ -171,7 +179,10 @@ func (c *Cron) Start(name ...string) {
}
}
} else {
c.runningLock.Lock()
c.status.Set(StatusReady)
c.running = true
c.runningLock.Unlock()
}
}
@ -185,14 +196,32 @@ func (c *Cron) Stop(name ...string) {
}
}
} else {
c.runningLock.Lock()
c.status.Set(StatusStopped)
c.running = false
c.runningLock.Unlock()
}
}
// StopGracefully Blocks and waits all current running jobs done.
func (c *Cron) StopGracefully() {
c.status.Set(StatusStopped)
c.jobWaiter.Wait()
ctx := c.StopGracefullyNonBlocking()
<-ctx.Done()
}
// StopGracefullyNonBlocking stops all running tasks gracefully without blocking,
// returning a context that callers can use to wait for completion.
func (c *Cron) StopGracefullyNonBlocking() context.Context {
ctx, cancel := context.WithCancel(context.Background())
go func() {
c.runningLock.Lock()
defer c.runningLock.Unlock()
c.status.Set(StatusStopped)
c.running = false
c.jobWaiter.Wait()
cancel()
}()
return ctx
}
// Remove deletes scheduled task which named `name`.
@ -204,7 +233,10 @@ func (c *Cron) Remove(name string) {
// Close stops and closes current cron.
func (c *Cron) Close() {
c.runningLock.Lock()
defer c.runningLock.Unlock()
c.status.Set(StatusClosed)
c.running = false
}
// Size returns the size of the timed tasks.

View File

@ -152,7 +152,15 @@ func (e *Entry) checkAndRun(ctx context.Context) {
e.Close()
case StatusReady, StatusRunning:
e.cron.jobWaiter.Add(1)
e.cron.runningLock.Lock()
if e.cron.running {
e.cron.jobWaiter.Add(1)
} else {
e.cron.runningLock.Unlock()
e.logDebugf(ctx, `cron job "%s" stoped or closed`, e.getJobNameWithPattern())
return
}
e.cron.runningLock.Unlock()
defer func() {
e.cron.jobWaiter.Done()
if exception := recover(); exception != nil {

View File

@ -31,7 +31,7 @@ func ExampleCron_gracefulShutdown() {
g.Log().Debug(ctx, "Every 2s job start")
time.Sleep(5 * time.Second)
g.Log().Debug(ctx, "Every 2s job after 5 second end")
}, "MyCronJob")
}, "MyCronJob1")
if err != nil {
panic(err)
}
@ -46,3 +46,25 @@ func ExampleCron_gracefulShutdown() {
gcron.StopGracefully()
glog.Print(ctx, "All cron jobs completed")
}
func ExampleCron_StopGracefullyNonBlocking() {
_, err := gcron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every 2s job start")
time.Sleep(5 * time.Second)
g.Log().Debug(ctx, "Every 2s job after 5 second end")
}, "MyCronJob2")
if err != nil {
panic(err)
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
sig := <-quit
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
glog.Print(ctx, "Waiting for all cron jobs to complete...")
ctx := gcron.StopGracefullyNonBlocking()
<-ctx.Done()
glog.Print(ctx, "All cron jobs completed")
}

View File

@ -22,10 +22,12 @@ func TestCron_Entry_Operations(t *testing.T) {
cron = gcron.New()
array = garray.New(true)
)
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
go func() {
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
}()
time.Sleep(800 * time.Millisecond)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
@ -39,12 +41,16 @@ func TestCron_Entry_Operations(t *testing.T) {
cron = gcron.New()
array = garray.New(true)
)
entry, err1 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(err1, nil)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
var entry *gcron.Entry
go func() {
var err error
entry, err = cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(err, nil)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
}()
time.Sleep(1300 * time.Millisecond)
t.Assert(array.Len(), 1)
t.Assert(cron.Size(), 1)

View File

@ -30,18 +30,23 @@ func TestCron_Add_Close(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
array := garray.New(true)
_, err1 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Print(ctx, "cron1")
array.Append(1)
})
_, err2 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Print(ctx, "cron2")
array.Append(1)
}, "test")
t.Assert(err1, nil)
t.Assert(err2, nil)
t.Assert(cron.Size(), 2)
g.Log().Debugf(ctx, "TestCron_Add_Close Add begin Start time %v ", time.Now().UnixMilli())
go func() {
_, err1 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Print(ctx, "cron1")
array.Append(1)
})
t.Assert(err1, nil)
}()
go func() {
_, err2 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Print(ctx, "cron2")
array.Append(1)
}, "test")
t.Assert(err2, nil)
}()
time.Sleep(1300 * time.Millisecond)
t.Assert(cron.Size(), 2)
t.Assert(array.Len(), 2)
time.Sleep(1300 * time.Millisecond)
t.Assert(array.Len(), 4)
@ -56,8 +61,8 @@ func TestCron_Add_Close(t *testing.T) {
func TestCron_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
cron.Add(ctx, "* * * * * *", func(ctx context.Context) {}, "add")
// fmt.Println("start", time.Now())
cron.DelayAdd(ctx, time.Second, "* * * * * *", func(ctx context.Context) {}, "delay_add")
t.Assert(cron.Size(), 1)
time.Sleep(1200 * time.Millisecond)
@ -93,17 +98,25 @@ func TestCron_Remove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
array := garray.New(true)
cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
}, "add")
t.Assert(array.Len(), 0)
go func() {
cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
}, "add")
t.Assert(array.Len(), 0)
}()
time.Sleep(1200 * time.Millisecond)
t.Assert(array.Len(), 1)
cron.Remove("add")
t.Assert(array.Len(), 1)
time.Sleep(1200 * time.Millisecond)
t.Assert(array.Len(), 1)
time.Sleep(1200 * time.Millisecond)
t.Assert(array.Len(), 1)
time.Sleep(1200 * time.Millisecond)
t.Assert(array.Len(), 1)
time.Sleep(1200 * time.Millisecond)
t.Assert(array.Len(), 1)
})
}
@ -122,17 +135,19 @@ func doTestCronAddFixedPattern(t *testing.T) {
expect = now.Add(time.Second * 2)
)
defer cron.Close()
var pattern = fmt.Sprintf(
`%d %d %d %d %d %s`,
expect.Second(), expect.Minute(), expect.Hour(), expect.Day(), expect.Month(), expect.Weekday().String(),
)
cron.SetLogger(g.Log())
g.Log().Debugf(ctx, `pattern: %s`, pattern)
_, err := cron.Add(ctx, pattern, func(ctx context.Context) {
array.Append(1)
})
t.AssertNil(err)
go func() {
g.Log().Debugf(ctx, `pattern: %s`, pattern)
_, err := cron.Add(ctx, pattern, func(ctx context.Context) {
g.Log().Debugf(ctx, `receive job`)
array.Append(1)
})
t.AssertNil(err)
}()
time.Sleep(3500 * time.Millisecond)
g.Log().Debug(ctx, `current time`)
t.Assert(array.Len(), 1)
@ -143,9 +158,12 @@ func TestCron_AddSingleton(t *testing.T) {
// un used, can be removed
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
cron.Add(ctx, "* * * * * *", func(ctx context.Context) {}, "add")
cron.DelayAdd(ctx, time.Second, "* * * * * *", func(ctx context.Context) {}, "delay_add")
t.Assert(cron.Size(), 1)
defer cron.Close()
go func() {
cron.Add(ctx, "* * * * * *", func(ctx context.Context) {}, "add")
cron.DelayAdd(ctx, time.Second, "* * * * * *", func(ctx context.Context) {}, "delay_add")
t.Assert(cron.Size(), 1)
}()
time.Sleep(1200 * time.Millisecond)
t.Assert(cron.Size(), 2)
@ -160,12 +178,15 @@ func TestCron_AddSingleton(t *testing.T) {
// keep this
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
time.Sleep(50 * time.Second)
})
t.Assert(cron.Size(), 1)
go func() {
cron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
time.Sleep(50 * time.Second)
})
t.Assert(cron.Size(), 1)
}()
time.Sleep(3500 * time.Millisecond)
t.Assert(array.Len(), 1)
})
@ -175,14 +196,17 @@ func TestCron_AddSingleton(t *testing.T) {
func TestCron_AddOnce1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.AddOnce(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
cron.AddOnce(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 2)
go func() {
cron.AddOnce(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
cron.AddOnce(ctx, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 2)
}()
time.Sleep(2500 * time.Millisecond)
t.Assert(array.Len(), 2)
t.Assert(cron.Size(), 0)
@ -192,11 +216,14 @@ func TestCron_AddOnce1(t *testing.T) {
func TestCron_AddOnce2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.AddOnce(ctx, "@every 2s", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 1)
go func() {
cron.AddOnce(ctx, "@every 2s", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 1)
}()
time.Sleep(3000 * time.Millisecond)
t.Assert(array.Len(), 1)
t.Assert(cron.Size(), 0)
@ -206,10 +233,13 @@ func TestCron_AddOnce2(t *testing.T) {
func TestCron_AddTimes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
_, _ = cron.AddTimes(ctx, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
go func() {
_, _ = cron.AddTimes(ctx, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
}()
time.Sleep(3500 * time.Millisecond)
t.Assert(array.Len(), 2)
t.Assert(cron.Size(), 0)
@ -219,11 +249,14 @@ func TestCron_AddTimes(t *testing.T) {
func TestCron_DelayAdd(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.DelayAdd(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
go func() {
cron.DelayAdd(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
}()
time.Sleep(800 * time.Millisecond)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
@ -236,12 +269,15 @@ func TestCron_DelayAdd(t *testing.T) {
func TestCron_DelayAddSingleton(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.DelayAddSingleton(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
time.Sleep(10 * time.Second)
})
t.Assert(cron.Size(), 0)
go func() {
cron.DelayAddSingleton(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
time.Sleep(10 * time.Second)
})
t.Assert(cron.Size(), 0)
}()
time.Sleep(2200 * time.Millisecond)
t.Assert(array.Len(), 1)
t.Assert(cron.Size(), 1)
@ -251,11 +287,14 @@ func TestCron_DelayAddSingleton(t *testing.T) {
func TestCron_DelayAddOnce(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.DelayAddOnce(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
go func() {
cron.DelayAddOnce(ctx, 500*time.Millisecond, "* * * * * *", func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
}()
time.Sleep(800 * time.Millisecond)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
@ -268,11 +307,14 @@ func TestCron_DelayAddOnce(t *testing.T) {
func TestCron_DelayAddTimes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
array := garray.New(true)
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
go func() {
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
array.Append(1)
})
t.Assert(cron.Size(), 0)
}()
time.Sleep(800 * time.Millisecond)
t.Assert(array.Len(), 0)
t.Assert(cron.Size(), 1)
@ -284,27 +326,30 @@ func TestCron_DelayAddTimes(t *testing.T) {
func TestCron_JobWaiter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var err error
s1 := garray.New(true)
s2 := garray.New(true)
_, err = gcron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every second")
s1.Append(struct{}{})
}, "MyFirstCronJob")
t.Assert(err, nil)
_, err = gcron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every 2s job start")
time.Sleep(3 * time.Second)
s2.Append(struct{}{})
g.Log().Debug(ctx, "Every 2s job after 3 second end")
}, "MySecondCronJob")
t.Assert(err, nil)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
cron := gcron.New()
defer cron.Close()
go func() {
time.Sleep(4 * time.Second) // Ensure that the job is triggered twice
_, err := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every second")
s1.Append(struct{}{})
}, "TestCronJobWaiterMyFirstCronJob")
t.Assert(err, nil)
}()
go func() {
_, err := cron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every 2s job start")
time.Sleep(3 * time.Second)
s2.Append(struct{}{})
g.Log().Debug(ctx, "Every 2s job after 3 second end")
}, "TestCronJobMySecondCronJob")
t.Assert(err, nil)
}()
go func() {
time.Sleep(4300 * time.Millisecond) // Ensure that the job is triggered twice
glog.Print(ctx, "Sending SIGINT")
quit <- syscall.SIGINT // Send SIGINT
}()
@ -312,10 +357,136 @@ func TestCron_JobWaiter(t *testing.T) {
sig := <-quit
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
glog.Print(ctx, "Waiting for all cron jobs to complete...")
gcron.StopGracefully()
glog.Print(ctx, "TestCron_JobWaiter Waiting for all cron jobs to complete...")
cron.StopGracefully()
glog.Print(ctx, "All cron jobs completed")
t.Assert(s1.Len(), 4)
t.Assert(s2.Len(), 2)
})
}
func TestCron_JobWaiterNonBlocking(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := garray.New(true)
s2 := garray.New(true)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
cron := gcron.New()
defer cron.Close()
go func() {
_, err := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every second start")
s1.Append(struct{}{})
g.Log().Debug(ctx, "Every second end ")
})
t.Assert(err, nil)
}()
go func() {
_, err := cron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every 2s job start ")
time.Sleep(3 * time.Second)
s2.Append(struct{}{})
g.Log().Debug(ctx, "Every 2s job after 3 second end ")
})
t.Assert(err, nil)
}()
go func() {
time.Sleep(4300 * time.Millisecond) // Ensure that the job is triggered twice
glog.Print(ctx, "Sending SIGINT")
quit <- syscall.SIGINT // Send SIGINT
}()
sig := <-quit
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
glog.Print(ctx, "TestCron_JobWaiterNonBlocking Waiting for all cron jobs to complete...")
ctx := cron.StopGracefullyNonBlocking()
<-ctx.Done()
glog.Print(ctx, "All cron jobs completed")
t.Assert(s1.Len(), 4)
t.Assert(s2.Len(), 2)
})
}
func TestCron_NoneJobsDoneImmediately(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
ctx := cron.StopGracefullyNonBlocking()
select {
case <-ctx.Done():
case <-time.After(110 * time.Millisecond):
t.Error("context was not done immediately")
}
})
}
func TestCron_RepeatedCallsStopGracefullyNonBlocking(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
_ = cron.StopGracefullyNonBlocking()
ctx := cron.StopGracefullyNonBlocking()
select {
case <-ctx.Done():
case <-time.After(110 * time.Millisecond):
t.Error("context was not done immediately")
}
})
}
func TestCron_RepeatedCallsStopGracefullyNonBlocking2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := garray.New(true)
s2 := garray.New(true)
cron := gcron.New()
defer cron.Close()
g.Log().Debugf(ctx, "TestCron_RepeatedCallsStopGracefullyNonBlocking2 Add begin Start time %v ", time.Now().UnixMilli())
go func() {
_, err := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every second")
s1.Append(struct{}{})
})
t.Assert(err, nil)
}()
go func() {
_, err := cron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every 2s job start")
time.Sleep(3 * time.Second)
s2.Append(struct{}{})
g.Log().Debug(ctx, "Every 2s job after 3 second end")
})
t.Assert(err, nil)
}()
time.Sleep(4300 * time.Millisecond)
g.Log().Debugf(ctx, "TestCron_RepeatedCallsStopGracefullyNonBlocking2 end time %v ", time.Now().UnixMilli())
glog.Print(ctx, "Waiting for all cron jobs to complete...")
_ = cron.StopGracefullyNonBlocking()
ctx := cron.StopGracefullyNonBlocking()
<-ctx.Done()
glog.Print(ctx, "All cron jobs completed")
t.Assert(s1.Len(), 4)
t.Assert(s2.Len(), 2)
})
}
func TestCron_StopGracefullyWithStopStart(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
cron := gcron.New()
defer cron.Close()
go func() {
_, err := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
g.Log().Debug(ctx, "Every second")
})
t.Assert(err, nil)
}()
time.Sleep(1300 * time.Millisecond)
cron.Stop()
cron.Start()
ctx := cron.StopGracefullyNonBlocking()
<-ctx.Done()
})
}