|  | // 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() | 
|  | } | 
|  | } |