state.py: reduce race condition between Lock.trylock() and unlock().
If 'redo clean' deletes the lockfile after trylock() succeeds but before unlock(), then unlock() won't be able to open the pipe in order to release readers, and any waiters might end up waiting forever. We can't open the fifo for write until there's at least one reader, so let's open a reader *just* to let us open a writer. Then we'll leave them open until the later unlock(), which can just close them both.
This commit is contained in:
parent
dd937d6102
commit
2dbd47100d
3 changed files with 40 additions and 36 deletions
35
state.py
35
state.py
|
|
@ -1,6 +1,6 @@
|
|||
import sys, os, errno, glob
|
||||
import vars
|
||||
from helpers import unlink, debug2, mkdirp
|
||||
from helpers import unlink, debug2, mkdirp, close_on_exec
|
||||
|
||||
|
||||
def init():
|
||||
|
|
@ -145,8 +145,8 @@ def start(t):
|
|||
class Lock:
|
||||
def __init__(self, t):
|
||||
self.owned = False
|
||||
self.rfd = self.wfd = None
|
||||
self.lockname = _sname('lock', t)
|
||||
self.tmpname = _sname('lock%d' % os.getpid(), t)
|
||||
|
||||
def __del__(self):
|
||||
if self.owned:
|
||||
|
|
@ -156,6 +156,10 @@ class Lock:
|
|||
try:
|
||||
os.mkfifo(self.lockname, 0600)
|
||||
self.owned = True
|
||||
self.rfd = os.open(self.lockname, os.O_RDONLY|os.O_NONBLOCK)
|
||||
self.wfd = os.open(self.lockname, os.O_WRONLY)
|
||||
close_on_exec(self.rfd, True)
|
||||
close_on_exec(self.wfd, True)
|
||||
except OSError, e:
|
||||
if e.errno == errno.EEXIST:
|
||||
pass
|
||||
|
|
@ -172,22 +176,11 @@ class Lock:
|
|||
if not self.owned:
|
||||
raise Exception("can't unlock %r - we don't own it"
|
||||
% self.lockname)
|
||||
# make sure no readers can connect
|
||||
try:
|
||||
os.rename(self.lockname, self.tmpname)
|
||||
except OSError, e:
|
||||
if e.errno == errno.ENOENT: # 'make clean' might do this
|
||||
self.owned = False
|
||||
return
|
||||
try:
|
||||
# ping any connected readers
|
||||
os.close(os.open(self.tmpname, os.O_WRONLY|os.O_NONBLOCK))
|
||||
except OSError, e:
|
||||
if e.errno == errno.ENXIO: # no readers open; that's ok
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
os.unlink(self.tmpname)
|
||||
unlink(self.lockname)
|
||||
# ping any connected readers
|
||||
os.close(self.rfd)
|
||||
os.close(self.wfd)
|
||||
self.rfd = self.wfd = None
|
||||
self.owned = False
|
||||
|
||||
def wait(self):
|
||||
|
|
@ -195,7 +188,11 @@ class Lock:
|
|||
raise Exception("can't wait on %r - we own it" % self.lockname)
|
||||
try:
|
||||
# open() will finish only when a writer exists and does close()
|
||||
os.close(os.open(self.lockname, os.O_RDONLY))
|
||||
fd = os.open(self.lockname, os.O_RDONLY)
|
||||
try:
|
||||
os.read(fd, 1)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except OSError, e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass # it's not even unlocked or was unlocked earlier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue