mirror of
https://github.com/redis/go-redis.git
synced 2025-12-02 06:22:31 +03:00
199 lines
5.0 KiB
Go
199 lines
5.0 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var semTimers = sync.Pool{
|
|
New: func() interface{} {
|
|
t := time.NewTimer(time.Hour)
|
|
t.Stop()
|
|
return t
|
|
},
|
|
}
|
|
|
|
// waiter represents a goroutine waiting for a token.
|
|
type waiter struct {
|
|
ready chan struct{}
|
|
next *waiter
|
|
}
|
|
|
|
// FastSemaphore is a channel-based semaphore optimized for performance.
|
|
// It uses a fast path that avoids timer allocation when tokens are available.
|
|
// The channel is pre-filled with tokens: Acquire = receive, Release = send.
|
|
// Closing the semaphore unblocks all waiting goroutines.
|
|
//
|
|
// Performance: ~30 ns/op with zero allocations on fast path.
|
|
// Fairness: Eventual fairness (no starvation) but not strict FIFO.
|
|
type FastSemaphore struct {
|
|
tokens chan struct{}
|
|
max int32
|
|
}
|
|
|
|
// NewFastSemaphore creates a new fast semaphore with the given capacity.
|
|
func NewFastSemaphore(capacity int32) *FastSemaphore {
|
|
ch := make(chan struct{}, capacity)
|
|
// Pre-fill with tokens
|
|
for i := int32(0); i < capacity; i++ {
|
|
ch <- struct{}{}
|
|
}
|
|
return &FastSemaphore{
|
|
tokens: ch,
|
|
max: capacity,
|
|
}
|
|
}
|
|
|
|
// TryAcquire attempts to acquire a token without blocking.
|
|
// Returns true if successful, false if no tokens available.
|
|
func (s *FastSemaphore) TryAcquire() bool {
|
|
select {
|
|
case <-s.tokens:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Acquire acquires a token, blocking if necessary until one is available.
|
|
// Returns an error if the context is cancelled or the timeout expires.
|
|
// Uses a fast path to avoid timer allocation when tokens are immediately available.
|
|
func (s *FastSemaphore) Acquire(ctx context.Context, timeout time.Duration, timeoutErr error) error {
|
|
// Check context first
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// Try fast path first (no timer needed)
|
|
select {
|
|
case <-s.tokens:
|
|
return nil
|
|
default:
|
|
}
|
|
|
|
// Slow path: need to wait with timeout
|
|
timer := semTimers.Get().(*time.Timer)
|
|
defer semTimers.Put(timer)
|
|
timer.Reset(timeout)
|
|
|
|
select {
|
|
case <-s.tokens:
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return nil
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return ctx.Err()
|
|
case <-timer.C:
|
|
return timeoutErr
|
|
}
|
|
}
|
|
|
|
// AcquireBlocking acquires a token, blocking indefinitely until one is available.
|
|
func (s *FastSemaphore) AcquireBlocking() {
|
|
<-s.tokens
|
|
}
|
|
|
|
// Release releases a token back to the semaphore.
|
|
func (s *FastSemaphore) Release() {
|
|
s.tokens <- struct{}{}
|
|
}
|
|
|
|
// Close closes the semaphore, unblocking all waiting goroutines.
|
|
// After close, all Acquire calls will receive a closed channel signal.
|
|
func (s *FastSemaphore) Close() {
|
|
close(s.tokens)
|
|
}
|
|
|
|
// Len returns the current number of acquired tokens.
|
|
func (s *FastSemaphore) Len() int32 {
|
|
return s.max - int32(len(s.tokens))
|
|
}
|
|
|
|
// FIFOSemaphore is a channel-based semaphore with strict FIFO ordering.
|
|
// Unlike FastSemaphore, this guarantees that threads are served in the exact order they call Acquire().
|
|
// The channel is pre-filled with tokens: Acquire = receive, Release = send.
|
|
// Closing the semaphore unblocks all waiting goroutines.
|
|
//
|
|
// Performance: ~115 ns/op with zero allocations (slower than FastSemaphore due to timer allocation).
|
|
// Fairness: Strict FIFO ordering guaranteed by Go runtime.
|
|
type FIFOSemaphore struct {
|
|
tokens chan struct{}
|
|
max int32
|
|
}
|
|
|
|
// NewFIFOSemaphore creates a new FIFO semaphore with the given capacity.
|
|
func NewFIFOSemaphore(capacity int32) *FIFOSemaphore {
|
|
ch := make(chan struct{}, capacity)
|
|
// Pre-fill with tokens
|
|
for i := int32(0); i < capacity; i++ {
|
|
ch <- struct{}{}
|
|
}
|
|
return &FIFOSemaphore{
|
|
tokens: ch,
|
|
max: capacity,
|
|
}
|
|
}
|
|
|
|
// TryAcquire attempts to acquire a token without blocking.
|
|
// Returns true if successful, false if no tokens available.
|
|
func (s *FIFOSemaphore) TryAcquire() bool {
|
|
select {
|
|
case <-s.tokens:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Acquire acquires a token, blocking if necessary until one is available.
|
|
// Returns an error if the context is cancelled or the timeout expires.
|
|
// Always uses timer to guarantee FIFO ordering (no fast path).
|
|
func (s *FIFOSemaphore) Acquire(ctx context.Context, timeout time.Duration, timeoutErr error) error {
|
|
// No fast path - always use timer to guarantee FIFO
|
|
timer := semTimers.Get().(*time.Timer)
|
|
defer semTimers.Put(timer)
|
|
timer.Reset(timeout)
|
|
|
|
select {
|
|
case <-s.tokens:
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return nil
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return ctx.Err()
|
|
case <-timer.C:
|
|
return timeoutErr
|
|
}
|
|
}
|
|
|
|
// AcquireBlocking acquires a token, blocking indefinitely until one is available.
|
|
func (s *FIFOSemaphore) AcquireBlocking() {
|
|
<-s.tokens
|
|
}
|
|
|
|
// Release releases a token back to the semaphore.
|
|
func (s *FIFOSemaphore) Release() {
|
|
s.tokens <- struct{}{}
|
|
}
|
|
|
|
// Close closes the semaphore, unblocking all waiting goroutines.
|
|
// After close, all Acquire calls will receive a closed channel signal.
|
|
func (s *FIFOSemaphore) Close() {
|
|
close(s.tokens)
|
|
}
|
|
|
|
// Len returns the current number of acquired tokens.
|
|
func (s *FIFOSemaphore) Len() int32 {
|
|
return s.max - int32(len(s.tokens))
|
|
} |