~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to notification/lockfile.py

  • Committer: Holger Rapp
  • Date: 2010-09-26 13:30:30 UTC
  • Revision ID: sirver@gmx.de-20100926133030-ceirjf83vde91tyt
Added a simple events model to display dates on the homepage

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
"""
3
 
lockfile.py - Platform-independent advisory file locks.
4
 
 
5
 
Requires Python 2.5 unless you apply 2.4.diff
6
 
Locking is done on a per-thread basis instead of a per-process basis.
7
 
 
8
 
Usage:
9
 
 
10
 
>>> lock = FileLock('somefile')
11
 
>>> try:
12
 
...     lock.acquire()
13
 
... except AlreadyLocked:
14
 
...     print 'somefile', 'is locked already.'
15
 
... except LockFailed:
16
 
...     print 'somefile', 'can\\'t be locked.'
17
 
... else:
18
 
...     print 'got lock'
19
 
got lock
20
 
>>> print lock.is_locked()
21
 
True
22
 
>>> lock.release()
23
 
 
24
 
>>> lock = FileLock('somefile')
25
 
>>> print lock.is_locked()
26
 
False
27
 
>>> with lock:
28
 
...    print lock.is_locked()
29
 
True
30
 
>>> print lock.is_locked()
31
 
False
32
 
>>> # It is okay to lock twice from the same thread...
33
 
>>> with lock:
34
 
...     lock.acquire()
35
 
...
36
 
>>> # Though no counter is kept, so you can't unlock multiple times...
37
 
>>> print lock.is_locked()
38
 
False
39
 
 
40
 
Exceptions:
41
 
 
42
 
    Error - base class for other exceptions
43
 
        LockError - base class for all locking exceptions
44
 
            AlreadyLocked - Another thread or process already holds the lock
45
 
            LockFailed - Lock failed for some other reason
46
 
        UnlockError - base class for all unlocking exceptions
47
 
            AlreadyUnlocked - File was not locked.
48
 
            NotMyLock - File was locked but not by the current thread/process
49
 
"""
50
 
 
51
 
from __future__ import division
52
 
 
53
 
import sys
54
 
import socket
55
 
import os
56
 
import threading
57
 
import time
58
 
import errno
59
 
 
60
 
# Work with PEP8 and non-PEP8 versions of threading module.
61
 
try:
62
 
    threading.current_thread
63
 
except AttributeError:
64
 
    threading.current_thread = threading.currentThread
65
 
try:
66
 
    # python 2.6 has threading.current_thread so we need to do this separately.
67
 
    threading.Thread.get_name
68
 
except AttributeError:
69
 
    threading.Thread.get_name = threading.Thread.getName
70
 
 
71
 
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
72
 
           'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
73
 
           'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock']
74
 
 
75
 
 
76
 
class Error(Exception):
77
 
    """Base class for other exceptions.
78
 
 
79
 
    >>> try:
80
 
    ...   raise Error
81
 
    ... except Exception:
82
 
    ...   pass
83
 
 
84
 
    """
85
 
    pass
86
 
 
87
 
 
88
 
class LockError(Error):
89
 
    """Base class for error arising from attempts to acquire the lock.
90
 
 
91
 
    >>> try:
92
 
    ...   raise LockError
93
 
    ... except Error:
94
 
    ...   pass
95
 
 
96
 
    """
97
 
    pass
98
 
 
99
 
 
100
 
class LockTimeout(LockError):
101
 
    """Raised when lock creation fails within a user-defined period of time.
102
 
 
103
 
    >>> try:
104
 
    ...   raise LockTimeout
105
 
    ... except LockError:
106
 
    ...   pass
107
 
    """
108
 
    pass
109
 
 
110
 
 
111
 
class AlreadyLocked(LockError):
112
 
    """Some other thread/process is locking the file.
113
 
 
114
 
    >>> try:
115
 
    ...   raise AlreadyLocked
116
 
    ... except LockError:
117
 
    ...   pass
118
 
 
119
 
    """
120
 
    pass
121
 
 
122
 
 
123
 
class LockFailed(LockError):
124
 
    """Lock file creation failed for some other reason.
125
 
 
126
 
    >>> try:
127
 
    ...   raise LockFailed
128
 
    ... except LockError:
129
 
    ...   pass
130
 
 
131
 
    """
132
 
    pass
133
 
 
134
 
 
135
 
class UnlockError(Error):
136
 
    """Base class for errors arising from attempts to release the lock.
137
 
 
138
 
    >>> try:
139
 
    ...   raise UnlockError
140
 
    ... except Error:
141
 
    ...   pass
142
 
 
143
 
    """
