Skip to content

Commit 2742c98

Browse files
wanghaolong613hailazCopilot
authored
fix(os/gcron): unit testing case of package gcron occasionally failed (gogf#4419)
fix gogf#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>
1 parent 4226a23 commit 2742c98

6 files changed

Lines changed: 341 additions & 96 deletions

File tree

os/gcron/gcron.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,9 @@ func Stop(name ...string) {
125125
func StopGracefully() {
126126
defaultCron.StopGracefully()
127127
}
128+
129+
// StopGracefullyNonBlocking stops all running tasks gracefully without blocking,
130+
// returning a context that callers can use to wait for completion.
131+
func StopGracefullyNonBlocking() context.Context {
132+
return defaultCron.StopGracefullyNonBlocking()
133+
}

os/gcron/gcron_cron.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import (
2020

2121
// Cron stores all the cron job entries.
2222
type Cron struct {
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.
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+
loggerMu sync.RWMutex
28+
jobWaiter sync.WaitGroup // Graceful shutdown when cron jobs are stopped.
29+
running bool
30+
runningLock sync.Mutex
2831
}
2932

3033
// New returns a new Cron object with default settings.
@@ -33,16 +36,21 @@ func New() *Cron {
3336
idGen: gtype.NewInt64(),
3437
status: gtype.NewInt(StatusRunning),
3538
entries: gmap.NewStrAnyMap(true),
39+
running: true,
3640
}
3741
}
3842

3943
// SetLogger sets the logger for cron.
4044
func (c *Cron) SetLogger(logger glog.ILogger) {
45+
c.loggerMu.Lock()
46+
defer c.loggerMu.Unlock()
4147
c.logger = logger
4248
}
4349

4450
// GetLogger returns the logger in the cron.
4551
func (c *Cron) GetLogger() glog.ILogger {
52+
c.loggerMu.RLock()
53+
defer c.loggerMu.RUnlock()
4654
return c.logger
4755
}
4856

@@ -171,7 +179,10 @@ func (c *Cron) Start(name ...string) {
171179
}
172180
}
173181
} else {
182+
c.runningLock.Lock()
174183
c.status.Set(StatusReady)
184+
c.running = true
185+
c.runningLock.Unlock()
175186
}
176187
}
177188

@@ -185,14 +196,32 @@ func (c *Cron) Stop(name ...string) {
185196
}
186197
}
187198
} else {
199+
c.runningLock.Lock()
188200
c.status.Set(StatusStopped)
201+
c.running = false
202+
c.runningLock.Unlock()
189203
}
190204
}
191205

192206
// StopGracefully Blocks and waits all current running jobs done.
193207
func (c *Cron) StopGracefully() {
194-
c.status.Set(StatusStopped)
195-
c.jobWaiter.Wait()
208+
ctx := c.StopGracefullyNonBlocking()
209+
<-ctx.Done()
210+
}
211+
212+
// StopGracefullyNonBlocking stops all running tasks gracefully without blocking,
213+
// returning a context that callers can use to wait for completion.
214+
func (c *Cron) StopGracefullyNonBlocking() context.Context {
215+
ctx, cancel := context.WithCancel(context.Background())
216+
go func() {
217+
c.runningLock.Lock()
218+
defer c.runningLock.Unlock()
219+
c.status.Set(StatusStopped)
220+
c.running = false
221+
c.jobWaiter.Wait()
222+
cancel()
223+
}()
224+
return ctx
196225
}
197226

198227
// Remove deletes scheduled task which named `name`.
@@ -204,7 +233,10 @@ func (c *Cron) Remove(name string) {
204233

205234
// Close stops and closes current cron.
206235
func (c *Cron) Close() {
236+
c.runningLock.Lock()
237+
defer c.runningLock.Unlock()
207238
c.status.Set(StatusClosed)
239+
c.running = false
208240
}
209241

210242
// Size returns the size of the timed tasks.

os/gcron/gcron_entry.go

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

154154
case StatusReady, StatusRunning:
155-
e.cron.jobWaiter.Add(1)
155+
e.cron.runningLock.Lock()
156+
if e.cron.running {
157+
e.cron.jobWaiter.Add(1)
158+
} else {
159+
e.cron.runningLock.Unlock()
160+
e.logDebugf(ctx, `cron job "%s" stoped or closed`, e.getJobNameWithPattern())
161+
return
162+
}
163+
e.cron.runningLock.Unlock()
156164
defer func() {
157165
e.cron.jobWaiter.Done()
158166
if exception := recover(); exception != nil {

os/gcron/gcron_z_example_1_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func ExampleCron_gracefulShutdown() {
3131
g.Log().Debug(ctx, "Every 2s job start")
3232
time.Sleep(5 * time.Second)
3333
g.Log().Debug(ctx, "Every 2s job after 5 second end")
34-
}, "MyCronJob")
34+
}, "MyCronJob1")
3535
if err != nil {
3636
panic(err)
3737
}
@@ -46,3 +46,25 @@ func ExampleCron_gracefulShutdown() {
4646
gcron.StopGracefully()
4747
glog.Print(ctx, "All cron jobs completed")
4848
}
49+
50+
func ExampleCron_StopGracefullyNonBlocking() {
51+
_, err := gcron.Add(ctx, "*/2 * * * * *", func(ctx context.Context) {
52+
g.Log().Debug(ctx, "Every 2s job start")
53+
time.Sleep(5 * time.Second)
54+
g.Log().Debug(ctx, "Every 2s job after 5 second end")
55+
}, "MyCronJob2")
56+
if err != nil {
57+
panic(err)
58+
}
59+
60+
quit := make(chan os.Signal, 1)
61+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
62+
63+
sig := <-quit
64+
glog.Printf(ctx, "Signal received: %s, stopping cron", sig)
65+
66+
glog.Print(ctx, "Waiting for all cron jobs to complete...")
67+
ctx := gcron.StopGracefullyNonBlocking()
68+
<-ctx.Done()
69+
glog.Print(ctx, "All cron jobs completed")
70+
}

os/gcron/gcron_z_unit_entry_test.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ func TestCron_Entry_Operations(t *testing.T) {
2222
cron = gcron.New()
2323
array = garray.New(true)
2424
)
25-
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
26-
array.Append(1)
27-
})
28-
t.Assert(cron.Size(), 0)
25+
go func() {
26+
cron.DelayAddTimes(ctx, 500*time.Millisecond, "* * * * * *", 2, func(ctx context.Context) {
27+
array.Append(1)
28+
})
29+
t.Assert(cron.Size(), 0)
30+
}()
2931
time.Sleep(800 * time.Millisecond)
3032
t.Assert(array.Len(), 0)
3133
t.Assert(cron.Size(), 1)
@@ -39,12 +41,16 @@ func TestCron_Entry_Operations(t *testing.T) {
3941
cron = gcron.New()
4042
array = garray.New(true)
4143
)
42-
entry, err1 := cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
43-
array.Append(1)
44-
})
45-
t.Assert(err1, nil)
46-
t.Assert(array.Len(), 0)
47-
t.Assert(cron.Size(), 1)
44+
var entry *gcron.Entry
45+
go func() {
46+
var err error
47+
entry, err = cron.Add(ctx, "* * * * * *", func(ctx context.Context) {
48+
array.Append(1)
49+
})
50+
t.Assert(err, nil)
51+
t.Assert(array.Len(), 0)
52+
t.Assert(cron.Size(), 1)
53+
}()
4854
time.Sleep(1300 * time.Millisecond)
4955
t.Assert(array.Len(), 1)
5056
t.Assert(cron.Size(), 1)

0 commit comments

Comments
 (0)