72
72
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
73
73
'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock']
75
76
class Error(Exception):
77
Base class for other exceptions.
77
"""Base class for other exceptions.
81
81
... except Exception:
86
88
class LockError(Error):
88
Base class for error arising from attempts to acquire the lock.
89
"""Base class for error arising from attempts to acquire the lock.
91
92
... raise LockError
97
100
class LockTimeout(LockError):
98
101
"""Raised when lock creation fails within a user-defined period of time.
121
127
... raise LockFailed
122
128
... except LockError:
127
135
class UnlockError(Error):
129
Base class for errors arising from attempts to release the lock.
136
"""Base class for errors arising from attempts to release the lock.
132
139
... raise UnlockError
133
140
... except Error:
138
147
class NotLocked(UnlockError):
139
148
"""Raised when an attempt is made to unlock an unlocked file.
152
163
... raise NotMyLock
153
164
... except UnlockError:
159
172
"""Base class for platform-specific lock classes."""
160
174
def __init__(self, path, threaded=True):
162
176
>>> lock = LockBase('somefile')
163
177
>>> lock = LockBase('somefile', threaded=False)
166
self.lock_file = os.path.abspath(path) + ".lock"
180
self.lock_file = os.path.abspath(path) + '.lock'
167
181
self.hostname = socket.gethostname()
168
182
self.pid = os.getpid()
170
tname = "%s-" % threading.current_thread().get_name()
184
tname = '%s-' % threading.current_thread().get_name()
173
187
dirname = os.path.dirname(self.lock_file)
174
188
self.unique_name = os.path.join(dirname,
175
"%s.%s%s" % (self.hostname,
189
'%s.%s%s' % (self.hostname,
179
193
def acquire(self, timeout=None):
183
196
* If timeout is omitted (or None), wait forever trying to lock the
190
203
* If timeout <= 0, raise AlreadyLocked immediately if the file is
193
raise NotImplemented("implement in subclass")
207
raise NotImplemented('implement in subclass')
195
209
def release(self):
199
212
If the file is not locked, raise NotLocked.
201
raise NotImplemented("implement in subclass")
215
raise NotImplemented('implement in subclass')
203
217
def is_locked(self):
205
Tell whether or not the file is locked.
207
raise NotImplemented("implement in subclass")
218
"""Tell whether or not the file is locked."""
219
raise NotImplemented('implement in subclass')
209
221
def i_am_locking(self):
211
Return True if this object is locking the file.
213
raise NotImplemented("implement in subclass")
222
"""Return True if this object is locking the file."""
223
raise NotImplemented('implement in subclass')
215
225
def break_lock(self):
217
Remove a lock. Useful if a locking thread failed to unlock.
219
raise NotImplemented("implement in subclass")
228
Useful if a locking thread failed to unlock.
231
raise NotImplemented('implement in subclass')
221
233
def __enter__(self):
223
Context manager support.
234
"""Context manager support."""
228
238
def __exit__(self, *_exc):
230
Context manager support.
239
"""Context manager support."""
234
243
class LinkFileLock(LockBase):
235
244
"""Lock access to a file using atomic property of link(2)."""
237
246
def acquire(self, timeout=None):
239
open(self.unique_name, "wb").close()
248
open(self.unique_name, 'wb').close()
288
297
if os.path.exists(self.lock_file):
289
298
os.unlink(self.lock_file)
291
301
class MkdirFileLock(LockBase):
292
302
"""Lock file by creating a directory."""
293
304
def __init__(self, path, threaded=True):
295
306
>>> lock = MkdirFileLock('somefile')
298
309
LockBase.__init__(self, path, threaded)
300
tname = "%x-" % thread.get_ident()
311
tname = '%x-' % thread.get_ident()
303
314
# Lock file itself is a directory. Place the unique file name into
305
self.unique_name = os.path.join(self.lock_file,
306
"%s.%s%s" % (self.hostname,
316
self.unique_name = os.path.join(self.lock_file,
317
'%s.%s%s' % (self.hostname,
310
321
def acquire(self, timeout=None):
311
322
end_time = time.time()
362
373
os.unlink(os.path.join(self.lock_file, name))
363
374
os.rmdir(self.lock_file)
365
377
class SQLiteFileLock(LockBase):
366
"Demonstration of using same SQL-based locking."
378
'Demonstration of using same SQL-based locking.'
369
381
_fd, testdb = tempfile.mkstemp()
380
392
self.connection = sqlite3.connect(SQLiteFileLock.testdb)
382
394
c = self.connection.cursor()
384
c.execute("create table locks"
386
" lock_file varchar(32),"
387
" unique_name varchar(32)"
396
c.execute('create table locks'
398
' lock_file varchar(32),'
399
' unique_name varchar(32)'
389
401
except sqlite3.OperationalError:
411
423
if not self.is_locked():
412
424
# Not locked. Try to lock it.
413
cursor.execute("insert into locks"
414
" (lock_file, unique_name)"
425
cursor.execute('insert into locks'
426
' (lock_file, unique_name)'
417
429
(self.lock_file, self.unique_name))
418
430
self.connection.commit()
420
432
# Check to see if we are the only lock holder.
421
cursor.execute("select * from locks"
422
" where unique_name = ?",
433
cursor.execute('select * from locks'
434
' where unique_name = ?',
423
435
(self.unique_name,))
424
436
rows = cursor.fetchall()
425
437
if len(rows) > 1:
426
438
# Nope. Someone else got there. Remove our lock.
427
cursor.execute("delete from locks"
428
" where unique_name = ?",
439
cursor.execute('delete from locks'
440
' where unique_name = ?',
429
441
(self.unique_name,))
430
442
self.connection.commit()
435
447
# Check to see if we are the only lock holder.
436
cursor.execute("select * from locks"
437
" where unique_name = ?",
448
cursor.execute('select * from locks'
449
' where unique_name = ?',
438
450
(self.unique_name,))
439
451
rows = cursor.fetchall()
440
452
if len(rows) == 1:
441
453
# We're the locker, so go home.
444
456
# Maybe we should wait a bit longer.
445
457
if timeout is not None and time.time() > end_time:
459
471
if not self.i_am_locking():
460
472
raise NotMyLock((self._who_is_locking(), self.unique_name))
461
473
cursor = self.connection.cursor()
462
cursor.execute("delete from locks"
463
" where unique_name = ?",
474
cursor.execute('delete from locks'
475
' where unique_name = ?',
464
476
(self.unique_name,))
465
477
self.connection.commit()
467
479
def _who_is_locking(self):
468
480
cursor = self.connection.cursor()
469
cursor.execute("select unique_name from locks"
470
" where lock_file = ?",
481
cursor.execute('select unique_name from locks'
482
' where lock_file = ?',
471
483
(self.lock_file,))
472
484
return cursor.fetchone()[0]
474
486
def is_locked(self):
475
487
cursor = self.connection.cursor()
476
cursor.execute("select * from locks"
477
" where lock_file = ?",
488
cursor.execute('select * from locks'
489
' where lock_file = ?',
478
490
(self.lock_file,))
479
491
rows = cursor.fetchall()
480
492
return not not rows
482
494
def i_am_locking(self):
483
495
cursor = self.connection.cursor()
484
cursor.execute("select * from locks"
485
" where lock_file = ?"
486
" and unique_name = ?",
496
cursor.execute('select * from locks'
497
' where lock_file = ?'
498
' and unique_name = ?',
487
499
(self.lock_file, self.unique_name))
488
500
return not not cursor.fetchall()
490
502
def break_lock(self):
491
503
cursor = self.connection.cursor()
492
cursor.execute("delete from locks"
493
" where lock_file = ?",
504
cursor.execute('delete from locks'
505
' where lock_file = ?',
494
506
(self.lock_file,))
495
507
self.connection.commit()
497
if hasattr(os, "link"):
509
if hasattr(os, 'link'):
498
510
FileLock = LinkFileLock
500
512
FileLock = MkdirFileLock