~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to notification/lockfile.py

  • Committer: franku
  • Date: 2016-12-13 18:28:51 UTC
  • mto: This revision was merged to the branch mainline in revision 443.
  • Revision ID: somal@arcor.de-20161213182851-bo5ebf8pdvw5beua
run the script

Show diffs side-by-side

added added

removed removed

Lines of Context:
72
72
           'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
73
73
           'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock']
74
74
 
 
75
 
75
76
class Error(Exception):
76
 
    """
77
 
    Base class for other exceptions.
 
77
    """Base class for other exceptions.
78
78
 
79
79
    >>> try:
80
80
    ...   raise Error
81
81
    ... except Exception:
82
82
    ...   pass
 
83
 
83
84
    """
84
85
    pass
85
86
 
 
87
 
86
88
class LockError(Error):
87
 
    """
88
 
    Base class for error arising from attempts to acquire the lock.
 
89
    """Base class for error arising from attempts to acquire the lock.
89
90
 
90
91
    >>> try:
91
92
    ...   raise LockError
92
93
    ... except Error:
93
94
    ...   pass
 
95
 
94
96
    """
95
97
    pass
96
98
 
 
99
 
97
100
class LockTimeout(LockError):
98
101
    """Raised when lock creation fails within a user-defined period of time.
99
102
 
104
107
    """
105
108
    pass
106
109
 
 
110
 
107
111
class AlreadyLocked(LockError):
108
112
    """Some other thread/process is locking the file.
109
113
 
111
115
    ...   raise AlreadyLocked
112
116
    ... except LockError:
113
117
    ...   pass
 
118
 
114
119
    """
115
120
    pass
116
121
 
 
122
 
117
123
class LockFailed(LockError):
118
124
    """Lock file creation failed for some other reason.
119
125
 
121
127
    ...   raise LockFailed
122
128
    ... except LockError:
123
129
    ...   pass
 
130
 
124
131
    """
125
132
    pass
126
133
 
 
134
 
127
135
class UnlockError(Error):
128
 
    """
129
 
    Base class for errors arising from attempts to release the lock.
 
136
    """Base class for errors arising from attempts to release the lock.
130
137
 
131
138
    >>> try:
132
139
    ...   raise UnlockError
133
140
    ... except Error:
134
141
    ...   pass
 
142
 
135
143
    """
136
144
    pass
137
145
 
 
146
 
138
147
class NotLocked(UnlockError):
139
148
    """Raised when an attempt is made to unlock an unlocked file.
140
149
 
142
151
    ...   raise NotLocked
143
152
    ... except UnlockError:
144
153
    ...   pass
 
154
 
145
155
    """
146
156
    pass
147
157
 
 
158
 
148
159
class NotMyLock(UnlockError):
149
160
    """Raised when an attempt is made to unlock a file someone else locked.
150
161
 
152
163
    ...   raise NotMyLock
153
164
    ... except UnlockError:
154
165
    ...   pass
 
166
 
155
167
    """
156
168
    pass
157
169
 
 
170
 
158
171
class LockBase:
159
172
    """Base class for platform-specific lock classes."""
 
173
 
160
174
    def __init__(self, path, threaded=True):
161
175
        """
162
176
        >>> lock = LockBase('somefile')
163
177
        >>> lock = LockBase('somefile', threaded=False)
164
178
        """
165
179
        self.path = path
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()
169
183
        if threaded:
170
 
            tname = "%s-" % threading.current_thread().get_name()
 
184
            tname = '%s-' % threading.current_thread().get_name()
171
185
        else:
172
 
            tname = ""
 
186
            tname = ''
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,
176
190
                                                     tname,
177
191
                                                     self.pid))
178
192
 
179
193
    def acquire(self, timeout=None):
180
 
        """
181
 
        Acquire the lock.
 
194
        """Acquire the lock.
182
195
 
183
196
        * If timeout is omitted (or None), wait forever trying to lock the
184
197
          file.
189
202
 
190
203
        * If timeout <= 0, raise AlreadyLocked immediately if the file is
191
204
          already locked.
 
205
 
192
206
        """
193
 
        raise NotImplemented("implement in subclass")
 
207
        raise NotImplemented('implement in subclass')
194
208
 
195
209
    def release(self):
196
 
        """
197
 
        Release the lock.
 
210
        """Release the lock.
198
211
 
199
212
        If the file is not locked, raise NotLocked.
 
213
 
200
214
        """
201
 
        raise NotImplemented("implement in subclass")
 
215
        raise NotImplemented('implement in subclass')
202
216
 
203
217
    def is_locked(self):
204
 
        """
205
 
        Tell whether or not the file is locked.
206
 
        """
207
 
        raise NotImplemented("implement in subclass")
 
218
        """Tell whether or not the file is locked."""
 
219
        raise NotImplemented('implement in subclass')
208
220
 
209
221
    def i_am_locking(self):
210
 
        """
211
 
        Return True if this object is locking the file.
212
 
        """
213
 
        raise NotImplemented("implement in subclass")
 
222
        """Return True if this object is locking the file."""
 
223
        raise NotImplemented('implement in subclass')
214
224
 
215
225
    def break_lock(self):
216
 
        """
