semaphore.go

v0.6.1
Doc Versions Source
1
package async
2
3
import (
4
	"context"
5
	"sync/atomic"
6
)
7
8
// Semaphore is a counting semaphore bounding concurrency to a fixed number of
9
// tokens. A Semaphore created with n<=0 is unlimited: every [Semaphore.Acquire]
10
// and [Semaphore.TryAcquire] succeeds without blocking.
11
//
12
// Each successful Acquire/TryAcquire must be matched by exactly one
13
// [Semaphore.Release]. The zero value is not usable; use [NewSemaphore].
14
type Semaphore struct {
15
	tokens    chan struct{}
16
	unlimited bool
17
	inflight  atomic.Int64
18
}
19
20
// NewSemaphore creates a [Semaphore] with n tokens. n<=0 yields an unlimited
21
// semaphore that never blocks.
22
func NewSemaphore(n int) *Semaphore {
23
	if n <= 0 {
24
		return &Semaphore{unlimited: true}
25
	}
26
	return &Semaphore{tokens: make(chan struct{}, n)}
27
}
28
29
// Acquire takes a token, blocking until one is available or ctx is cancelled,
30
// in which case it returns ctx.Err(). An unlimited semaphore returns nil
31
// immediately without consulting ctx.
32
func (s *Semaphore) Acquire(ctx context.Context) error {
33
	if s.unlimited {
34
		s.inflight.Add(1)
35
		return nil
36
	}
37
38
	select {
39
	case s.tokens <- struct{}{}:
40
		s.inflight.Add(1)
41
		return nil
42
	case <-ctx.Done():
43
		return ctx.Err()
44
	}
45
}
46
47
// TryAcquire takes a token without blocking, reporting whether it succeeded.
48
// An unlimited semaphore always succeeds.
49
func (s *Semaphore) TryAcquire() bool {
50
	if s.unlimited {
51
		s.inflight.Add(1)
52
		return true
53
	}
54
55
	select {
56
	case s.tokens <- struct{}{}:
57
		s.inflight.Add(1)
58
		return true
59
	default:
60
		return false
61
	}
62
}
63
64
// Release returns a token. Release on a semaphore with no outstanding tokens
65
// neither blocks nor panics. It must be paired with a prior successful Acquire
66
// or TryAcquire.
67
func (s *Semaphore) Release() {
68
	if s.unlimited {
69
		s.inflight.Add(-1)
70
		return
71
	}
72
73
	select {
74
	case <-s.tokens:
75
		s.inflight.Add(-1)
76
	default:
77
		// No outstanding token: drop without blocking.
78
	}
79
}
80
81
// InFlight returns the number of currently held tokens.
82
func (s *Semaphore) InFlight() int {
83
	return int(s.inflight.Load())
84
}
85

Source Files