NamedMutex.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.1 (the License). You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License. You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10. # Author: David Harrison
  11. if __name__ == "__main__":
  12. def _(s):
  13. return s
  14. else:
  15. from BTL.translation import _
  16. import os
  17. if os.name == 'nt':
  18. import win32file
  19. import win32event
  20. #import BTL.likewin32api as win32api
  21. import win32api
  22. import winerror
  23. import pywintypes
  24. elif os.name == 'posix':
  25. import fcntl
  26. from BitTorrent import platform
  27. from fcntl import flock
  28. class NamedMutex(object):
  29. """Reasonably cross-platform, cross-process mutex.
  30. It does not implement mutual exclusion between threads in the
  31. same process. Use threading.Lock or threading.RLock for mutual
  32. exclusion between threads.
  33. """
  34. # In Unix:
  35. # Same semantics as directly locking a file: mutual exclusion is
  36. # provided when a process can lock the file. If
  37. # the file does not exist, it is created.
  38. #
  39. # In NT:
  40. # Uses Windows CreateMutex.
  41. # obtain_mutex = 1
  42. # mutex = win32event.CreateMutex(None, obtain_mutex, name)
  43. def __init__(self, name):
  44. self._mylock = False
  45. self._name = name
  46. self._mutex = None
  47. if os.name in ('posix','max'):
  48. ddir = platform.get_dot_dir()
  49. self._path = os.path.join( ddir, "mutex", name )
  50. if not os.path.exists(ddir):
  51. os.mkdir(ddir, 0700)
  52. def owner(self):
  53. return self._mylock
  54. def acquire(self, wait=True):
  55. """Acquires mutual exclusion. Returns true iff acquired."""
  56. if os.name == 'nt':
  57. # Gotcha: Not checking self._mylock in windows, because it is
  58. # possible to acquire the mutex from more than one object with
  59. # the same name. This needs some work to make the semantics
  60. # exactly the same between Windows and Unix.
  61. obtain_mutex = 1
  62. self._mutex = win32event.CreateMutex(None,obtain_mutex,self._name)
  63. if self._mutex is None:
  64. return False
  65. lasterror = win32api.GetLastError()
  66. if lasterror == winerror.ERROR_ALREADY_EXISTS:
  67. if wait:
  68. # mutex exists and has been opened(not created, not locked).
  69. r = win32event.WaitForSingleObject(self._mutex,
  70. win32event.INFINITE)
  71. else:
  72. r = win32event.WaitForSingleObject(self._mutex, 0)
  73. # WAIT_OBJECT_0 means the mutex was obtained
  74. # WAIT_ABANDONED means the mutex was obtained,
  75. # and it had previously been abandoned
  76. if (r != win32event.WAIT_OBJECT_0 and
  77. r != win32event.WAIT_ABANDONED):
  78. return False
  79. elif os.name in ('posix','max'):
  80. if self._mylock:
  81. return True
  82. if os.path.exists(self._path) and not os.path.isfile(self._path):
  83. raise BTFailure(
  84. "Cannot lock file that is not regular file." )
  85. (dir,name) = os.path.split(self._path)
  86. if not os.path.exists(dir):
  87. os.mkdir(dir, 0700) # mode=0700 = allow user access.
  88. while True: # <--- for file deletion race condition (see __del__)
  89. # UNIX does not support O_TEMPORARY!! Blech. This complicates
  90. # file deletion (see "while True" above and path checking after
  91. # "flock").
  92. #self._mutex = os.open( self._path, os.O_CREAT|os.O_TEMPORARY )
  93. self._mutex = open( self._path, "w" )
  94. if wait:
  95. flags = fcntl.LOCK_EX
  96. else:
  97. flags = fcntl.LOCK_EX | fcntl.LOCK_NB
  98. try:
  99. flock( self._mutex.fileno(), flags)
  100. except IOError:
  101. return False
  102. # race condition: __del__ may have deleted the file.
  103. if not os.path.exists( self._path ):
  104. self._mutex.close()
  105. else:
  106. break
  107. else:
  108. # dangerous, but what should we do if the platform neither
  109. # supports named mutexes nor file locking? --Dave
  110. pass
  111. self._mylock = True
  112. return True
  113. def release(self):
  114. # unlock
  115. assert self._mylock
  116. if os.name == 'nt':
  117. win32event.ReleaseMutex(self._mutex)
  118. # Error code 123?
  119. #lasterror = win32api.GetLastError()
  120. #if lasterror != 0:
  121. # raise IOError( _("Could not release mutex %s due to "
  122. # "error windows code %d.") %
  123. # (self._name,lasterror) )
  124. elif os.name == 'posix':
  125. self._mylock = False
  126. if not os.path.exists(self._path):
  127. raise IOError( _("Non-existent file: %s") % self._path )
  128. flock( self._mutex.fileno(), fcntl.LOCK_UN )
  129. self._mutex.close()
  130. def __del__(self):
  131. if os.name == 'nt':
  132. if self._mutex is not None:
  133. # Gotchas: Don't close the handle before releasing it or the
  134. # mutex won't release until the python script exits. It's
  135. # safe to call ReleaseMutex even if the local process hasn't
  136. # acquired the mutex. If this process doesn't have mutex then
  137. # the call fails. Note that in Windows, mutexes are literally
  138. # per process. Multiple mutexes created with the same name
  139. # from the same process will be treated as one with respect to
  140. # release and acquire.
  141. self.release()
  142. # windows will destroy the mutex when the last handle to that
  143. # mutex is closed.
  144. win32file.CloseHandle(self._mutex)
  145. del self._mutex
  146. elif os.name == 'posix':
  147. if self._mylock:
  148. self.release()
  149. # relock file non-blocking to see if anyone else was
  150. # waiting on it. (A race condition exists where another process
  151. # could have just opened but not yet locked the file. This process
  152. # then deletes the file. When the other process resumes, the
  153. # flock call fails. This is however not a particularly bad
  154. # race condition since acquire() simply repeats the open & flock.)
  155. if os.path.exists(self._path) and self.acquire(False):
  156. try:
  157. os.remove(self._path)
  158. except:
  159. pass
  160. flock( self._mutex.fileno(), fcntl.LOCK_UN )
  161. self._mutex.close()
  162. if __name__ == "__main__":
  163. # perform unit tests.
  164. n_tests = n_tests_passed = 0
  165. n_tests += 1
  166. mutex = NamedMutex("blah")
  167. if mutex.acquire() and mutex._mutex is not None and mutex._mylock:
  168. n_tests_passed += 1
  169. else:
  170. print "FAIL! Failed to acquire mutex on a new NamedMutex."
  171. n_tests += 1
  172. mutex.release()
  173. if mutex._mutex is not None and not mutex._mylock:
  174. n_tests_passed += 1
  175. else:
  176. print "FAIL! Did not properly release mutex."
  177. n_tests += 1
  178. if mutex.acquire():
  179. if mutex._mutex is not None and mutex._mylock:
  180. n_tests_passed += 1
  181. else:
  182. print ( "FAIL! After calling acquire on a released NamedMutex, "
  183. "either mutex._mutex is None or mutex._mylock is false." )
  184. else:
  185. print "FAIL! Failed to acquire mutex a released NamedMutex."
  186. # okay. I should add more tests.
  187. del mutex
  188. if n_tests == n_tests_passed:
  189. print "Passed all %d tests." % n_tests