1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
#!/usr/local/bin/python -u
""" FileLock - Implements a file lock mechanism that does not depend
on fcntl.
Copyright (c) 1997-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
See the documentation for further information on copyrights,
or contact the author. All Rights Reserved.
"""
from ExitFunctions import ExitFunctions
import os,exceptions,time,string
# Get fully qualified hostname
def _fqhostname(hostname=None,default=('localhost','127.0.0.1')):
""" Returns fully qualified (hostname, ip) for the given hostname.
If hostname is not given, the default name of the local host
is chosen.
Defaults to default in case an error occurs while trying to
determine the data.
"""
try:
import socket
except ImportError:
return default
try:
if hostname is None:
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
hostname = socket.gethostbyaddr(ip)[0]
except socket.error:
return default
else:
return hostname,ip
hostname,ip = _fqhostname()
### Errors
class Error(exceptions.StandardError):
pass
# Backward compatibility:
FileLockError = Error
### Baseclass using symbolic links
class SymbolicFileLock:
""" Implements a file lock mechanism that uses symbolic links
for locking.
Note that since the mechanism does not use file system
function calls this may not always work in the desired
way.
The lock is acquired per process, not per thread.
Instancevariables:
filename - file the lock applies to
lockfilename - name of the lock file
locked - indicator if the lock is in position (1) or not (0)
"""
# Do we hold the lock ?
locked = 0
def __init__(self,filename):
self.filename = filename
self.lockfilename = filename + '.locked'
self.locked = 0
# Avoid deadlocks
ExitFunctions.register(self.unlock)
def __del__(self):
if self.locked:
self.unlock(0)
try:
ExitFunctions.deregister(self.unlock)
except:
pass
def lock(self,timeout=500,sleeptime=0.0001,
sleep=time.sleep,Error=Error,time=time.time,error=os.error,
hostname=hostname,ip=ip):
""" Try to lock the file for this process, waiting
timeout ms if necessary.
Raises an exception if a timeout occurs. Multiple locking
by the same process is not an error.
Note that a non existent path to the file will also result
in a timeout.
If the lock is held by a process running on our host, a
timeout will first invoke a check of the locking
process. If it is not alive anymore, the lock is removed
and granted to the current process.
"""
if self.locked:
return
lockfilename = self.lockfilename
lockinfo = '%s:%i' % (hostname,os.getpid())
stop = time() + timeout * 0.001
# Localize these for speed
islink=os.path.islink
makelink=os.symlink
readlink=os.readlink
while 1:
# These are rather time-critical
if not islink(lockfilename):
try:
makelink(lockinfo,lockfilename)
except error:
# A non existent path will result in a time out.
pass
else:
break
sleep(sleeptime)
if time() > stop:
# Timeout...
try:
host,locking_pid = string.split(readlink(lockfilename),':')
except Error,why:
raise Error,\
'file "%s" could not be locked: %s' % \
(self.filename,why)
locking_pid = string.atoi(locking_pid)
if host != hostname:
# Ok, then compare via IPs
other_ip = _fqhostname(host,default=('???','???'))[1]
samehost = (ip == other_ip)
else:
samehost = 1
if samehost:
# Check whether the locking process is still alive
try:
os.kill(locking_pid,0)
except error,why:
# It's gone without a trace...
try:
os.unlink(self.lockfilename)
except error:
# We probably don't have proper permissions.
pass
else:
continue
raise Error,\
'file "%s" is locked by process %s:%i' % \
(self.filename,host,locking_pid,hostname)
self.locked = 1
def unlock(self,sleeptime=0.0001,
unlink=os.unlink,Error=Error,sleep=time.sleep,error=os.error):
""" Release the lock, letting other processes using this
mechanism access the file.
Multiple unlocking is not an error. Raises an exception if
the lock file was already deleted by another process.
After having unlocked the file the process sleeps for
sleeptime seconds to give other processes a chance to
acquire the lock too. If the lock will only be used every
once in a while by the process, it is safe to set it to 0.
"""
if not self.locked:
return
self.locked = 0
try:
unlink(self.lockfilename)
except error:
raise Error,'lock file "%s" is already gone' % \
self.lockfilename
# Give other processes a chance too
if sleeptime:
sleep(sleeptime)
return 1
def remove_lock(self,
unlink=os.unlink):
""" Remove any existing lock on the file.
"""
self.locked = 0
try:
unlink(self.lockfilename)
except:
pass
def __repr__(self):
return '<%s for "%s" at %x>' % (self.__class__.__name__,
self.filename,
id(self))
# Alias
FileLock = SymbolicFileLock
def _test():
lock = SymbolicFileLock('test.lock')
for i in range(10000):
print '%i\r'%i,
lock.lock()
time.sleep(i/100000.0)
lock.unlock()
#time.sleep(i/100000.0)
print
if __name__ == '__main__':
_test()
|