| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- # The contents of this file are subject to the BitTorrent Open Source License
- # Version 1.1 (the License). You may not copy or use this file, in either
- # source code or executable form, except in compliance with the License. You
- # may obtain a copy of the License at http://www.bittorrent.com/license/.
- #
- # Software distributed under the License is distributed on an AS IS basis,
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- # for the specific language governing rights and limitations under the
- # License.
- # Author: David Harrison
- if __name__ == "__main__":
- def _(s):
- return s
- else:
- from BTL.translation import _
- import os
- if os.name == 'nt':
- import win32file
- import win32event
- #import BTL.likewin32api as win32api
- import win32api
- import winerror
- import pywintypes
- elif os.name == 'posix':
- import fcntl
- from BitTorrent import platform
- from fcntl import flock
- class NamedMutex(object):
- """Reasonably cross-platform, cross-process mutex.
- It does not implement mutual exclusion between threads in the
- same process. Use threading.Lock or threading.RLock for mutual
- exclusion between threads.
- """
- # In Unix:
- # Same semantics as directly locking a file: mutual exclusion is
- # provided when a process can lock the file. If
- # the file does not exist, it is created.
- #
- # In NT:
- # Uses Windows CreateMutex.
- # obtain_mutex = 1
- # mutex = win32event.CreateMutex(None, obtain_mutex, name)
- def __init__(self, name):
- self._mylock = False
- self._name = name
- self._mutex = None
- if os.name in ('posix','max'):
- ddir = platform.get_dot_dir()
- self._path = os.path.join( ddir, "mutex", name )
- if not os.path.exists(ddir):
- os.mkdir(ddir, 0700)
- def owner(self):
- return self._mylock
-
- def acquire(self, wait=True):
- """Acquires mutual exclusion. Returns true iff acquired."""
- if os.name == 'nt':
- # Gotcha: Not checking self._mylock in windows, because it is
- # possible to acquire the mutex from more than one object with
- # the same name. This needs some work to make the semantics
- # exactly the same between Windows and Unix.
- obtain_mutex = 1
- self._mutex = win32event.CreateMutex(None,obtain_mutex,self._name)
- if self._mutex is None:
- return False
- lasterror = win32api.GetLastError()
- if lasterror == winerror.ERROR_ALREADY_EXISTS:
- if wait:
- # mutex exists and has been opened(not created, not locked).
- r = win32event.WaitForSingleObject(self._mutex,
- win32event.INFINITE)
- else:
- r = win32event.WaitForSingleObject(self._mutex, 0)
- # WAIT_OBJECT_0 means the mutex was obtained
- # WAIT_ABANDONED means the mutex was obtained,
- # and it had previously been abandoned
- if (r != win32event.WAIT_OBJECT_0 and
- r != win32event.WAIT_ABANDONED):
- return False
-
- elif os.name in ('posix','max'):
- if self._mylock:
- return True
-
- if os.path.exists(self._path) and not os.path.isfile(self._path):
- raise BTFailure(
- "Cannot lock file that is not regular file." )
- (dir,name) = os.path.split(self._path)
- if not os.path.exists(dir):
- os.mkdir(dir, 0700) # mode=0700 = allow user access.
- while True: # <--- for file deletion race condition (see __del__)
- # UNIX does not support O_TEMPORARY!! Blech. This complicates
- # file deletion (see "while True" above and path checking after
- # "flock").
- #self._mutex = os.open( self._path, os.O_CREAT|os.O_TEMPORARY )
- self._mutex = open( self._path, "w" )
- if wait:
- flags = fcntl.LOCK_EX
- else:
- flags = fcntl.LOCK_EX | fcntl.LOCK_NB
- try:
- flock( self._mutex.fileno(), flags)
- except IOError:
- return False
- # race condition: __del__ may have deleted the file.
- if not os.path.exists( self._path ):
- self._mutex.close()
- else:
- break
-
- else:
- # dangerous, but what should we do if the platform neither
- # supports named mutexes nor file locking? --Dave
- pass
-
- self._mylock = True
- return True
-
- def release(self):
- # unlock
- assert self._mylock
- if os.name == 'nt':
- win32event.ReleaseMutex(self._mutex)
- # Error code 123?
- #lasterror = win32api.GetLastError()
- #if lasterror != 0:
- # raise IOError( _("Could not release mutex %s due to "
- # "error windows code %d.") %
- # (self._name,lasterror) )
- elif os.name == 'posix':
- self._mylock = False
- if not os.path.exists(self._path):
- raise IOError( _("Non-existent file: %s") % self._path )
- flock( self._mutex.fileno(), fcntl.LOCK_UN )
- self._mutex.close()
- def __del__(self):
- if os.name == 'nt':
- if self._mutex is not None:
- # Gotchas: Don't close the handle before releasing it or the
- # mutex won't release until the python script exits. It's
- # safe to call ReleaseMutex even if the local process hasn't
- # acquired the mutex. If this process doesn't have mutex then
- # the call fails. Note that in Windows, mutexes are literally
- # per process. Multiple mutexes created with the same name
- # from the same process will be treated as one with respect to
- # release and acquire.
- self.release()
- # windows will destroy the mutex when the last handle to that
- # mutex is closed.
- win32file.CloseHandle(self._mutex)
- del self._mutex
-
- elif os.name == 'posix':
- if self._mylock:
- self.release()
- # relock file non-blocking to see if anyone else was
- # waiting on it. (A race condition exists where another process
- # could have just opened but not yet locked the file. This process
- # then deletes the file. When the other process resumes, the
- # flock call fails. This is however not a particularly bad
- # race condition since acquire() simply repeats the open & flock.)
- if os.path.exists(self._path) and self.acquire(False):
- try:
- os.remove(self._path)
- except:
- pass
- flock( self._mutex.fileno(), fcntl.LOCK_UN )
- self._mutex.close()
- if __name__ == "__main__":
- # perform unit tests.
- n_tests = n_tests_passed = 0
- n_tests += 1
- mutex = NamedMutex("blah")
- if mutex.acquire() and mutex._mutex is not None and mutex._mylock:
- n_tests_passed += 1
- else:
- print "FAIL! Failed to acquire mutex on a new NamedMutex."
- n_tests += 1
- mutex.release()
- if mutex._mutex is not None and not mutex._mylock:
- n_tests_passed += 1
- else:
- print "FAIL! Did not properly release mutex."
- n_tests += 1
- if mutex.acquire():
- if mutex._mutex is not None and mutex._mylock:
- n_tests_passed += 1
- else:
- print ( "FAIL! After calling acquire on a released NamedMutex, "
- "either mutex._mutex is None or mutex._mylock is false." )
- else:
- print "FAIL! Failed to acquire mutex a released NamedMutex."
- # okay. I should add more tests.
- del mutex
- if n_tests == n_tests_passed:
- print "Passed all %d tests." % n_tests
|