Skip to content

Commit 8824b8b

Browse files
authored
os/gcron: add graceful shutdown support (#3625)
1 parent f8272bc commit 8824b8b

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

os/gcron/gcron.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,8 @@ func Start(name ...string) {
120120
func Stop(name ...string) {
121121
defaultCron.Stop(name...)
122122
}
123+
124+
// StopGracefully Blocks and waits all current running jobs done.
125+
func StopGracefully() {
126+
defaultCron.StopGracefully()
127+
}

os/gcron/gcron_cron.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package gcron
88

99
import (
1010
"context"
11+
"sync"
1112
"time"
1213

1314
"github.com/gogf/gf/v2/container/garray"
@@ -19,10 +20,11 @@ import (
1920

2021
// Cron stores all the cron job entries.
2122
type Cron struct {
22-
idGen *gtype.Int64 // Used for unique name generation.
23-
status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed)
24-
entries *gmap.StrAnyMap // All timed task entries.
25-
logger glog.ILogger // Logger, it is nil in default.
23+
idGen *gtype.Int64 // Used for unique name generation.
24+
status *gtype.Int // Timed task status(0: Not Start; 1: Running; 2: Stopped; -1: Closed)
25+
entries *gmap.StrAnyMap // All timed task entries.
26+
logger glog.ILogger // Logger, it is nil in default.
27+
jobWaiter sync.WaitGroup // Graceful shutdown when cron jobs are stopped.
2628
}
2729

2830
// New returns a new Cron object with default settings.
@@ -187,6 +189,12 @@ func (c *Cron) Stop(name ...string) {
187189
}
188190
}
189191

192+
// StopGracefully Blocks and waits all current running jobs done.
193+
func (c *Cron) StopGracefully() {
194+
c.status.Set(StatusStopped)
195+
c.jobWaiter.Wait()
196+
}
197+
190198
// Remove deletes scheduled task which named `name`.
191199
func (c *Cron) Remove(name string) {
192200
if v := c.entries.Get(name); v != nil {

os/gcron/gcron_entry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ func (e *Entry) checkAndRun(ctx context.Context) {
152152
e.Close()
153153

154154
case StatusReady, StatusRunning:
155+
e.cron.jobWaiter.Add(1)
155156
defer func() {
157+
e.cron.jobWaiter.Done()
156158
if exception := recover(); exception != nil {
157159
// Exception caught, it logs the error content to logger in default behavior.
158160
e.logErrorf(ctx,

os/gcron/gcron_z_example_1_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ package gcron_test
88

99
import (
1010
"context"
11+
"os"
12+
"os/signal"
13+
"syscall"
1114
"time"
1215

16+
"github.com/gogf/gf/v2/frame/g"
1317
"github.com/gogf/gf/v2/os/gcron"
1418
"github.com/gogf/gf/v2/os/glog"
1519
)
@@ -21,3 +25,24 @@ func ExampleCronAddSingleton() {
2125
})
2226
select {}
2327
}
28+
29+
func ExampleCronGracefulShutdown() {
30+
_, err := gcron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
31+
g.Log().Debug(ctx, "Every 2s job start")
32+
time.Sleep(5 * time.Second)
33+
g.Log().Debug(ctx, "Every 2s job after 5 second end")
34+
}, "MyCronJob")
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
quit := make(chan os.Signal, 1)
40+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
41+
42+
sig := <-quit
43+
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
44+
45+
glog.Print(ctx, "Waiting for all cron jobs to complete...")
46+
gcron.StopGracefully()
47+
glog.Print(ctx, "All cron jobs completed")
48+
}

os/gcron/gcron_z_unit_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ package gcron_test
99
import (
1010
"context"
1111
"fmt"
12+
"os"
13+
"os/signal"
14+
"syscall"
1215
"testing"
1316
"time"
1417

1518
"github.com/gogf/gf/v2/container/garray"
1619
"github.com/gogf/gf/v2/frame/g"
1720
"github.com/gogf/gf/v2/os/gcron"
21+
"github.com/gogf/gf/v2/os/glog"
1822
"github.com/gogf/gf/v2/test/gtest"
1923
)
2024

@@ -277,3 +281,41 @@ func TestCron_DelayAddTimes(t *testing.T) {
277281
t.Assert(cron.Size(), 0)
278282
})
279283
}
284+
285+
func TestCron_JobWaiter(t *testing.T) {
286+
gtest.C(t, func(t *gtest.T) {
287+
var err error
288+
s1 := garray.New(true)
289+
s2 := garray.New(true)
290+
_, err = gcron.Add(ctx, "* * * * * *", func(ctx context.Context) {
291+
g.Log().Debug(ctx, "Every second")
292+
s1.Append(struct{}{})
293+
}, "MyFirstCronJob")
294+
t.Assert(err, nil)
295+
_, err = gcron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
296+
g.Log().Debug(ctx, "Every 2s job start")
297+
time.Sleep(3 * time.Second)
298+
s2.Append(struct{}{})
299+
g.Log().Debug(ctx, "Every 2s job after 3 second end")
300+
}, "MySecondCronJob")
301+
t.Assert(err, nil)
302+
303+
quit := make(chan os.Signal, 1)
304+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
305+
306+
go func() {
307+
time.Sleep(4 * time.Second) // Ensure that the job is triggered twice
308+
glog.Print(ctx, "Sending SIGINT")
309+
quit <- syscall.SIGINT // Send SIGINT
310+
}()
311+
312+
sig := <-quit
313+
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
314+
315+
glog.Print(ctx, "Waiting for all cron jobs to complete...")
316+
gcron.StopGracefully()
317+
glog.Print(ctx, "All cron jobs completed")
318+
t.Assert(s1.Len(), 4)
319+
t.Assert(s2.Len(), 2)
320+
})
321+
}

0 commit comments

Comments
 (0)