| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package semaphore_test |
| |
| import ( |
| "context" |
| "errors" |
| "sync" |
| "sync/atomic" |
| "testing" |
| "time" |
| |
| "go.chromium.org/build/siso/sync/semaphore" |
| ) |
| |
| func TestWaitAcquire(t *testing.T) { |
| ctx := t.Context() |
| sema := semaphore.New(t.Name(), 3) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 0 { |
| t.Errorf("NumRequests=%d; want %d", n, 0) |
| } |
| |
| var dones []func(error) |
| for i := range 3 { |
| _, done, err := sema.WaitAcquire(ctx) |
| if err != nil { |
| t.Fatalf("WaitAcquire %d: %v", i, err) |
| } |
| dones = append(dones, done) |
| if n := sema.NumServs(); n != i+1 { |
| t.Errorf("NumServs=%d; want %d", n, i+1) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != i+1 { |
| t.Errorf("NumRequests=%d; want %d", n, i+1) |
| } |
| } |
| t.Logf("all acquired") |
| func() { |
| ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) |
| defer cancel() |
| _, _, err := sema.WaitAcquire(ctx) |
| if err == nil { |
| t.Fatalf("WaitAcquire ok; want err") |
| } |
| if n := sema.NumServs(); n != 3 { |
| t.Errorf("NumServs=%d; want %d", n, 3) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 3 { |
| t.Errorf("NumRequests=%d; want %d", n, 3) |
| } |
| }() |
| t.Logf("release first") |
| dones[0](nil) |
| if n := sema.NumServs(); n != 2 { |
| t.Errorf("NumServs=%d; want %d", n, 2) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 3 { |
| t.Errorf("NumRequests=%d; want %d", n, 3) |
| } |
| _, done, err := sema.WaitAcquire(ctx) |
| if err != nil { |
| t.Fatalf("WaitAcquire %v", err) |
| } |
| if n := sema.NumServs(); n != 3 { |
| t.Errorf("NumServs=%d; want %d", n, 2) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 4 { |
| t.Errorf("NumRequests=%d; want %d", n, 4) |
| } |
| dones[1](nil) |
| dones[2](nil) |
| done(nil) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 4 { |
| t.Errorf("NumRequests=%d; want %d", n, 4) |
| } |
| } |
| |
| func TestTryAcquire(t *testing.T) { |
| ctx := t.Context() |
| sema := semaphore.New(t.Name(), 3) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 0 { |
| t.Errorf("NumRequests=%d; want %d", n, 0) |
| } |
| |
| var dones []func(error) |
| for i := range 3 { |
| _, done, err := sema.TryAcquire(ctx) |
| if err != nil { |
| t.Fatalf("TryAcquire %d: %v", i, err) |
| } |
| dones = append(dones, done) |
| if n := sema.NumServs(); n != i+1 { |
| t.Errorf("NumServs=%d; want %d", n, i+1) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != i+1 { |
| t.Errorf("NumRequests=%d; want %d", n, i+1) |
| } |
| } |
| t.Logf("all acquired") |
| _, _, err := sema.TryAcquire(ctx) |
| if err == nil { |
| t.Fatalf("TryAcquire ok; want err") |
| } |
| if n := sema.NumServs(); n != 3 { |
| t.Errorf("NumServs=%d; want %d", n, 3) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 3 { |
| t.Errorf("NumRequests=%d; want %d", n, 3) |
| } |
| t.Logf("release first") |
| dones[0](nil) |
| if n := sema.NumServs(); n != 2 { |
| t.Errorf("NumServs=%d; want %d", n, 2) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 3 { |
| t.Errorf("NumRequests=%d; want %d", n, 3) |
| } |
| _, done, err := sema.TryAcquire(ctx) |
| if err != nil { |
| t.Fatalf("TryAcquire %v", err) |
| } |
| if n := sema.NumServs(); n != 3 { |
| t.Errorf("NumServs=%d; want %d", n, 2) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 4 { |
| t.Errorf("NumRequests=%d; want %d", n, 4) |
| } |
| dones[1](nil) |
| dones[2](nil) |
| done(nil) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 4 { |
| t.Errorf("NumRequests=%d; want %d", n, 4) |
| } |
| } |
| |
| // TestDo tests the Do function. |
| func TestDo(t *testing.T) { |
| ctx := t.Context() |
| sema := semaphore.New(t.Name(), 3) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 0 { |
| t.Errorf("NumRequests=%d; want %d", n, 0) |
| } |
| |
| var called atomic.Int32 |
| const count = 50 |
| var wg sync.WaitGroup |
| for i := range count { |
| wg.Go(func() { |
| err := sema.Do(ctx, func(context.Context) error { |
| called.Add(1) |
| return nil |
| }) |
| if err != nil { |
| t.Errorf("Do %d: %v", i, err) |
| } |
| }) |
| } |
| wg.Wait() |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != count { |
| t.Errorf("NumRequests=%d; want %d", n, count) |
| } |
| if n := called.Load(); int(n) != count { |
| t.Errorf("called=%d; want %d", n, count) |
| } |
| } |
| |
| func TestDo_err(t *testing.T) { |
| ctx := t.Context() |
| sema := semaphore.New(t.Name(), 3) |
| if n := sema.NumServs(); n != 0 { |
| t.Errorf("NumServs=%d; want %d", n, 0) |
| } |
| if n := sema.NumWaits(); n != 0 { |
| t.Errorf("NumWaits=%d; want %d", n, 0) |
| } |
| if n := sema.NumRequests(); n != 0 { |
| t.Errorf("NumRequests=%d; want %d", n, 0) |
| } |
| |
| var wantErr = errors.New("error") |
| |
| f := func(ctx context.Context) error { |
| return wantErr |
| } |
| |
| err := sema.Do(ctx, f) |
| if !errors.Is(err, wantErr) { |
| t.Errorf("Do %v; want %v", err, wantErr) |
| } |
| } |
| |
| func TestDo_timeout(t *testing.T) { |
| started := time.Now() |
| ctx := t.Context() |
| const count = 3 |
| sema := semaphore.New(t.Name(), count) |
| |
| done := make(chan struct{}) |
| f := func(ctx context.Context) error { |
| t.Logf("%s f called", time.Since(started)) |
| select { |
| case <-ctx.Done(): |
| case <-done: |
| } |
| t.Logf("%s f return %v", time.Since(started), ctx.Err()) |
| return ctx.Err() |
| } |
| var wg sync.WaitGroup |
| for i := range count { |
| wg.Go(func() { |
| err := sema.Do(ctx, f) |
| if err != nil { |
| t.Logf("%s Do %d: %v", time.Since(started), i, err) |
| } |
| }) |
| } |
| |
| if n := sema.Capacity(); n != count { |
| t.Errorf("Capacity=%d; want %d", n, count) |
| } |
| t.Logf("wait until semaphore becomes busy") |
| waitStart := time.Now() |
| for time.Since(waitStart) < 1*time.Second { |
| if n := sema.NumServs(); n != count { |
| t.Logf("%s check servs=%d", time.Since(started), n) |
| time.Sleep(1 * time.Millisecond) |
| } |
| } |
| if n := sema.NumServs(); n != count { |
| t.Fatalf("%s NumServs=%d; want %d", time.Since(started), n, count) |
| } |
| t.Logf("now semaphore is busy") |
| { |
| ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond) |
| defer cancel() |
| err := sema.Do(ctx, func(ctx context.Context) error { |
| return nil |
| }) |
| t.Logf("%s Do timeout %v", time.Since(started), err) |
| if !errors.Is(err, context.DeadlineExceeded) { |
| t.Errorf("%s Do timeout %v; want %v", time.Since(started), err, context.DeadlineExceeded) |
| } |
| } |
| close(done) |
| wg.Wait() |
| } |