mirror of
https://github.com/crazybber/go-pattern-examples.git
synced 2024-11-22 11:56:03 +03:00
add a semaphore pattern
This commit is contained in:
parent
2f23bc271f
commit
ed3cbab384
52
gomore/08_semaphore/semaphore/semaphore.go
Normal file
52
gomore/08_semaphore/semaphore/semaphore.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Package semaphore implements the semaphore resiliency pattern for Go.
|
||||||
|
package semaphore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoTickets is the error returned by Acquire when it could not acquire
|
||||||
|
// a ticket from the semaphore within the configured timeout.
|
||||||
|
var ErrNoTickets = errors.New("could not acquire semaphore ticket")
|
||||||
|
|
||||||
|
// Semaphore implements the semaphore resiliency pattern
|
||||||
|
type Semaphore struct {
|
||||||
|
sem chan struct{}
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// New constructs a new Semaphore with the given ticket-count
|
||||||
|
// and timeout.
|
||||||
|
func New(tickets int, timeout time.Duration) *Semaphore {
|
||||||
|
return &Semaphore{
|
||||||
|
sem: make(chan struct{}, tickets),
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire tries to acquire a ticket from the semaphore. If it can, it returns nil.
|
||||||
|
// If it cannot after "timeout" amount of time, it returns ErrNoTickets. It is
|
||||||
|
// safe to call Acquire concurrently on a single Semaphore.
|
||||||
|
func (s *Semaphore) Acquire() error {
|
||||||
|
select {
|
||||||
|
case s.sem <- struct{}{}:
|
||||||
|
return nil
|
||||||
|
case <-time.After(s.timeout):
|
||||||
|
return ErrNoTickets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases an acquired ticket back to the semaphore. It is safe to call
|
||||||
|
// Release concurrently on a single Semaphore. It is an error to call Release on
|
||||||
|
// a Semaphore from which you have not first acquired a ticket.
|
||||||
|
func (s *Semaphore) Release() {
|
||||||
|
<-s.sem
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty will return true if no tickets are being held at that instant.
|
||||||
|
// It is safe to call concurrently with Acquire and Release, though do note
|
||||||
|
// that the result may then be unpredictable.
|
||||||
|
func (s *Semaphore) IsEmpty() bool {
|
||||||
|
return len(s.sem) == 0
|
||||||
|
}
|
81
gomore/08_semaphore/semaphore/semaphore_test.go
Normal file
81
gomore/08_semaphore/semaphore/semaphore_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package semaphore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSemaphoreAcquireRelease(t *testing.T) {
|
||||||
|
sem := New(3, 1*time.Second)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
sem.Release()
|
||||||
|
sem.Release()
|
||||||
|
sem.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSemaphoreBlockTimeout(t *testing.T) {
|
||||||
|
sem := New(1, 200*time.Millisecond)
|
||||||
|
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
if err := sem.Acquire(); err != ErrNoTickets {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if start.Add(200 * time.Millisecond).After(time.Now()) {
|
||||||
|
t.Error("semaphore did not wait long enough")
|
||||||
|
}
|
||||||
|
|
||||||
|
sem.Release()
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSemaphoreEmpty(t *testing.T) {
|
||||||
|
sem := New(2, 200*time.Millisecond)
|
||||||
|
|
||||||
|
if !sem.IsEmpty() {
|
||||||
|
t.Error("semaphore should be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
sem.Acquire()
|
||||||
|
|
||||||
|
if sem.IsEmpty() {
|
||||||
|
t.Error("semaphore should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
sem.Release()
|
||||||
|
|
||||||
|
if !sem.IsEmpty() {
|
||||||
|
t.Error("semaphore should be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSemaphore() {
|
||||||
|
sem := New(3, 1*time.Second)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func() {
|
||||||
|
if err := sem.Acquire(); err != nil {
|
||||||
|
return //could not acquire semaphore
|
||||||
|
}
|
||||||
|
defer sem.Release()
|
||||||
|
|
||||||
|
// do something semaphore-guarded
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user