diff --git a/semaphore.go b/semaphore.go index c9b56b5..86a6c05 100644 --- a/semaphore.go +++ b/semaphore.go @@ -26,9 +26,14 @@ func (s *semaphore) Lock() { s.ch <- struct{}{} } -// Unlock releases semaphore, can block if nothing acquired before. +// Unlock releases semaphore. It panics if called without a matching Lock/TryLock, +// matching sync.Mutex rather than blocking forever on an empty channel. func (s *semaphore) Unlock() { - <-s.ch + select { + case <-s.ch: + default: + panic("syncs: unlock of unlocked semaphore") + } } // TryLock acquires semaphore if possible, returns true if acquired, false otherwise. diff --git a/semaphore_test.go b/semaphore_test.go index 3757d2b..b329699 100644 --- a/semaphore_test.go +++ b/semaphore_test.go @@ -85,3 +85,18 @@ func TestSemaphore_TryLock(t *testing.T) { }) } } + +func TestSemaphore_UnlockUnlocked(t *testing.T) { + sema := NewSemaphore(1) + assert.PanicsWithValue(t, "syncs: unlock of unlocked semaphore", func() { + sema.Unlock() + }) + + sema.Lock() + assert.NotPanics(t, func() { + sema.Unlock() + }) + assert.PanicsWithValue(t, "syncs: unlock of unlocked semaphore", func() { + sema.Unlock() + }) +}