| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- #!/usr/bin/python
- #
- # Copyright 2006-2007 BitTorrent, Inc. All Rights Reserved.
- #
- # Written by Ben Teitelbaum
- import os
- import sys
- import socket
- from time import asctime, gmtime, time, sleep
- from twisted.internet import reactor, task
- class dlock(object):
- def __init__(self, deadlockfile, update_period=300, myhost=None, debug=None):
- if myhost == None: myhost = socket.gethostname()
- self.host = myhost
- self.pid = os.getpid()
- self.deadlockfile = deadlockfile
- self.refresher = task.LoopingCall(self.refresh)
- self.update_period = update_period
- self.debug = debug
- # Block until lock is acquired, then refresh the lock file every
- # update_period seconds.
- #
- # Nota Bene: while blocked on acquiring the lock, this sleeps the
- # whole process; once the lock is acquired, an event-driven model
- # (twisted reactor) is presumed. The intended use (see test at
- # bottom) is to block on acquire before running the Twisted
- # reactor.
- #
- def acquire(self):
- while True:
- while self.islocked():
- if self.debug:
- lock = self._readlock()
- print '%s locked by %s' % (self.deadlockfile, self._lockdict2string(lock))
- sleep(self.update_period)
- try:
- # Use link count hack to work around NFS's broken
- # file locking.
- tempfile = '.' + str(self.pid) + self.host + str(time()) + '.tmp'
- lockfile = self.deadlockfile + '.lock'
- # Create temp lock file
- fh = open(tempfile, "w")
- fh.close()
- # Atomicallly create lockfile as a hard link
- try:
- os.link(tempfile, lockfile)
- except:
- if self.debug:
- print "tempfile: " + tempfile
- print "lockfile: " + lockfile
- raise
- # Check the number of links
- if os.stat(tempfile)[3] == os.stat(lockfile)[3]:
- # Hooray, I have the write lock on the deadlock file!
- self._timestamp_deadlockfile(time())
- if self.debug:
- lock = self._readlock()
- print '%s acquired by %s' % (self.deadlockfile, self._lockdict2string(lock))
- self.refresher.start(self.update_period)
- # Release the lock
- os.unlink(tempfile)
- os.unlink(lockfile)
- return self
- else:
- # Failed to grab write lock on deadlock file, keep looping
- if self.debug:
- print '%d failed to grab write lock on deadlock file: %s (will retry)' % (self.pid, self.deadlockfile)
- except:
- if self.debug:
- print 'File Lock Error: %s@%s could not acquire %s' % (self.pid, self.host, self.deadlockfile)
- raise
- def refresh(self):
- assert self.ownlock()
- # No need to grab a write lock on the deadlock file, since it's not stale
- self._timestamp_deadlockfile(time())
- def _timestamp_deadlockfile(self, ts):
- try:
- fh = open(self.deadlockfile, 'w')
- fh.write(self._lockstr(ts))
- fh.close()
- os.chmod(self.deadlockfile, 0644)
- except:
- if self.debug:
- print 'File Lock Error: %s@%s could not write %s' % (self.pid, self.host, self.deadlockfile)
- raise
- def release(self):
- if self.ownlock():
- try:
- self.refresher.stop()
- self._timestamp_deadlockfile(0)
- if self.debug:
- print '%s@%s released lock %s' % (self.pid, self.host, self.deadlockfile)
- except:
- if self.debug:
- print 'File Lock Error: %s@%s could not release %s' % (self.pid, self.host, self.deadlockfile)
- raise
- return self
- def islocked(self):
- try:
- if self._isstale():
- # Lock seems stale, wait for one more update period and check again
- sleep(self.update_period)
- return not self._isstale()
- else:
- return True
- except:
- if self.debug:
- print "islocked exception"
- return False
- def _isstale(self):
- lock = self._readlock()
- if time() - lock['timestamp'] > self.update_period:
- return True
- else:
- return False
- def _readlock(self):
- try:
- lock = {}
- fh = open(self.deadlockfile)
- data = fh.read().split()
- fh.close()
- assert len(data) == 3
- lock['pid'] = int(data[0])
- lock['host'] = data[1]
- lock['timestamp'] = float(data[2])
- return lock
- except:
- if self.debug:
- print 'File Lock Error: %s@%s reading %s' % (self.pid, self.host, self.deadlockfile)
- raise
- # Public method to read a lockfile.
- @classmethod
- def readlock(cls, lockfile):
- lock = cls(deadlockfile=lockfile, myhost='dummy')
- return lock._readlock()
- def _lockdict2string(self, lock):
- return '%s@%s at %s' % (lock['pid'], lock['host'], asctime(gmtime(lock['timestamp'])))
- def _lockstr(self, ts):
- return '%d %s %f'%(self.pid, self.host, ts)
- def ownlock(self):
- lock = self._readlock()
- return (self.host == lock['host'] and
- self.pid == lock['pid'])
- def __del__(self):
- self.release()
- # Tests
- #
- # Run several in parallel on multiple machines, but have at most one
- # whack the deadlock file on initialization.
- #
- def run_tests(argv=None):
- if argv is None:
- argv = sys.argv
- deadlockfile = './dlock_test'
- l = dlock(deadlockfile, 5, debug=True)
- # Stupid argv handling; just grab first arg and run that test
- if len(argv) > 1:
- if argv[1] == 'none':
- print "Removing deadlock file."
- os.unlink(deadlockfile)
- elif argv[1] == 'old':
- print "Creating stale deadlock file owned by no one."
- fh = open(l.deadlockfile, 'w')
- fh.write('%d %s %f'%(0, 0, 0))
- fh.close()
- elif argv[1] == 'new':
- print "Creating fresh deadlock file owned by no one."
- fh = open(l.deadlockfile, 'w')
- fh.write('%d %s %f'%(0, 0, time()))
- fh.close()
- else:
- print "Un-known arg--starting with old deadlock file."
- else:
- print "Starting with old deadlock file."
- # Tease for a while, then release the lock
- def tease(l, n):
- if n > 0:
- assert l.ownlock()
- print 'I (%d) have the lock--ha, ha ha!'%os.getpid()
- reactor.callLater(1, tease, l, n - 1)
- else:
- l.release()
- # Start teasing once reactor is run
- reactor.callLater(1, tease, l, 20)
- # But first, grab the lock (this blocks)
- l.acquire()
- reactor.run()
- if __name__ == "__main__":
- sys.exit(run_tests())
|