📄 namedmutex.py
字号:
# 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 Harrisonif __name__ == "__main__": def _(s): return selse: from BitTorrent.translation import _import osif os.name == 'nt': import win32file, win32event, win32api, winerror from win32file import CreateFile, CreateFileW import pywintypeselif os.name == 'posix': import fcntl from BitTorrent import platform from fcntl import flockclass 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'): # We don't use config["data_dir"] to keep mutex separate from # other configuration data since bittorrent.py will have a # separate config directory from bittorrent console and curses # apps. 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. win32api.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 # Strange to unlock the file after it has been deleted. # Works on all UNIX platforms? If I release before # deleting then I introduce a race condition where the # another process acquires the lock then # this process deletes the file. 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." # attempt to acquire again. Since already acquired, it should fail. #n_tests += 1 #if not mutex.acquire(): # n_tests_passed += 1 #else: # print "FAIL! Second acquire should return false but returned true." 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -