If you only have games of a single limit type (fixed, pot, or no limit), but of more...
[fpdb-dooglus.git] / pyfpdb / interlocks.py
blob3ec53b4dd493ca0a6803622e50dd6d0983967029
1 # -*- coding: utf-8 -*-
3 # Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
4 # Thanks JJ!
6 import L10n
7 _ = L10n.get_translation()
9 import sys
10 import os, os.path
11 import subprocess
12 import time
13 import signal
14 import base64
16 InterProcessLock = None
18 """
19 Just use me like a thread lock. acquire() / release() / locked()
21 Differences compared to thread locks:
22 1. By default, acquire()'s wait parameter is false.
23 2. When acquire fails, SingleInstanceError is thrown instead of simply returning false.
24 3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking
25 mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl.
27 Differences in fpdb version to JJ's original:
28 1. Changed acquire() to return false like other locks
29 2. Made acquire fail if same process already has the lock
30 """
32 class SingleInstanceError(RuntimeError):
33 "Thrown when you try to acquire an InterProcessLock and another version of the process is already running."
35 class InterProcessLockBase:
36 def __init__(self, name=None ):
37 self._has_lock = False
38 if not name:
39 name = sys.argv[0]
40 self.name = name
41 self.heldBy = None
43 def getHashedName(self):
44 return base64.b64encode(self.name).replace('=','')
46 def acquire_impl(self, wait): abstract
48 def acquire(self, source, wait=False, retry_time=1):
49 if source == None:
50 source="Unknown"
51 if self._has_lock: # make sure 2nd acquire in same process fails
52 print _("lock already held by:"),self.heldBy
53 return False
54 while not self._has_lock:
55 try:
56 self.acquire_impl(wait)
57 self._has_lock = True
58 self.heldBy=source
59 #print 'i have the lock'
60 except SingleInstanceError:
61 if not wait:
62 # raise # change back to normal acquire functionality, sorry JJ!
63 return False
64 time.sleep(retry_time)
65 return True
67 def release(self):
68 self.release_impl()
69 self._has_lock = False
70 self.heldBy=None
72 def locked(self):
73 if self.acquire():
74 self.release()
75 return False
76 return True
78 LOCK_FILE_DIRECTORY = '/tmp'
80 class InterProcessLockFcntl(InterProcessLockBase):
81 def __init__(self, name=None):
82 InterProcessLockBase.__init__(self, name)
83 self.lockfd = 0
84 self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck')
85 assert(os.path.isdir(LOCK_FILE_DIRECTORY))
87 # This is the suggested way to get a safe file name, but I like having a descriptively named lock file.
88 def getHashedName(self):
89 import re
90 bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]')
91 return bad_filename_character_re.sub('_',self.name)
93 def acquire_impl(self, wait):
94 self.lockfd = open(self.lock_file_name, 'w')
95 fcntrl_options = fcntl.LOCK_EX
96 if not wait:
97 fcntrl_options |= fcntl.LOCK_NB
98 try:
99 fcntl.flock(self.lockfd, fcntrl_options)
100 except IOError:
101 self.lockfd.close()
102 self.lockfd = 0
103 raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name)
105 def release_impl(self):
106 fcntl.lockf(self.lockfd, fcntl.LOCK_UN)
107 self.lockfd.close()
108 self.lockfd = 0
109 try:
110 os.unlink(self.lock_file_name)
111 except IOError:
112 # We don't care about the existence of the file too much here. It's the flock() we care about,
113 # And that should just go away magically.
114 pass
116 class InterProcessLockWin32(InterProcessLockBase):
117 def __init__(self, name=None):
118 InterProcessLockBase.__init__(self, name)
119 self.mutex = None
121 def acquire_impl(self,wait):
122 self.mutex = win32event.CreateMutex(None, 0, self.getHashedName())
123 if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
124 self.mutex.Close()
125 self.mutex = None
126 raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
128 def release_impl(self):
129 self.mutex.Close()
131 class InterProcessLockSocket(InterProcessLockBase):
132 def __init__(self, name=None):
133 InterProcessLockBase.__init__(self, name)
134 self.socket = None
135 self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749
137 def acquire_impl(self, wait):
138 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
139 try:
140 self.socket.bind(('127.0.0.1', self.portno))
141 except socket.error:
142 self.socket.close()
143 self.socket = None
144 raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
146 def release_impl(self):
147 self.socket.close()
148 self.socket = None
150 # Set InterProcessLock to the correct type given the sysem parameters available
151 try:
152 import fcntl
153 InterProcessLock = InterProcessLockFcntl
154 except ImportError:
155 try:
156 import win32event
157 import win32api
158 import winerror
159 InterProcessLock = InterProcessLockWin32
160 except ImportError:
161 import socket
162 InterProcessLock = InterProcessLockSocket
164 def test_construct():
166 # Making the name of the test unique so it can be executed my multiple users on the same machine.
167 >>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time())
169 >>> lock1 = InterProcessLock(name=test_name)
170 >>> lock1.acquire()
171 True
173 >>> lock2 = InterProcessLock(name=test_name)
174 >>> lock3 = InterProcessLock(name=test_name)
176 # Since lock1 is locked, other attempts to acquire it fail.
177 >>> lock2.acquire()
178 False
180 >>> lock3.acquire()
181 False
183 # Release the lock and let lock2 have it.
184 >>> lock1.release()
185 >>> lock2.acquire()
186 True
188 >>> lock3.acquire()
189 False
191 # Release it and give it back to lock1
192 >>> lock2.release()
193 >>> lock1.acquire()
194 True
196 >>> lock2.acquire()
197 False
199 # Test lock status
200 >>> lock2.locked()
201 True
202 >>> lock3.locked()
203 True
204 >>> lock1.locked()
205 True
207 >>> lock1.release()
209 >>> lock2.locked()
210 False
211 >>> lock3.locked()
212 False
213 >>> lock1.locked()
214 False
216 >>> if os.name == 'posix':
217 ... def os_independent_kill(pid):
218 ... import signal
219 ... os.kill(pid, signal.SIGKILL)
220 ... else:
221 ... assert(os.name == 'nt')
222 ... def os_independent_kill(pid):
223 ... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows '''
224 ... import win32api
225 ... import win32con
226 ... import pywintypes
227 ... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
228 ... #return (0 != win32api.TerminateProcess(handle, 0))
230 # Test to acquire the lock in another process.
231 >>> def execute(cmd):
232 ... cmd = 'import time;' + cmd + 'time.sleep(10);'
233 ... process = subprocess.Popen([sys.executable, '-c', cmd])
234 ... pid = process.pid
235 ... time.sleep(2) # quick hack, but we test synchronization in the end
236 ... return pid
238 >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
240 >>> lock1.acquire()
241 False
243 >>> os_independent_kill(pid)
245 >>> time.sleep(1)
247 >>> lock1.acquire()
248 True
249 >>> lock1.release()
251 # Testing wait
253 >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
255 >>> lock1.acquire()
256 False
258 >>> os_independent_kill(pid)
260 >>> lock1.acquire(True)
261 True
262 >>> lock1.release()
266 pass
268 if __name__=='__main__':
269 import doctest
270 doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)