144
 
    pass
145
 
 
146
 
 
147
 
class NotLocked(UnlockError):
148
 
    """Raised when an attempt is made to unlock an unlocked file.
149
 
 
150
 
    >>> try:
151
 
    ...   raise NotLocked
152
 
    ... except UnlockError:
153
 
    ...   pass
154
 
 
155
 
    """
156
 
    pass
157
 
 
158
 
 
159
 
class NotMyLock(UnlockError):
160
 
    """Raised when an attempt is made to unlock a file someone else locked.
161
 
 
162
 
    >>> try:
163
 
    ...   raise NotMyLock
164
 
    ... except UnlockError:
165
 
    ...   pass
166
 
 
167
 
    """
168
 
    pass
169
 
 
170
 
 
171
 
class LockBase:
172
 
    """Base class for platform-specific lock classes."""
173
 
 
174
 
    def __init__(self, path, threaded=True):
175
 
        """
176
 
        >>> lock = LockBase('somefile')
177
 
        >>> lock = LockBase('somefile', threaded=False)
178
 
        """
179
 
        self.path = path
180
 
        self.lock_file = os.path.abspath(path) + '.lock'
181
 
        self.hostname = socket.gethostname()
182
 
        self.pid = os.getpid()
183
 
        if threaded:
184
 
            tname = '%s-' % threading.current_thread().get_name()
185
 
        else:
186
 
            tname = ''
187
 
        dirname = os.path.dirname(self.lock_file)
188
 
        self.unique_name = os.path.join(dirname,
189
 
                                        '%s.%s%s' % (self.hostname,
190
 
                                                     tname,
191
 
                                                     self.pid))
192
 
 
193
 
    def acquire(self, timeout=None):
194
 
        """Acquire the lock.
195
 
 
196
 
        * If timeout is omitted (or None), wait forever trying to lock the
197
 
          file.
198
 
 
199
 
        * If timeout > 0, try to acquire the lock for that many seconds.  If
200
 
          the lock period expires and the file is still locked, raise
201
 
          LockTimeout.
202
 
 
203
 
        * If timeout <= 0, raise AlreadyLocked immediately if the file is
204
 
          already locked.
205
 
 
206
 
        """
207
 
        raise NotImplemented('implement in subclass')
208
 
 
209
 
    def release(self):
210
 
        """Release the lock.
211
 
 
212
 
        If the file is not locked, raise NotLocked.
213
 
 
214
 
        """
215
 
        raise NotImplemented('implement in subclass')
216
 
 
217
 
    def is_locked(self):
218
 
        """Tell whether or not the file is locked."""
219
 
        raise NotImplemented('implement in subclass')
220
 
 
221
 
    def i_am_locking(self):
222
 
        """Return True if this object is locking the file."""
223
 
        raise NotImplemented('implement in subclass')
224
 
 
225
 
    def break_lock(self):
226
 
        """Remove a lock.
227
 
 
228
 
        Useful if a locking thread failed to unlock.
229
 
 
230
 
        """
231
 
        raise NotImplemented('implement in subclass')
232
 
 
233
 
    def __enter__(self):
234
 
        """Context manager support."""
235
 
        self.acquire()
236
 
        return self
237
 
 
238
 
    def __exit__(self, *_exc):
239
 
        """Context manager support."""
240
 
        self.release()
241
 
 
242
 
 
243
 
class LinkFileLock(LockBase):
244
 
    """Lock access to a file using atomic property of link(2)."""
245
 
 
246
 
    def acquire(self, timeout=None):
247
 
        try:
248
 
            open(self.unique_name, 'wb').close()
249
 
        except IOError:
250
 
            raise LockFailed
251
 
 
252
 
        end_time = time.time()
253
 
        if timeout is not None and timeout > 0:
254
 
            end_time += timeout
255
 
 
256
 
        while True:
257
 
            # Try and create a hard link to it.
258
 
            try:
259
 
                os.link(self.unique_name, self.lock_file)
260
 
            except OSError:
261
 
                # Link creation failed.  Maybe we've double-locked?
262
 
                nlinks = os.stat(self.unique_name).st_nlink
263
 
                if nlinks == 2:
264
 
                    # The original link plus the one I created == 2.  We're
265
 
                    # good to go.
266
 
                    return
267
 
                else:
268
 
                    # Otherwise the lock creation failed.
269
 
                    if timeout is not None and time.time() > end_time:
270
 
                        os.unlink(self.unique_name)
271
 
                        if timeout > 0:
272
 
                            raise LockTimeout
273
 
                        else:
274
 
                            raise AlreadyLocked
