Skip to content

Commit e6d7036

Browse files
committed
better testing
1 parent f08bd7c commit e6d7036

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

internal/worker/concurrency.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"context"
2525
"fmt"
2626
"sync"
27+
"time"
2728

2829
"github.com/marusama/semaphore/v2"
2930
)
@@ -56,14 +57,16 @@ func NewPermit(initCount int) Permit {
5657
}
5758

5859
// Acquire is blocking until a permit is acquired or returns error after context is done
60+
// Remember to call Release(count) to release the permit after usage
5961
func (p *permit) Acquire(ctx context.Context, count int) error {
6062
if err := p.sem.Acquire(ctx, count); err != nil {
6163
return fmt.Errorf("failed to acquire permit before context is done: %w", err)
6264
}
6365
return nil
6466
}
6567

66-
// AcquireChan returns a permit ready channel. It's closed then permit is acquired
68+
// AcquireChan returns a permit ready channel. Similar to Acquire, but non-blocking.
69+
// Remember to call Release(1) to release the permit after usage
6770
func (p *permit) AcquireChan(ctx context.Context, wg *sync.WaitGroup) <-chan struct{} {
6871
ch := make(chan struct{})
6972
wg.Add(1)
@@ -74,7 +77,7 @@ func (p *permit) AcquireChan(ctx context.Context, wg *sync.WaitGroup) <-chan str
7477
}
7578
select { // try to send to channel, but don't block if listener is gone
7679
case ch <- struct{}{}:
77-
default:
80+
case <-time.After(10 * time.Millisecond): // wait time is needed to avoid race condition of channel sending
7881
p.sem.Release(1)
7982
}
8083
}()

internal/worker/concurrency_test.go

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,50 +36,58 @@ func TestPermit_Simulation(t *testing.T) {
3636
tests := []struct {
3737
name string
3838
capacity []int // update every 50ms
39-
goroutines int // each would block on acquiring 2-6 tokens for 100ms
40-
goroutinesAcquireChan int // each would block using AcquireChan for 100ms
39+
goroutines int // each would block on acquiring 1-2 tokens for 100-150ms
40+
goroutinesAcquireChan int // each would block using AcquireChan for 100-150ms
4141
maxTestDuration time.Duration
42-
expectFailures int
43-
expectFailuresAtLeast int
42+
expectFailuresRange []int // range of failures, inclusive [min, max]
4443
}{
4544
{
4645
name: "enough permit, no blocking",
47-
maxTestDuration: 100 * time.Millisecond,
48-
capacity: []int{1000},
46+
maxTestDuration: 200 * time.Millisecond, // at most need 150 ms, add 50 ms buffer
47+
capacity: []int{10000},
4948
goroutines: 100,
5049
goroutinesAcquireChan: 100,
51-
expectFailures: 0,
50+
expectFailuresRange: []int{0, 0},
5251
},
5352
{
5453
name: "not enough permit, blocking but all acquire",
55-
maxTestDuration: 1 * time.Second,
54+
maxTestDuration: 1200 * time.Millisecond, // at most need 150ms * (1000 + 500) / 200 = 1125ms to acquire all permit
5655
capacity: []int{200},
57-
goroutines: 500,
58-
goroutinesAcquireChan: 500,
59-
expectFailures: 0,
56+
goroutines: 500, // at most 1000 tokens
57+
goroutinesAcquireChan: 500, // 500 tokens
58+
expectFailuresRange: []int{0, 0},
6059
},
6160
{
6261
name: "not enough permit for some to acquire, fail some",
63-
maxTestDuration: 300 * time.Millisecond,
64-
capacity: []int{100},
62+
maxTestDuration: 250 * time.Millisecond, // at least need 100ms * (500 + 500) / 200 = 250ms to acquire all permit
63+
capacity: []int{200},
6564
goroutines: 500,
6665
goroutinesAcquireChan: 500,
67-
expectFailuresAtLeast: 1,
66+
expectFailuresRange: []int{1, 999}, // should at least pass some acquires
6867
},
6968
{
7069
name: "not enough permit at beginning but due to capacity change, blocking but all acquire",
71-
maxTestDuration: 300 * time.Millisecond,
72-
capacity: []int{100, 300, 500},
70+
maxTestDuration: 250 * time.Millisecond,
71+
capacity: []int{200, 400, 600},
72+
goroutines: 500,
73+
goroutinesAcquireChan: 500,
74+
expectFailuresRange: []int{0, 0},
75+
},
76+
{
77+
name: "enough permit at beginning but due to capacity change, some would fail",
78+
maxTestDuration: 250 * time.Millisecond,
79+
capacity: []int{600, 400, 200},
7380
goroutines: 500,
7481
goroutinesAcquireChan: 500,
75-
expectFailures: 0,
82+
expectFailuresRange: []int{1, 999},
7683
},
7784
{
78-
name: "not enough permit for any acquire, fail all",
79-
maxTestDuration: 300 * time.Millisecond,
80-
capacity: []int{0},
81-
goroutines: 1000,
82-
expectFailures: 1000,
85+
name: "not enough permit for any acquire, fail all",
86+
maxTestDuration: 300 * time.Millisecond,
87+
capacity: []int{0},
88+
goroutines: 500,
89+
goroutinesAcquireChan: 500,
90+
expectFailuresRange: []int{1000, 1000},
8391
},
8492
}
8593

@@ -102,13 +110,12 @@ func TestPermit_Simulation(t *testing.T) {
102110
wg.Add(1)
103111
go func() {
104112
defer wg.Done()
105-
num := rand.Intn(2) + 2
106-
// num := 1
113+
num := rand.Intn(2) + 1
107114
if err := permit.Acquire(ctx, num); err != nil {
108-
failures.Add(1)
115+
failures.Inc()
109116
return
110117
}
111-
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
118+
time.Sleep(time.Duration(100+rand.Intn(50)) * time.Millisecond)
112119
permit.Release(num)
113120
}()
114121
}
@@ -118,22 +125,24 @@ func TestPermit_Simulation(t *testing.T) {
118125
defer wg.Done()
119126
select {
120127
case <-permit.AcquireChan(ctx, wg):
121-
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
128+
time.Sleep(time.Duration(100+rand.Intn(50)) * time.Millisecond)
122129
permit.Release(1)
123130
case <-ctx.Done():
124-
failures.Add(1)
131+
failures.Inc()
125132
}
126133
}()
127134
}
128135

129136
wg.Wait()
137+
// sanity check
130138
assert.Equal(t, 0, permit.Count())
131-
if tt.expectFailuresAtLeast > 0 {
132-
assert.LessOrEqual(t, tt.expectFailuresAtLeast, int(failures.Load()))
133-
} else {
134-
assert.Equal(t, tt.expectFailures, int(failures.Load()))
135-
}
136139
assert.Equal(t, tt.capacity[len(tt.capacity)-1], permit.Quota())
140+
141+
// expect failures in range
142+
expectFailureMin := tt.expectFailuresRange[0]
143+
expectFailureMax := tt.expectFailuresRange[1]
144+
assert.GreaterOrEqual(t, int(failures.Load()), expectFailureMin)
145+
assert.LessOrEqual(t, int(failures.Load()), expectFailureMax)
137146
})
138147
}
139148
}

0 commit comments

Comments
 (0)