Skip to content

Commit 9e1642a

Browse files
authored
Merge pull request #28 from donutloop/opt/fifo
schedule: optimized spawning of goroutines
2 parents de9cf5b + d2f33e3 commit 9e1642a

File tree

2 files changed

+70
-19
lines changed

2 files changed

+70
-19
lines changed

schedule/schedule.go

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ func (f *Fifo) run() {
110110
close(f.resume)
111111
}()
112112

113+
async := newAsync(f.ctx, f.PanicHandler)
114+
113115
for {
114116
var job Job
115117
f.mu.Lock()
@@ -128,15 +130,13 @@ func (f *Fifo) run() {
128130
f.mu.Unlock()
129131
// clean up pending jobs
130132
for _, job := range pendings {
131-
done := asyncDo(f.ctx, f.PanicHandler, job)
132-
<-done
133+
async.Do(job)
133134
}
135+
async.Close()
134136
return
135137
}
136138
} else {
137-
done := asyncDo(f.ctx, f.PanicHandler, job)
138-
<-done
139-
139+
async.Do(job)
140140
f.finishCond.L.Lock()
141141
f.finished++
142142
f.pendings = f.pendings[1:]
@@ -146,18 +146,45 @@ func (f *Fifo) run() {
146146
}
147147
}
148148

149-
// AsyncDo is a basic promise implementation: it wraps calls a function in a goroutine
150-
func asyncDo(ctx context.Context, panicHandler func(stack DebugStack), f func(ctx context.Context)) <-chan struct{} {
151-
ch := make(chan struct{}, 1)
152-
go func(ch chan struct{}, ctx context.Context, panicHandler func(stack DebugStack)) {
153-
defer func() {
154-
if err := recover(); err != nil {
155-
panicHandler(DebugStack(debug.Stack()))
156-
}
157-
}()
149+
func newAsync(ctx context.Context, panicHandler func(stack DebugStack)) *async {
150+
a := &async{
151+
Ctx: ctx,
152+
PanicHandler: panicHandler,
153+
Jobs: make(chan func(ctx context.Context)),
154+
}
155+
a.init()
156+
return a
157+
}
158+
159+
type async struct {
160+
Ctx context.Context
161+
Jobs chan func(ctx context.Context)
162+
PanicHandler func(stack DebugStack)
163+
}
164+
165+
func (a *async) init() {
166+
go func(jobs chan func(ctx context.Context), ctx context.Context, panicHandler func(stack DebugStack)) {
167+
for job := range jobs {
168+
do(job, ctx, panicHandler)
169+
}
170+
}(a.Jobs, a.Ctx, a.PanicHandler)
171+
}
172+
173+
func (a *async) Do(f func(ctx context.Context)) {
174+
a.Jobs <- f
175+
}
176+
177+
func (a *async) Close() {
178+
close(a.Jobs)
179+
}
180+
181+
func do(job func(ctx context.Context), ctx context.Context, panicHandler func(stack DebugStack)) {
182+
183+
defer func() {
184+
if err := recover(); err != nil {
185+
panicHandler(DebugStack(debug.Stack()))
186+
}
187+
}()
158188

159-
f(ctx)
160-
ch <- struct{}{}
161-
}(ch, ctx, panicHandler)
162-
return ch
189+
job(ctx)
163190
}

schedule/schedule_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"github.com/donutloop/toolkit/schedule"
1010
"testing"
11+
"time"
1112
)
1213

1314
func TestFIFOSchedule(t *testing.T) {
@@ -40,6 +41,30 @@ func TestFIFOSchedule(t *testing.T) {
4041
}
4142
}
4243

44+
func TestFIFOScheduleCtx(t *testing.T) {
45+
s := schedule.NewFIFOScheduler()
46+
47+
s.Schedule(func(ctx context.Context) {
48+
<-time.After(250 * time.Millisecond)
49+
err := ctx.Err()
50+
if err == nil {
51+
t.Fatal("unexpected nil error")
52+
}
53+
54+
expectedErrorMessage := "context canceled"
55+
if err.Error() != expectedErrorMessage {
56+
t.Fatal(err)
57+
}
58+
})
59+
60+
s.Stop()
61+
62+
expectedJobCount := 1
63+
if s.Scheduled() != expectedJobCount {
64+
t.Fatalf("scheduled (actual: %d, expected: %d)", s.Scheduled(), expectedJobCount)
65+
}
66+
}
67+
4368
func BenchmarkFIFOSchedule(b *testing.B) {
4469
for n := 0; n < b.N; n++ {
4570
s := schedule.NewFIFOScheduler()
@@ -69,5 +94,4 @@ func BenchmarkFIFOSchedule(b *testing.B) {
6994
}
7095
s.Stop()
7196
}
72-
7397
}

0 commit comments

Comments
 (0)