diff --git a/sync.go b/sync.go index 65ab319..4c0bbdc 100644 --- a/sync.go +++ b/sync.go @@ -554,3 +554,51 @@ func (d *AtomicDuration) Duration() time.Duration { defer d.m.Unlock() return d.d } + +// FIFOMutex is a mutex guaranteeing FIFO order +type FIFOMutex struct { + busy bool + m sync.Mutex // Locks busy and waiting + waiting []*sync.Cond +} + +func (m *FIFOMutex) Lock() { + // No need to wait + m.m.Lock() + if !m.busy { + m.busy = true + m.m.Unlock() + return + } + + // Create cond + c := sync.NewCond(&sync.Mutex{}) + + // Make sure to lock cond when waiting mutex is still held + c.L.Lock() + + // Add to waiting queue + m.waiting = append(m.waiting, c) + m.m.Unlock() + + // Wait + c.Wait() +} + +func (m *FIFOMutex) Unlock() { + // Lock + m.m.Lock() + defer m.m.Unlock() + + // Waiting queue is empty + if len(m.waiting) == 0 { + m.busy = false + return + } + + // Signal and remove first item in waiting queue + m.waiting[0].L.Lock() + m.waiting[0].Signal() + m.waiting[0].L.Unlock() + m.waiting = m.waiting[1:] +} diff --git a/sync_test.go b/sync_test.go index 74e6480..ed6e76b 100644 --- a/sync_test.go +++ b/sync_test.go @@ -192,3 +192,29 @@ func TestDebugMutex(t *testing.T) { t.Fatalf("%s doesn't contain %s", g, s) } } + +func TestFIFOMutex(t *testing.T) { + m := FIFOMutex{} + var r []int + m.Lock() + wg := sync.WaitGroup{} + testFIFOMutex(1, &m, &r, &wg) + m.Unlock() + wg.Wait() + if e, g := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, r; !reflect.DeepEqual(e, g) { + t.Fatalf("expected %v, got %v", e, g) + } +} + +func testFIFOMutex(i int, m *FIFOMutex, r *[]int, wg *sync.WaitGroup) { + wg.Add(1) + go func() { + defer wg.Done() + if i < 10 { + testFIFOMutex(i+1, m, r, wg) + } + m.Lock() + *r = append(*r, i) + m.Unlock() + }() +}