Skip to content

Commit ce84a6c

Browse files
committed
fix: handled panic
1 parent 11955d1 commit ce84a6c

File tree

4 files changed

+364
-145
lines changed

4 files changed

+364
-145
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require github.com/stretchr/testify v1.7.0
77
require (
88
github.com/davecgh/go-spew v1.1.1 // indirect
99
github.com/kr/text v0.2.0 // indirect
10+
go.uber.org/goleak v1.1.12 // indirect
1011
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
1112
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
1213
)

go.sum

+30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
33
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
44
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
56
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
67
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
78
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -13,7 +14,36 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
1314
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1415
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
1516
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
18+
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
19+
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
20+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
21+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
22+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
23+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
24+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
25+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
26+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
27+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
28+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
29+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
30+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
31+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
32+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
33+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
36+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
37+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
38+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
39+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
40+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
41+
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
42+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
43+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
44+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1645
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
46+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1747
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
1848
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
1949
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

mapreduce.go

+129-49
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package mapreduce
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"sync"
87
"sync/atomic"
98
)
@@ -21,12 +20,12 @@ var (
2120
)
2221

2322
type (
23+
// ForEachFunc is used to do element processing, but no output.
24+
ForEachFunc func(item interface{})
2425
// GenerateFunc is used to let callers send elements into source.
2526
GenerateFunc func(source chan<- interface{})
2627
// MapFunc is used to do element processing and write the output to writer.
2728
MapFunc func(item interface{}, writer Writer)
28-
// VoidMapFunc is used to do element processing, but no output.
29-
VoidMapFunc func(item interface{})
3029
// MapperFunc is used to do element processing and write the output to writer,
3130
// use cancel func to cancel the processing.
3231
MapperFunc func(item interface{}, writer Writer, cancel func(error))
@@ -39,6 +38,16 @@ type (
3938
// Option defines the method to customize the mapreduce.
4039
Option func(opts *mapReduceOptions)
4140

41+
mapperContext struct {
42+
ctx context.Context
43+
mapper MapFunc
44+
source <-chan interface{}
45+
panicChan *onceChan
46+
collector chan<- interface{}
47+
doneChan <-chan struct{}
48+
workers int
49+
}
50+
4251
mapReduceOptions struct {
4352
ctx context.Context
4453
workers int
@@ -76,7 +85,7 @@ func FinishVoid(fns ...func()) {
7685
return
7786
}
7887

79-
MapVoid(func(source chan<- interface{}) {
88+
ForEach(func(source chan<- interface{}) {
8089
for _, fn := range fns {
8190
source <- fn
8291
}
@@ -86,38 +95,72 @@ func FinishVoid(fns ...func()) {
8695
}, WithWorkers(len(fns)))
8796
}
8897

89-
// Map maps all elements generated from given generate func, and returns an output channel.
90-
func Map(generate GenerateFunc, mapper MapFunc, opts ...Option) chan interface{} {
98+
// ForEach maps all elements from given generate but no output.
99+
func ForEach(generate GenerateFunc, mapper ForEachFunc, opts ...Option) {
91100
options := buildOptions(opts...)
92-
source := buildSource(generate)
101+
panicChan := make(chan interface{})
102+
source := buildSource(generate, panicChan)
93103
collector := make(chan interface{}, options.workers)
94104
done := make(chan struct{})
95105

96-
go executeMappers(options.ctx, mapper, source, collector, done, options.workers)
106+
go executeMappers(mapperContext{
107+
ctx: options.ctx,
108+
mapper: func(item interface{}, writer Writer) {
109+
mapper(item)
110+
},
111+
source: source,
112+
panicChan: &onceChan{
113+
channel: panicChan,
114+
},
115+
collector: collector,
116+
doneChan: done,
117+
workers: options.workers,
118+
})
97119

98-
return collector
120+
for {
121+
select {
122+
case v := <-panicChan:
123+
panic(v)
124+
case _, ok := <-collector:
125+
if !ok {
126+
return
127+
}
128+
}
129+
}
99130
}
100131

101132
// MapReduce maps all elements generated from given generate func,
102133
// and reduces the output elements with given reducer.
103134
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc,
104135
opts ...Option) (interface{}, error) {
105-
source := buildSource(generate)
106-
return MapReduceWithSource(source, mapper, reducer, opts...)
136+
panicChan := make(chan interface{})
137+
source := buildSource(generate, panicChan)
138+
return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
107139
}
108140

109-
// MapReduceWithSource maps all elements from source, and reduce the output elements with given reducer.
110-
func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer ReducerFunc,
141+
// MapReduceChan maps all elements from source, and reduce the output elements with given reducer.
142+
func MapReduceChan(source <-chan interface{}, mapper MapperFunc, reducer ReducerFunc,
111143
opts ...Option) (interface{}, error) {
144+
panicChan := make(chan interface{})
145+
return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
146+
}
147+
148+
// MapReduceChan maps all elements from source, and reduce the output elements with given reducer.
149+
func mapReduceWithPanicChan(source <-chan interface{}, panicChan chan interface{}, mapper MapperFunc,
150+
reducer ReducerFunc, opts ...Option) (interface{}, error) {
112151
options := buildOptions(opts...)
152+
// output is used to write the final result
113153
output := make(chan interface{})
114154
defer func() {
155+
// reducer can only write once, if more, panic
115156
for range output {
116157
panic("more than one element written in reducer")
117158
}
118159
}()
119160

161+
// collector is used to collect data from mapper, and consume in reducer
120162
collector := make(chan interface{}, options.workers)
163+
// if done is closed, all mappers and reducer should stop processing
121164
done := make(chan struct{})
122165
writer := newGuardedWriter(options.ctx, output, done)
123166
var closeOnce sync.Once
@@ -142,30 +185,47 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
142185

143186
go func() {
144187
defer func() {
145-
drain(collector)
146-
147188
if r := recover(); r != nil {
148-
cancel(fmt.Errorf("%v", r))
189+
panicChan <- r
149190
} else {
150191
finish()
151192
}
193+
194+
drain(collector)
152195
}()
153196

154-
// callers need to make sure reducer not panic
155197
reducer(collector, writer, cancel)
156198
}()
157199

158-
go executeMappers(options.ctx, func(item interface{}, w Writer) {
159-
mapper(item, w, cancel)
160-
}, source, collector, done, options.workers)
161-
162-
value, ok := <-output
163-
if err := retErr.Load(); err != nil {
164-
return nil, err.(error)
165-
} else if ok {
166-
return value, nil
167-
} else {
168-
return nil, ErrReduceNoOutput
200+
go executeMappers(mapperContext{
201+
ctx: options.ctx,
202+
mapper: func(item interface{}, w Writer) {
203+
mapper(item, w, cancel)
204+
},
205+
source: source,
206+
panicChan: &onceChan{
207+
channel: panicChan,
208+
},
209+
collector: collector,
210+
doneChan: done,
211+
workers: options.workers,
212+
})
213+
214+
select {
215+
case <-options.ctx.Done():
216+
cancel(context.DeadlineExceeded)
217+
return nil, context.DeadlineExceeded
218+
case v := <-panicChan:
219+
finish()
220+
panic(v)
221+
case v, ok := <-output:
222+
if err := retErr.Load(); err != nil {
223+
return nil, err.(error)
224+
} else if ok {
225+
return v, nil
226+
} else {
227+
return nil, ErrReduceNoOutput
228+
}
169229
}
170230
}
171231

@@ -175,20 +235,12 @@ func MapReduceVoid(generate GenerateFunc, mapper MapperFunc, reducer VoidReducer
175235
_, err := MapReduce(generate, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
176236
reducer(input, cancel)
177237
// We need to write a placeholder to let MapReduce to continue on reducer done,
178-
// otherwise, all goroutines are waiting.
179-
// The placeholder will be discarded by MapReduce.
238+
// otherwise, all goroutines are waiting. The placeholder will be discarded by MapReduce.
180239
writer.Write(struct{}{})
181240
}, opts...)
182241
return err
183242
}
184243

185-
// MapVoid maps all elements from given generate but no output.
186-
func MapVoid(generate GenerateFunc, mapper VoidMapFunc, opts ...Option) {
187-
drain(Map(generate, func(item interface{}, writer Writer) {
188-
mapper(item)
189-
}, opts...))
190-
}
191-
192244
// WithContext customizes a mapreduce processing accepts a given ctx.
193245
func WithContext(ctx context.Context) Option {
194246
return func(opts *mapReduceOptions) {
@@ -216,10 +268,16 @@ func buildOptions(opts ...Option) *mapReduceOptions {
216268
return options
217269
}
218270

219-
func buildSource(generate GenerateFunc) chan interface{} {
271+
func buildSource(generate GenerateFunc, panicChan chan interface{}) chan interface{} {
220272
source := make(chan interface{})
221273
go func() {
222-
defer close(source)
274+
defer func() {
275+
if r := recover(); r != nil {
276+
panicChan <- r
277+
}
278+
close(source)
279+
}()
280+
223281
generate(source)
224282
}()
225283

@@ -233,24 +291,31 @@ func drain(channel <-chan interface{}) {
233291
}
234292
}
235293

236-
func executeMappers(ctx context.Context, mapper MapFunc, input <-chan interface{},
237-
collector chan<- interface{}, done <-chan struct{}, workers int) {
294+
func executeMappers(mCtx mapperContext) {
238295
var wg sync.WaitGroup
239296
defer func() {
240297
wg.Wait()
241-
close(collector)
298+
close(mCtx.collector)
299+
drain(mCtx.source)
242300
}()
243301

244-
pool := make(chan struct{}, workers)
245-
writer := newGuardedWriter(ctx, collector, done)
302+
pool := make(chan struct{}, mCtx.workers)
303+
innerPanicChan := make(chan interface{})
304+
onceInnerChan := &onceChan{
305+
channel: innerPanicChan,
306+
}
307+
writer := newGuardedWriter(mCtx.ctx, mCtx.collector, mCtx.doneChan)
246308
for {
247309
select {
248-
case <-ctx.Done():
310+
case <-mCtx.ctx.Done():
249311
return
250-
case <-done:
312+
case <-mCtx.doneChan:
313+
return
314+
case v := <-innerPanicChan:
315+
mCtx.panicChan.write(v)
251316
return
252317
case pool <- struct{}{}:
253-
item, ok := <-input
318+
item, ok := <-mCtx.source
254319
if !ok {
255320
<-pool
256321
return
@@ -259,12 +324,14 @@ func executeMappers(ctx context.Context, mapper MapFunc, input <-chan interface{
259324
wg.Add(1)
260325
go func() {
261326
defer func() {
327+
if r := recover(); r != nil {
328+
onceInnerChan.write(r)
329+
}
262330
wg.Done()
263331
<-pool
264332
}()
265333

266-
// callers need to make sure mapper won't panic
267-
mapper(item, writer)
334+
mCtx.mapper(item, writer)
268335
}()
269336
}
270337
}
@@ -311,3 +378,16 @@ func (gw guardedWriter) Write(v interface{}) {
311378
gw.channel <- v
312379
}
313380
}
381+
382+
type onceChan struct {
383+
channel chan<- interface{}
384+
wrote int32
385+
}
386+
387+
func (oc *onceChan) write(val interface{}) {
388+
if atomic.AddInt32(&oc.wrote, 1) > 1 {
389+
return
390+
}
391+
392+
oc.channel <- val
393+
}

0 commit comments

Comments
 (0)