diff --git a/sync/mutex/mutex.go b/sync/mutex/mutex.go index 3c2e93f..17efe2e 100644 --- a/sync/mutex/mutex.go +++ b/sync/mutex/mutex.go @@ -1,15 +1,22 @@ -package rwmutex +package mutex import ( "context" "sync" ) -type Mutex struct { +// A RWMutex is a reader/writer mutual exclusion lock. +// The lock can be held by an arbitrary number of readers or a single writer. +// The zero value for a RWMutex is an unlocked mutex. +// +// A RWMutex must not be copied after first use. +type RWMutex struct { mu sync.RWMutex } -func (m *Mutex) TryLock(ctx context.Context) error { +// TryLock waits to lock RWMutex for writing. +// A context error is returned on canceled context. +func (m *RWMutex) TryLock(ctx context.Context) error { if m.mu.TryLock() { return nil } @@ -17,21 +24,35 @@ func (m *Mutex) TryLock(ctx context.Context) error { return wait(ctx, &m.mu) } -func (m *Mutex) TryRLock(ctx context.Context) error { +// TryRLock waits to lock RWMutex for reading. +// A context error is returned on canceled context. +func (m *RWMutex) TryRLock(ctx context.Context) error { if m.mu.TryRLock() { return nil } return wait(ctx, (&m.mu).RLocker()) } -func (m *Mutex) Lock() { +// Lock locks RWMutex for writing. +func (m *RWMutex) Lock() { m.mu.Lock() } -func (m *Mutex) Unlock() { +// Unlock unlocks RWMutex for writing. +func (m *RWMutex) Unlock() { m.mu.Unlock() } +// RLock locks RWMutex for reading. +func (m *RWMutex) RLock() { + m.mu.RLock() +} + +// RUnlock undoes a single RLock call. +func (m *RWMutex) RUnlock() { + m.mu.RUnlock() +} + func wait(ctx context.Context, mu sync.Locker) error { done := make(chan struct{}) go func() { diff --git a/sync/mutex/mutex_test.go b/sync/mutex/mutex_test.go new file mode 100644 index 0000000..6f4be93 --- /dev/null +++ b/sync/mutex/mutex_test.go @@ -0,0 +1,63 @@ +package mutex_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/Zamony/go/sync/mutex" +) + +const timeout = 10 * time.Millisecond + +func TestMutexContext(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), timeout) + t.Cleanup(cancel) + + var mu mutex.RWMutex + mu.Lock() + defer mu.Unlock() + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + if err := mu.TryLock(ctx); err != ctx.Err() { + t.Error("lock", err) + } + }() + go func() { + defer wg.Done() + if err := mu.TryRLock(ctx); err != ctx.Err() { + t.Error("rlock", err) + } + }() + + wg.Wait() +} + +func TestMutexLock(t *testing.T) { + t.Parallel() + + ctx := context.Background() + var mu mutex.RWMutex + if err := mu.TryLock(ctx); err != nil { + t.Error("lock", err) + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := mu.TryRLock(ctx); err != nil { + t.Error("rlock", err) + } + mu.RUnlock() + }() + + time.Sleep(timeout) + mu.Unlock() + wg.Wait() +}