| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Locking, timeout, and other process management functions.""" |
| 6 |
| 7 import contextlib |
| 8 import fcntl |
| 9 import os |
| 10 import sys |
| 11 import tempfile |
| 12 |
| 13 |
| 14 @contextlib.contextmanager |
| 15 def _auto_closing_fd(*args, **kwargs): |
| 16 """Opens a file, yields its fd, and closes it when done.""" |
| 17 |
| 18 fd = os.open(*args, **kwargs) |
| 19 yield fd |
| 20 os.close(fd) |
| 21 |
| 22 |
| 23 class LockAlreadyLocked(RuntimeError): |
| 24 """Exception used when a lock couldn't be acquired.""" |
| 25 pass |
| 26 |
| 27 |
| 28 @contextlib.contextmanager |
| 29 def flock(lockfile, lockdir=None): |
| 30 """Keeps a critical section from executing concurrently using a file lock. |
| 31 |
| 32 This only protects critical sections across processes, not threads. For |
| 33 multithreaded programs. use threading.Lock or threading.RLock. |
| 34 |
| 35 Implementation based on http://goo.gl/dNf7fv (see John Mudd's comment) and |
| 36 http://stackoverflow.com/a/18745264/3984761. This implementation creates the |
| 37 lockfile if it doesn't exist and removes it when the critical section exits. |
| 38 It raises LockAlreadyLocked if it cannot acquire a lock. |
| 39 |
| 40 Note 1: this method only works for lockfiles on local filesystems with |
| 41 appropriate locking semantics (extfs, HFS+). It is unwise to use this on |
| 42 NFS-mounted filesystems. |
| 43 |
| 44 Note 2: be careful when forking processes within the lock, forked processes |
| 45 inherit open file descriptors. |
| 46 |
| 47 Example usage: |
| 48 |
| 49 try: |
| 50 with daemon.flock('toaster'): |
| 51 put_bread_in_toaster() |
| 52 except daemon.LockAlreadyLocked: |
| 53 print 'toaster is occupied!' |
| 54 """ |
| 55 |
| 56 if sys.platform.startswith('win'): # pragma: no cover |
| 57 raise NotImplementedError |
| 58 |
| 59 lockdir = lockdir or tempfile.gettempdir() |
| 60 full_lockfile = os.path.join(lockdir, lockfile) |
| 61 |
| 62 with _auto_closing_fd( |
| 63 full_lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY) as fd: |
| 64 try: |
| 65 # Request exclusive (EX) non-blocking (NB) advisory lock. |
| 66 fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) |
| 67 |
| 68 except IOError: |
| 69 # Could not obtain lock. |
| 70 raise LockAlreadyLocked() |
| 71 |
| 72 try: |
| 73 held_inode = os.fstat(fd).st_ino |
| 74 file_inode = os.stat(full_lockfile).st_ino |
| 75 |
| 76 if held_inode != file_inode: |
| 77 # The file was deleted under us, another process has created it again |
| 78 # and may get a lock on it. That process doesn't know about the lock |
| 79 # we have on the (now deleted) file, so we need to bail. |
| 80 raise LockAlreadyLocked() |
| 81 except OSError: |
| 82 # File has been deleted under us. We have to exit because another process |
| 83 # might try to create it and obtain a lock, not knowing that we had a |
| 84 # lock on the (now deleted) file. |
| 85 raise LockAlreadyLocked() |
| 86 |
| 87 yield |
| 88 |
| 89 try: |
| 90 # The order of these two operations is very important. We need to delete |
| 91 # the file before we release the lock. If we release the lock before we |
| 92 # delete the file, we run the risk of another process obtaining a lock on |
| 93 # the file we're about to delete. If the delete happens while the other |
| 94 # critical section is running, a third process could create the file, get |
| 95 # a lock on it, and run a second critical section simultaneously. Deleting |
| 96 # before unlocking prevents this scenario. |
| 97 os.unlink(full_lockfile) |
| 98 fcntl.lockf(fd, fcntl.LOCK_UN) |
| 99 except OSError: |
| 100 # If the file was deleted for some other reason, don't sweat it. |
| 101 pass |
| 102 |
| 103 |
| 104 def add_timeout(cmd, timeout_secs): |
| 105 """Adds a timeout to a command using linux's (gnu) /bin/timeout.""" |
| 106 |
| 107 if sys.platform.startswith('win') or sys.platform.startswith('darwin'): |
| 108 raise NotImplementedError # pragma: no cover |
| 109 |
| 110 return ['timeout', str(timeout_secs)] + cmd |
| OLD | NEW |