252 lines
6.4 KiB
Go
252 lines
6.4 KiB
Go
// Copyright 2013 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package poll
|
|
|
|
import "sync/atomic"
|
|
|
|
// fdMutex is a specialized synchronization primitive that manages
|
|
// lifetime of an fd and serializes access to Read, Write and Close
|
|
// methods on FD.
|
|
type fdMutex struct {
|
|
state uint64
|
|
rsema uint32
|
|
wsema uint32
|
|
}
|
|
|
|
// fdMutex.state is organized as follows:
|
|
// 1 bit - whether FD is closed, if set all subsequent lock operations will fail.
|
|
// 1 bit - lock for read operations.
|
|
// 1 bit - lock for write operations.
|
|
// 20 bits - total number of references (read+write+misc).
|
|
// 20 bits - number of outstanding read waiters.
|
|
// 20 bits - number of outstanding write waiters.
|
|
const (
|
|
mutexClosed = 1 << 0
|
|
mutexRLock = 1 << 1
|
|
mutexWLock = 1 << 2
|
|
mutexRef = 1 << 3
|
|
mutexRefMask = (1<<20 - 1) << 3
|
|
mutexRWait = 1 << 23
|
|
mutexRMask = (1<<20 - 1) << 23
|
|
mutexWWait = 1 << 43
|
|
mutexWMask = (1<<20 - 1) << 43
|
|
)
|
|
|
|
const overflowMsg = "too many concurrent operations on a single file or socket (max 1048575)"
|
|
|
|
// Read operations must do rwlock(true)/rwunlock(true).
|
|
//
|
|
// Write operations must do rwlock(false)/rwunlock(false).
|
|
//
|
|
// Misc operations must do incref/decref.
|
|
// Misc operations include functions like setsockopt and setDeadline.
|
|
// They need to use incref/decref to ensure that they operate on the
|
|
// correct fd in presence of a concurrent close call (otherwise fd can
|
|
// be closed under their feet).
|
|
//
|
|
// Close operations must do increfAndClose/decref.
|
|
|
|
// incref adds a reference to mu.
|
|
// It reports whether mu is available for reading or writing.
|
|
func (mu *fdMutex) incref() bool {
|
|
for {
|
|
old := atomic.LoadUint64(&mu.state)
|
|
if old&mutexClosed != 0 {
|
|
return false
|
|
}
|
|
new := old + mutexRef
|
|
if new&mutexRefMask == 0 {
|
|
panic(overflowMsg)
|
|
}
|
|
if atomic.CompareAndSwapUint64(&mu.state, old, new) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// increfAndClose sets the state of mu to closed.
|
|
// It returns false if the file was already closed.
|
|
func (mu *fdMutex) increfAndClose() bool {
|
|
for {
|
|
old := atomic.LoadUint64(&mu.state)
|
|
if old&mutexClosed != 0 {
|
|
return false
|
|
}
|
|
// Mark as closed and acquire a reference.
|
|
new := (old | mutexClosed) + mutexRef
|
|
if new&mutexRefMask == 0 {
|
|
panic(overflowMsg)
|
|
}
|
|
// Remove all read and write waiters.
|
|
new &^= mutexRMask | mutexWMask
|
|
if atomic.CompareAndSwapUint64(&mu.state, old, new) {
|
|
// Wake all read and write waiters,
|
|
// they will observe closed flag after wakeup.
|
|
for old&mutexRMask != 0 {
|
|
old -= mutexRWait
|
|
runtime_Semrelease(&mu.rsema)
|
|
}
|
|
for old&mutexWMask != 0 {
|
|
old -= mutexWWait
|
|
runtime_Semrelease(&mu.wsema)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// decref removes a reference from mu.
|
|
// It reports whether there is no remaining reference.
|
|
func (mu *fdMutex) decref() bool {
|
|
for {
|
|
old := atomic.LoadUint64(&mu.state)
|
|
if old&mutexRefMask == 0 {
|
|
panic("inconsistent poll.fdMutex")
|
|
}
|
|
new := old - mutexRef
|
|
if atomic.CompareAndSwapUint64(&mu.state, old, new) {
|
|
return new&(mutexClosed|mutexRefMask) == mutexClosed
|
|
}
|
|
}
|
|
}
|
|
|
|
// lock adds a reference to mu and locks mu.
|
|
// It reports whether mu is available for reading or writing.
|
|
func (mu *fdMutex) rwlock(read bool) bool {
|
|
var mutexBit, mutexWait, mutexMask uint64
|
|
var mutexSema *uint32
|
|
if read {
|
|
mutexBit = mutexRLock
|
|
mutexWait = mutexRWait
|
|
mutexMask = mutexRMask
|
|
mutexSema = &mu.rsema
|
|
} else {
|
|
mutexBit = mutexWLock
|
|
mutexWait = mutexWWait
|
|
mutexMask = mutexWMask
|
|
mutexSema = &mu.wsema
|
|
}
|
|
for {
|
|
old := atomic.LoadUint64(&mu.state)
|
|
if old&mutexClosed != 0 {
|
|
return false
|
|
}
|
|
var new uint64
|
|
if old&mutexBit == 0 {
|
|
// Lock is free, acquire it.
|
|
new = (old | mutexBit) + mutexRef
|
|
if new&mutexRefMask == 0 {
|
|
panic(overflowMsg)
|
|
}
|
|
} else {
|
|
// Wait for lock.
|
|
new = old + mutexWait
|
|
if new&mutexMask == 0 {
|
|
panic(overflowMsg)
|
|
}
|
|
}
|
|
if atomic.CompareAndSwapUint64(&mu.state, old, new) {
|
|
if old&mutexBit == 0 {
|
|
return true
|
|
}
|
|
runtime_Semacquire(mutexSema)
|
|
// The signaller has subtracted mutexWait.
|
|
}
|
|
}
|
|
}
|
|
|
|
// unlock removes a reference from mu and unlocks mu.
|
|
// It reports whether there is no remaining reference.
|
|
func (mu *fdMutex) rwunlock(read bool) bool {
|
|
var mutexBit, mutexWait, mutexMask uint64
|
|
var mutexSema *uint32
|
|
if read {
|
|
mutexBit = mutexRLock
|
|
mutexWait = mutexRWait
|
|
mutexMask = mutexRMask
|
|
mutexSema = &mu.rsema
|
|
} else {
|
|
mutexBit = mutexWLock
|
|
mutexWait = mutexWWait
|
|
mutexMask = mutexWMask
|
|
mutexSema = &mu.wsema
|
|
}
|
|
for {
|
|
old := atomic.LoadUint64(&mu.state)
|
|
if old&mutexBit == 0 || old&mutexRefMask == 0 {
|
|
panic("inconsistent poll.fdMutex")
|
|
}
|
|
// Drop lock, drop reference and wake read waiter if present.
|
|
new := (old &^ mutexBit) - mutexRef
|
|
if old&mutexMask != 0 {
|
|
new -= mutexWait
|
|
}
|
|
if atomic.CompareAndSwapUint64(&mu.state, old, new) {
|
|
if old&mutexMask != 0 {
|
|
runtime_Semrelease(mutexSema)
|
|
}
|
|
return new&(mutexClosed|mutexRefMask) == mutexClosed
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implemented in runtime package.
|
|
func runtime_Semacquire(sema *uint32)
|
|
func runtime_Semrelease(sema *uint32)
|
|
|
|
// incref adds a reference to fd.
|
|
// It returns an error when fd cannot be used.
|
|
func (fd *FD) incref() error {
|
|
if !fd.fdmu.incref() {
|
|
return errClosing(fd.isFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// decref removes a reference from fd.
|
|
// It also closes fd when the state of fd is set to closed and there
|
|
// is no remaining reference.
|
|
func (fd *FD) decref() error {
|
|
if fd.fdmu.decref() {
|
|
return fd.destroy()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readLock adds a reference to fd and locks fd for reading.
|
|
// It returns an error when fd cannot be used for reading.
|
|
func (fd *FD) readLock() error {
|
|
if !fd.fdmu.rwlock(true) {
|
|
return errClosing(fd.isFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readUnlock removes a reference from fd and unlocks fd for reading.
|
|
// It also closes fd when the state of fd is set to closed and there
|
|
// is no remaining reference.
|
|
func (fd *FD) readUnlock() {
|
|
if fd.fdmu.rwunlock(true) {
|
|
fd.destroy()
|
|
}
|
|
}
|
|
|
|
// writeLock adds a reference to fd and locks fd for writing.
|
|
// It returns an error when fd cannot be used for writing.
|
|
func (fd *FD) writeLock() error {
|
|
if !fd.fdmu.rwlock(false) {
|
|
return errClosing(fd.isFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeUnlock removes a reference from fd and unlocks fd for writing.
|
|
// It also closes fd when the state of fd is set to closed and there
|
|
// is no remaining reference.
|
|
func (fd *FD) writeUnlock() {
|
|
if fd.fdmu.rwunlock(false) {
|
|
fd.destroy()
|
|
}
|
|
}
|