275
 
                    time.sleep(timeout is not None and timeout / 10 or 0.1)
276
 
            else:
277
 
                # Link creation succeeded.  We're good to go.
278
 
                return
279
 
 
280
 
    def release(self):
281
 
        if not self.is_locked():
282
 
            raise NotLocked
283
 
        elif not os.path.exists(self.unique_name):
284
 
            raise NotMyLock
285
 
        os.unlink(self.unique_name)
286
 
        os.unlink(self.lock_file)
287
 
 
288
 
    def is_locked(self):
289
 
        return os.path.exists(self.lock_file)
290
 
 
291
 
    def i_am_locking(self):
292
 
        return (self.is_locked() and
293
 
                os.path.exists(self.unique_name) and
294
 
                os.stat(self.unique_name).st_nlink == 2)
295
 
 
296
 
    def break_lock(self):
297
 
        if os.path.exists(self.lock_file):
298
 
            os.unlink(self.lock_file)
299
 
 
300
 
 
301
 
class MkdirFileLock(LockBase):
302
 
    """Lock file by creating a directory."""
303
 
 
304
 
    def __init__(self, path, threaded=True):
305
 
        """
306
 
        >>> lock = MkdirFileLock('somefile')
307
 
        >>> lock = MkdirFileLock('somefile', threaded=False)
308
 
        """
309
 
        LockBase.__init__(self, path, threaded)
310
 
        if threaded:
311
 
            tname = '%x-' % thread.get_ident()
312
 
        else:
313
 
            tname = ''
314
 
        # Lock file itself is a directory.  Place the unique file name into
315
 
        # it.
316
 
        self.unique_name = os.path.join(self.lock_file,
317
 
                                        '%s.%s%s' % (self.hostname,
318
 
                                                     tname,
319
 
                                                     self.pid))
320
 
 
321
 
    def acquire(self, timeout=None):
322
 
        end_time = time.time()
323
 
        if timeout is not None and timeout > 0:
324
 
            end_time += timeout
325
 
 
326
 
        if timeout is None:
327
 
            wait = 0.1
328
 
        else:
329
 
            wait = max(0, timeout / 10)
330
 
 
331
 
        while True:
332
 
            try:
333
 
                os.mkdir(self.lock_file)
334
 
            except OSError:
335
 
                err = sys.exc_info()[1]
336
 
                if err.errno == errno.EEXIST:
337
 
                    # Already locked.
338
 
                    if os.path.exists(self.unique_name):
339
 
                        # Already locked by me.
340
 
                        return
341
 
                    if timeout is not None and time.time() > end_time:
342
 
                        if timeout > 0:
343
 
                            raise LockTimeout
344
 
                        else:
345
 
                            # Someone else has the lock.
346
 
                            raise AlreadyLocked
347
 
                    time.sleep(wait)
348
 
                else:
349
 
                    # Couldn't create the lock for some other reason
350
 
                    raise LockFailed
351
 
            else:
352
 
                open(self.unique_name, 'wb').close()
353
 
                return
354
 
 
355
 
    def release(self):
356
 
        if not self.is_locked():
357
 
            raise NotLocked
358
 
        elif not os.path.exists(self.unique_name):
359
 
            raise NotMyLock
360
 
        os.unlink(self.unique_name)
361
 
        os.rmdir(self.lock_file)
362
 
 
363
 
    def is_locked(self):
364
 
        return os.path.exists(self.lock_file)
365
 
 
366
 
    def i_am_locking(self):
367
 
        return (self.is_locked() and
368
 
                os.path.exists(self.unique_name))
369
 
 
370
 
    def break_lock(self):
371
 
        if os.path.exists(self.lock_file):
372
 
            for name in os.listdir(self.lock_file):
373
 
                os.unlink(os.path.join(self.lock_file, name))
374
 
            os.rmdir(self.lock_file)
375
 
 
376
 
 
377
 
class SQLiteFileLock(LockBase):
378
 
    'Demonstration of using same SQL-based locking.'
379
 
 
380
 
    import tempfile
381
 
    _fd, testdb = tempfile.mkstemp()
382
 
    os.close(_fd)
383
 
    os.unlink(testdb)
384
 
    del _fd, tempfile
385
 
 
386
 
    def __init__(self, path, threaded=True):
387
 
        LockBase.__init__(self, path, threaded)
388
 
        self.lock_file = unicode(self.lock_file)
389
 
        self.unique_name = unicode(self.unique_name)
390
 
 
391
 
        import sqlite3
392
 
        self.connection = sqlite3.connect(SQLiteFileLock.testdb)
