TST: make _do_test_lock_externally work on Windows

We need to access the platform-specific file handle locking
mechanisms, but they are hidden away in the class implementations.

This pushes them into static methods to make it easy to access them
while still allowing sub-classing.
This commit is contained in:
John Tyree 2016-02-15 14:58:19 -06:00
parent 526cccebec
commit 6d9d4ed012
2 changed files with 68 additions and 50 deletions

View File

@ -214,32 +214,44 @@ class _InterProcessLock(object):
return os.path.exists(self.path) return os.path.exists(self.path)
def trylock(self): def trylock(self):
raise NotImplementedError() self._trylock(self.lockfile)
def unlock(self): def unlock(self):
self._unlock(self.lockfile)
@staticmethod
def _trylock():
raise NotImplementedError()
@staticmethod
def _unlock():
raise NotImplementedError() raise NotImplementedError()
class _WindowsLock(_InterProcessLock): class _WindowsLock(_InterProcessLock):
"""Interprocess lock implementation that works on windows systems.""" """Interprocess lock implementation that works on windows systems."""
def trylock(self, lockfile=None): @staticmethod
fileno = (lockfile or self.lockfile).fileno() def _trylock(lockfile):
fileno = lockfile.fileno()
msvcrt.locking(fileno, msvcrt.LK_NBLCK, 1) msvcrt.locking(fileno, msvcrt.LK_NBLCK, 1)
def unlock(self, lockfile=None): @staticmethod
fileno = (lockfile or self.lockfile).fileno() def _unlock(lockfile):
fileno = lockfile.fileno()
msvcrt.locking(fileno, msvcrt.LK_UNLCK, 1) msvcrt.locking(fileno, msvcrt.LK_UNLCK, 1)
class _FcntlLock(_InterProcessLock): class _FcntlLock(_InterProcessLock):
"""Interprocess lock implementation that works on posix systems.""" """Interprocess lock implementation that works on posix systems."""
def trylock(self, lockfile=None): @staticmethod
fcntl.lockf(lockfile or self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) def _trylock(lockfile):
fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
def unlock(self, lockfile=None): @staticmethod
fcntl.lockf(lockfile or self.lockfile, fcntl.LOCK_UN) def _unlock(lockfile):
fcntl.lockf(lockfile, fcntl.LOCK_UN)
if os.name == 'nt': if os.name == 'nt':

View File

@ -55,6 +55,36 @@ def try_lock(lock_file):
os._exit(0) os._exit(0)
def lock_files(lock_path, handles_dir, num_handles=50):
with pl.InterProcessLock(lock_path):
# Open some files we can use for locking
handles = []
for n in range(num_handles):
path = os.path.join(handles_dir, ('file-%s' % n))
handles.append(open(path, 'w'))
# Loop over all the handles and try locking the file
# without blocking, keep a count of how many files we
# were able to lock and then unlock. If the lock fails
# we get an IOError and bail out with bad exit code
count = 0
for handle in handles:
try:
pl.InterProcessLock._trylock(handle)
count += 1
pl.InterProcessLock._unlock(handle)
except IOError:
print(os.getpid())
os._exit(2)
finally:
handle.close()
# Check if we were able to open all files
if count != num_handles:
raise AssertionError("Unable to open all handles")
class ProcessLockTest(test.TestCase): class ProcessLockTest(test.TestCase):
def setUp(self): def setUp(self):
super(ProcessLockTest, self).setUp() super(ProcessLockTest, self).setUp()
@ -111,49 +141,25 @@ class ProcessLockTest(test.TestCase):
def _do_test_lock_externally(self, lock_dir): def _do_test_lock_externally(self, lock_dir):
lock_path = os.path.join(lock_dir, "lock") lock_path = os.path.join(lock_dir, "lock")
def lock_files(handles_dir):
with pl.InterProcessLock(lock_path):
# Open some files we can use for locking
handles = []
for n in range(50):
path = os.path.join(handles_dir, ('file-%s' % n))
handles.append(open(path, 'w'))
# Loop over all the handles and try locking the file
# without blocking, keep a count of how many files we
# were able to lock and then unlock. If the lock fails
# we get an IOError and bail out with bad exit code
count = 0
for handle in handles:
try:
pl.InterProcessLock.trylock(handle)
count += 1
pl.InterProcessLock.unlock(handle)
except IOError:
os._exit(2)
finally:
handle.close()
# Check if we were able to open all files
self.assertEqual(50, count)
handles_dir = tempfile.mkdtemp() handles_dir = tempfile.mkdtemp()
self.tmp_dirs.append(handles_dir) self.tmp_dirs.append(handles_dir)
children = []
for n in range(50): num_handles = 50
pid = os.fork() num_processes = 50
if pid: args = [lock_path, handles_dir, num_handles]
children.append(pid) children = [multiprocessing.Process(target=lock_files, args=args)
else: for _ in range(num_processes)]
try:
lock_files(handles_dir) # We do this in three loops in an attempt to get all processes up and
finally: # running at the same time
os._exit(0) for c in children:
for child in children: # Just a precaution to avoid hung processes
(pid, status) = os.waitpid(child, 0) c.daemon = True
if pid: c.start()
self.assertEqual(0, status) for c in children:
c.join(10)
for c in children:
self.assertEqual(0, c.exitcode)
def test_lock_externally(self): def test_lock_externally(self):
self._do_test_lock_externally(self.lock_dir) self._do_test_lock_externally(self.lock_dir)