filelock.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright 2010 Matt Chaput. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are met:
  5. #
  6. # 1. Redistributions of source code must retain the above copyright notice,
  7. # this list of conditions and the following disclaimer.
  8. #
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
  14. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  15. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  16. # EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  17. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  18. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  19. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  20. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  21. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  22. # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. #
  24. # The views and conclusions contained in the software and documentation are
  25. # those of the authors and should not be interpreted as representing official
  26. # policies, either expressed or implied, of Matt Chaput.
  27. """
  28. This module contains classes implementing exclusive locks for platforms with
  29. fcntl (UNIX and Mac OS X) and Windows. Whoosh originally used directory
  30. creation as a locking method, but it had the problem that if the program
  31. crashed the lock directory was left behind and would keep the index locked
  32. until it was cleaned up. Using OS-level file locks fixes this.
  33. """
  34. import errno
  35. import os
  36. import sys
  37. import time
  38. def try_for(fn, timeout=5.0, delay=0.1):
  39. """Calls ``fn`` every ``delay`` seconds until it returns True or
  40. ``timeout`` seconds elapse. Returns True if the lock was acquired, or False
  41. if the timeout was reached.
  42. :param timeout: Length of time (in seconds) to keep retrying to acquire the
  43. lock. 0 means return immediately. Only used when blocking is False.
  44. :param delay: How often (in seconds) to retry acquiring the lock during
  45. the timeout period. Only used when blocking is False and timeout > 0.
  46. """
  47. until = time.time() + timeout
  48. v = fn()
  49. while not v and time.time() < until:
  50. time.sleep(delay)
  51. v = fn()
  52. return v
  53. class LockBase(object):
  54. """Base class for file locks.
  55. """
  56. def __init__(self, filename):
  57. self.fd = None
  58. self.filename = filename
  59. self.locked = False
  60. def __del__(self):
  61. if hasattr(self, "fd") and self.fd:
  62. try:
  63. self.release()
  64. except:
  65. pass
  66. def acquire(self, blocking=False):
  67. """Acquire the lock. Returns True if the lock was acquired.
  68. :param blocking: if True, call blocks until the lock is acquired.
  69. This may not be available on all platforms. On Windows, this is
  70. actually just a delay of 10 seconds, rechecking every second.
  71. """
  72. pass
  73. def release(self):
  74. pass
  75. class FcntlLock(LockBase):
  76. """File lock based on UNIX-only fcntl module.
  77. """
  78. def acquire(self, blocking=False):
  79. import fcntl # @UnresolvedImport
  80. flags = os.O_CREAT | os.O_WRONLY
  81. self.fd = os.open(self.filename, flags)
  82. mode = fcntl.LOCK_EX
  83. if not blocking:
  84. mode |= fcntl.LOCK_NB
  85. try:
  86. fcntl.flock(self.fd, mode)
  87. self.locked = True
  88. return True
  89. except IOError:
  90. e = sys.exc_info()[1]
  91. if e.errno not in (errno.EAGAIN, errno.EACCES):
  92. raise
  93. os.close(self.fd)
  94. self.fd = None
  95. return False
  96. def release(self):
  97. if self.fd is None:
  98. raise Exception("Lock was not acquired")
  99. import fcntl # @UnresolvedImport
  100. fcntl.flock(self.fd, fcntl.LOCK_UN)
  101. os.close(self.fd)
  102. self.fd = None
  103. class MsvcrtLock(LockBase):
  104. """File lock based on Windows-only msvcrt module.
  105. """
  106. def acquire(self, blocking=False):
  107. import msvcrt # @UnresolvedImport
  108. flags = os.O_CREAT | os.O_WRONLY
  109. mode = msvcrt.LK_NBLCK
  110. if blocking:
  111. mode = msvcrt.LK_LOCK
  112. self.fd = os.open(self.filename, flags)
  113. try:
  114. msvcrt.locking(self.fd, mode, 1)
  115. return True
  116. except IOError:
  117. e = sys.exc_info()[1]
  118. if e.errno not in (errno.EAGAIN, errno.EACCES, errno.EDEADLK):
  119. raise
  120. os.close(self.fd)
  121. self.fd = None
  122. return False
  123. def release(self):
  124. import msvcrt # @UnresolvedImport
  125. if self.fd is None:
  126. raise Exception("Lock was not acquired")
  127. msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1)
  128. os.close(self.fd)
  129. self.fd = None
  130. if os.name == "nt":
  131. FileLock = MsvcrtLock
  132. else:
  133. FileLock = FcntlLock