393
 
 
394
 
        c = self.connection.cursor()
395
 
        try:
396
 
            c.execute('create table locks'
397
 
                      '('
398
 
                      '   lock_file varchar(32),'
399
 
                      '   unique_name varchar(32)'
400
 
                      ')')
401
 
        except sqlite3.OperationalError:
402
 
            pass
403
 
        else:
404
 
            self.connection.commit()
405
 
            import atexit
406
 
            atexit.register(os.unlink, SQLiteFileLock.testdb)
407
 
 
408
 
    def acquire(self, timeout=None):
409
 
        end_time = time.time()
410
 
        if timeout is not None and timeout > 0:
411
 
            end_time += timeout
412
 
 
413
 
        if timeout is None:
414
 
            wait = 0.1
415
 
        elif timeout <= 0:
416
 
            wait = 0
417
 
        else:
418
 
            wait = timeout / 10
419
 
 
420
 
        cursor = self.connection.cursor()
421
 
 
422
 
        while True:
423
 
            if not self.is_locked():
424
 
                # Not locked.  Try to lock it.
425
 
                cursor.execute('insert into locks'
426
 
                               '  (lock_file, unique_name)'
427
 
                               '  values'
428
 
                               '  (?, ?)',
429
 
                               (self.lock_file, self.unique_name))
430
 
                self.connection.commit()
431
 
 
432
 
                # Check to see if we are the only lock holder.
433
 
                cursor.execute('select * from locks'
434
 
                               '  where unique_name = ?',
435
 
                               (self.unique_name,))
436
 
                rows = cursor.fetchall()
437
 
                if len(rows) > 1:
438
 
                    # Nope.  Someone else got there.  Remove our lock.
439
 
                    cursor.execute('delete from locks'
440
 
                                   '  where unique_name = ?',
441
 
                                   (self.unique_name,))
442
 
                    self.connection.commit()
443
 
                else:
444
 
                    # Yup.  We're done, so go home.
445
 
                    return
446
 
            else:
447
 
                # Check to see if we are the only lock holder.
448
 
                cursor.execute('select * from locks'
449
 
                               '  where unique_name = ?',
450
 
                               (self.unique_name,))
451
 
                rows = cursor.fetchall()
452
 
                if len(rows) == 1:
453
 
                    # We're the locker, so go home.
454
 
                    return
455
 
 
456
 
            # Maybe we should wait a bit longer.
457
 
            if timeout is not None and time.time() > end_time:
458
 
                if timeout > 0:
459
 
                    # No more waiting.
460
 
                    raise LockTimeout
461
 
                else:
462
 
                    # Someone else has the lock and we are impatient..
463
 
                    raise AlreadyLocked
464
 
 
465
 
            # Well, okay.  We'll give it a bit longer.
466
 
            time.sleep(wait)
467
 
 
468
 
    def release(self):
469
 
        if not self.is_locked():
470
 
            raise NotLocked
471
 
        if not self.i_am_locking():
472
 
            raise NotMyLock((self._who_is_locking(), self.unique_name))
473
 
        cursor = self.connection.cursor()
474
 
        cursor.execute('delete from locks'
475
 
                       '  where unique_name = ?',
476
 
                       (self.unique_name,))
477
 
        self.connection.commit()
478
 
 
479
 
    def _who_is_locking(self):
480
 
        cursor = self.connection.cursor()
481
 
        cursor.execute('select unique_name from locks'
482
 
                       '  where lock_file = ?',
483
 
                       (self.lock_file,))
484
 
        return cursor.fetchone()[0]
485
 
 
486
 
    def is_locked(self):
487
 
        cursor = self.connection.cursor()
488
 
        cursor.execute('select * from locks'
489
 
                       '  where lock_file = ?',
490
 
                       (self.lock_file,))
491
 
        rows = cursor.fetchall()
492
 
        return not not rows
493
 
 
494
 
    def i_am_locking(self):
495
 
        cursor = self.connection.cursor()
496
 
        cursor.execute('select * from locks'
497
 
                       '  where lock_file = ?'
498
 
                       '    and unique_name = ?',
499
 
                       (self.lock_file, self.unique_name))
500
 
        return not not cursor.fetchall()
501
 
 
502
 
    def break_lock(self):
503
 
        cursor = self.connection.cursor()
504
 
        cursor.execute('delete from locks'
505
 
                       '  where lock_file = ?',
506
 
                       (self.lock_file,))
507
 
        self.connection.commit()
508
 
 
509
 
if hasattr(os, 'link'):
510
 
    FileLock = LinkFileLock
511
 
else:
512
 
    FileLock = MkdirFileLock