unison/mutex.go (75 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package unison
import (
"time"
)
// Mutex provides a mutex based on go channels. The lock operations support
// timeout or cancellation by a context. Moreover one can try to lock the mutex
// from within a select statement when using Await.
//
// The zero value of Mutex will not be able to Lock the mutex ever. The Lock
// method will never return. Calling Unlock will panic.
type Mutex struct {
ch chan struct{}
}
// doneContext is a subset of context.Context, to allow more restrained
// cancellation types as well.
type doneContext interface {
Done() <-chan struct{}
Err() error
}
// MakeMutex creates a mutex.
func MakeMutex() Mutex {
ch := make(chan struct{}, 1)
ch <- struct{}{}
return Mutex{ch: ch}
}
// Lock blocks until the mutex has been acquired.
// The zero value of Mutex will block forever.
func (c Mutex) Lock() {
<-c.ch
}
// LockTimeout will try to lock the mutex. A failed lock attempt
// returns false, once the amount of configured duration has been passed.
//
// If duration is 0, then the call behaves like TryLock.
// If duration is <0, then the call behaves like Lock if the Mutex has been
// initialized, otherwise fails.
//
// The zero value of Mutex will never succeed.
func (c Mutex) LockTimeout(duration time.Duration) bool {
switch {
case duration == 0:
return c.TryLock()
case duration < 0:
if c.ch == nil {
return false
}
c.Lock()
return true
}
timer := time.NewTimer(duration)
select {
case <-c.ch:
timer.Stop()
return true
case <-timer.C:
select {
case <-c.ch: // still lock, if timer and lock occured at the same time
return true
default:
return false
}
}
}
// LockContext tries to lock the mutex. The Log operation can be cancelled by
// the context. LockContext returns nil on success, otherwise the error value
// returned by context.Err, which MUST NOT return nil after cancellation.
func (c Mutex) LockContext(context doneContext) error {
select {
case <-context.Done():
return context.Err()
default:
}
select {
case <-c.ch:
return nil
case <-context.Done():
return context.Err()
}
}
// TryLock attempts to lock the mutex. If the mutex has been already locked
// false is returned.
func (c Mutex) TryLock() bool {
select {
case <-c.ch:
return true
default:
return false
}
}
// Await returns a channel that will be triggered if the lock attempt did succeed.
// One can use the channel with select-case. The mutex is assumed to be locked if
// the branch waiting on the mutex has been triggered.
func (c Mutex) Await() <-chan struct{} {
return c.ch
}
// Unlock unlocks the mutex.
//
// The zero value of Mutex will panic.
func (c Mutex) Unlock() {
select {
case c.ch <- struct{}{}:
default:
panic("unlock on unlocked mutex")
}
}