Skip to content

Commit ce0a501

Browse files
committed
feat: Add Race and RaceWithContext.
1 parent bb432b2 commit ce0a501

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

race.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"sync/atomic"
6+
)
7+
8+
func Race(funcs ...func(context.Context) error) error {
9+
return race(context.Background(), funcs...)
10+
}
11+
12+
func RaceWithContext(ctx context.Context, funcs ...func(context.Context) error) error {
13+
return race(ctx, funcs...)
14+
}
15+
16+
func race(ctx context.Context, funcs ...func(context.Context) error) error {
17+
if len(funcs) == 0 {
18+
return nil
19+
}
20+
21+
finished := atomic.Bool{}
22+
ch := make(chan error)
23+
defer close(ch)
24+
25+
for i := 0; i < len(funcs); i++ {
26+
fn := funcs[i]
27+
28+
go func() {
29+
err := fn(ctx)
30+
if finished.CompareAndSwap(false, true) {
31+
ch <- err
32+
}
33+
}()
34+
}
35+
36+
err := <-ch
37+
38+
return err
39+
}

race_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package async
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/ghosind/go-assert"
10+
)
11+
12+
func TestRaceWithoutFuncs(t *testing.T) {
13+
a := assert.New(t)
14+
15+
err := Race()
16+
a.NilNow(err)
17+
}
18+
19+
func TestRace(t *testing.T) {
20+
a := assert.New(t)
21+
22+
data := make([]bool, 5)
23+
funcs := make([]func(context.Context) error, 0, 5)
24+
for i := 0; i < 5; i++ {
25+
n := i
26+
funcs = append(funcs, func(ctx context.Context) error {
27+
time.Sleep(time.Duration((n+1)*50) * time.Millisecond)
28+
data[n] = true
29+
return nil
30+
})
31+
}
32+
33+
err := Race(funcs...)
34+
a.NilNow(err)
35+
a.EqualNow(data, []bool{true, false, false, false, false})
36+
37+
time.Sleep(300 * time.Millisecond)
38+
a.EqualNow(data, []bool{true, true, true, true, true})
39+
}
40+
41+
func TestRaceWithContext(t *testing.T) {
42+
a := assert.New(t)
43+
44+
data := make([]bool, 5)
45+
funcs := make([]func(context.Context) error, 0, 5)
46+
for i := 0; i < 5; i++ {
47+
n := i
48+
funcs = append(funcs, func(ctx context.Context) error {
49+
time.Sleep(time.Duration((n+1)*50) * time.Millisecond)
50+
select {
51+
case <-ctx.Done():
52+
return errors.New("timeout")
53+
default:
54+
data[n] = true
55+
return nil
56+
}
57+
})
58+
}
59+
60+
ctx, canFunc := context.WithTimeout(context.Background(), 170*time.Millisecond)
61+
defer canFunc()
62+
63+
err := RaceWithContext(ctx, funcs...)
64+
a.NilNow(err)
65+
a.EqualNow(data, []bool{true, false, false, false, false})
66+
67+
time.Sleep(300 * time.Millisecond)
68+
a.EqualNow(data, []bool{true, true, true, false, false})
69+
}

0 commit comments

Comments
 (0)