lock.go (72 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 txfile import "sync" // lock provides the file locking primitives for use within the current // process. File locking as provided by lock, is not aware of other processes // accessing the file. // // Lock types: // - Shared: Shared locks are used by readonly transactions. Multiple readonly // transactions can co-exist with one active write transaction. // - Reserved: Write transactions take the 'Reserved' lock on a file, // such that no other concurrent write transaction can exist. // The Shared lock can still be locked by concurrent readers. // - Pending: The Pending lock is used by write transactions to signal // a write transaction is currently being committed. // The Shared lock can still be used by readonly transactions, // but no new readonly transaction can be started after // the Pending lock has been acquired. // - Exclusive: Once the exclusive lock is acquired by a write transaction, // No other active transactions/locks exist on the file. // // Each Locktype can be accessed using `(*lock).<Type>()`. Each lock type // implements a `Lock` and `Unlock` method. // // Note: Shared file access should be protected using `flock`. type lock struct { mu sync.Mutex // conditions + mutexes shared *sync.Cond exclusive *sync.Cond reserved sync.Mutex // state sharedCount uint pendingSet bool } type sharedLock lock type reservedLock lock type pendingLock lock type exclusiveLock lock func newLock() *lock { l := &lock{} l.init() return l } func (l *lock) init() { l.shared = sync.NewCond(&l.mu) l.exclusive = sync.NewCond(&l.mu) } // TxLock returns the standard Locker for the given transaction type. func (l *lock) TxLock(readonly bool) sync.Locker { if readonly { return l.Shared() } return l.Reserved() } // Shared returns the files shared locker. func (l *lock) Shared() *sharedLock { return (*sharedLock)(l) } // Reserved returns the files reserved locker. func (l *lock) Reserved() *reservedLock { return (*reservedLock)(l) } // Pending returns the files pending locker. func (l *lock) Pending() *pendingLock { return (*pendingLock)(l) } // Pending returns the files exclusive locker. func (l *lock) Exclusive() *exclusiveLock { return (*exclusiveLock)(l) } func (l *sharedLock) Lock() { waitCond(l.shared, l.check, l.inc) } func (l *sharedLock) Unlock() { withLocker(&l.mu, l.dec) } func (l *sharedLock) check() bool { return !l.pendingSet } func (l *sharedLock) inc() { l.sharedCount++ } func (l *sharedLock) dec() { l.sharedCount-- if l.sharedCount == 0 { l.exclusive.Signal() } } func (l *reservedLock) Lock() { l.reserved.Lock() } func (l *reservedLock) Unlock() { l.reserved.Unlock() } func (l *pendingLock) Lock() { l.mu.Lock() l.pendingSet = true l.mu.Unlock() } func (l *pendingLock) Unlock() { l.mu.Lock() l.pendingSet = false l.mu.Unlock() l.shared.Broadcast() } func (l *exclusiveLock) Lock() { waitCond(l.exclusive, l.check, func() {}) } func (l *exclusiveLock) Unlock() {} func (l *exclusiveLock) check() bool { return l.sharedCount == 0 } func waitCond(c *sync.Cond, check func() bool, upd func()) { withLocker(c.L, func() { for !check() { c.Wait() } upd() }) } func withLocker(l sync.Locker, fn func()) { l.Lock() defer l.Unlock() fn() }