diff --git a/terraform/util.go b/terraform/util.go index f2654141ff..ce7198d5b3 100644 --- a/terraform/util.go +++ b/terraform/util.go @@ -1,5 +1,47 @@ package terraform +// Semaphore is a wrapper around a channel to provide +// utility methods to clarify that we are treating the +// channel as a semaphore +type Semaphore chan struct{} + +// NewSemaphore creates a semaphore that allows up +// to a given limit of simultaneous acquisitions +func NewSemaphore(n int) Semaphore { + if n == 0 { + panic("semaphore with limit 0") + } + ch := make(chan struct{}, n) + return Semaphore(ch) +} + +// Acquire is used to acquire an available slot. +// Blocks until available. +func (s Semaphore) Acquire() { + s <- struct{}{} +} + +// TryAcquire is used to do a non-blocking acquire. +// Returns a bool indicating success +func (s Semaphore) TryAcquire() bool { + select { + case s <- struct{}{}: + return true + default: + return false + } +} + +// Release is used to return a slot. Acquire must +// be called as a pre-condition. +func (s Semaphore) Release() { + select { + case <-s: + default: + panic("release without an acquire") + } +} + // strSliceContains checks if a given string is contained in a slice // When anybody asks why Go needs generics, here you go. func strSliceContains(haystack []string, needle string) bool { diff --git a/terraform/util_test.go b/terraform/util_test.go index 3b98188725..560c8a8738 100644 --- a/terraform/util_test.go +++ b/terraform/util_test.go @@ -1,6 +1,36 @@ package terraform -import "testing" +import ( + "testing" + "time" +) + +func TestSemaphore(t *testing.T) { + s := NewSemaphore(2) + timer := time.AfterFunc(time.Second, func() { + panic("deadlock") + }) + defer timer.Stop() + + s.Acquire() + if !s.TryAcquire() { + t.Fatalf("should acquire") + } + if s.TryAcquire() { + t.Fatalf("should not acquire") + } + s.Release() + s.Release() + + // This release should panic + defer func() { + r := recover() + if r == nil { + t.Fatalf("should panic") + } + }() + s.Release() +} func TestStrSliceContains(t *testing.T) { if strSliceContains(nil, "foo") {