217
 
        Remove a lock.  Useful if a locking thread failed to unlock.
218
 
        """
219
 
        raise NotImplemented("implement in subclass")
 
226
        """Remove a lock.
 
227
 
 
228
        Useful if a locking thread failed to unlock.
 
229
 
 
230
        """
 
231
        raise NotImplemented('implement in subclass')
220
232
 
221
233
    def __enter__(self):
222
 
        """
223
 
        Context manager support.
224
 
        """
 
234
        """Context manager support."""
225
235
        self.acquire()
226
236
        return self
227
237
 
228
238
    def __exit__(self, *_exc):
229
 
        """
230
 
        Context manager support.
231
 
        """
 
239
        """Context manager support."""
232
240
        self.release()
233
241
 
 
242
 
234
243
class LinkFileLock(LockBase):
235
244
    """Lock access to a file using atomic property of link(2)."""
236
245
 
237
246
    def acquire(self, timeout=None):
238
247
        try:
239
 
            open(self.unique_name, "wb").close()
 
248
            open(self.unique_name, 'wb').close()
240
249
        except IOError:
241
250
            raise LockFailed
242
251
 
263
272
                            raise LockTimeout
264
273
                        else:
265
274
                            raise AlreadyLocked
266
 
                    time.sleep(timeout is not None and timeout/10 or 0.1)
 
275
                    time.sleep(timeout is not None and timeout / 10 or 0.1)
267
276
            else:
268
277
                # Link creation succeeded.  We're good to go.
269
278
                return
288
297
        if os.path.exists(self.lock_file):
289
298
            os.unlink(self.lock_file)
290
299
 
 
300
 
291
301
class MkdirFileLock(LockBase):
292
302
    """Lock file by creating a directory."""
 
303
 
293
304
    def __init__(self, path, threaded=True):
294
305
        """
295
306
        >>> lock = MkdirFileLock('somefile')
297
308
        """
298
309
        LockBase.__init__(self, path, threaded)
299
310
        if threaded:
300
 
            tname = "%x-" % thread.get_ident()
 
311
            tname = '%x-' % thread.get_ident()
301
312
        else:
302
 
            tname = ""
 
313
            tname = ''
303
314
        # Lock file itself is a directory.  Place the unique file name into
304
315
        # it.
305
 
        self.unique_name  = os.path.join(self.lock_file,
306
 
                                         "%s.%s%s" % (self.hostname,
307
 
                                                      tname,
308
 
                                                      self.pid))
 
316
        self.unique_name = os.path.join(self.lock_file,
 
317
                                        '%s.%s%s' % (self.hostname,
 
318
                                                     tname,
 
319
                                                     self.pid))
309
320
 
310
321
    def acquire(self, timeout=None):
311
322
        end_time = time.time()
338
349
                    # Couldn't create the lock for some other reason
339
350
                    raise LockFailed
340
351
            else:
341
 
                open(self.unique_name, "wb").close()
 
352
                open(self.unique_name, 'wb').close()
342
353
                return
343
354
 
344
355
    def release(self):
362
373
                os.unlink(os.path.join(self.lock_file, name))
363
374
            os.rmdir(self.lock_file)
364
375
 
 
376
 
365
377
class SQLiteFileLock(LockBase):
366
 
    "Demonstration of using same SQL-based locking."
 
378
    'Demonstration of using same SQL-based locking.'
367
379
 
368
380
    import tempfile
369
381
    _fd, testdb = tempfile.mkstemp()
378
390
 
379
391
        import sqlite3
380
392
        self.connection = sqlite3.connect(SQLiteFileLock.testdb)
381
 
        
 
393
 
382
394
        c = self.connection.cursor()
383
395
        try:
384
 
            c.execute("create table locks"
385
 
                      "("
386
 
                      "   lock_file varchar(32),"
387
 
                      "   unique_name varchar(32)"
388
 
                      ")")
 
396
            c.execute('create table locks'
 
397
                      '('
 
398
                      '   lock_file varchar(32),'
 
399
                      '   unique_name varchar(32)'
 
400
                      ')')
389
401
        except sqlite3.OperationalError:
390
402
            pass
391
403
        else:
410
422
        while True:
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)"
415
 
                               "  values"
416
 
                               "  (?, ?)",
 
425
                cursor.execute('insert into locks'
 
426
                               '  (lock_file, unique_name)'
 
427
                               '  values'
 
428
                               '  (?, ?)',
417
429
                               (self.lock_file, self.unique_name))
418
430
                self.connection.commit()
419
431
 
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()
431
443
                else:
433
445
                    return
434
446
            else:
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.
442
454
                    return
443
 
                    
 
455
 
444
456
            # Maybe we should wait a bit longer.
445
457
            if timeout is not None and time.time() > end_time:
446
458
                if timeout > 0:
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()
466
478
 
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]
473
 
        
 
485
 
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
481
493
 
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()
489
501
 
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()
496
508
 
497
 
if hasattr(os, "link"):
 
509
if hasattr(os, 'link'):
498
510
    FileLock = LinkFileLock
499
511
else:
500
512
    FileLock = MkdirFileLock