Chromium Code Reviews| Index: infra/libs/service_utils/daemon.py |
| diff --git a/infra/libs/service_utils/daemon.py b/infra/libs/service_utils/daemon.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..84be36fd75400e19753b875de74694dc12459984 |
| --- /dev/null |
| +++ b/infra/libs/service_utils/daemon.py |
| @@ -0,0 +1,100 @@ |
| +# Copyright 2015 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Locking, timeout, and other process management functions.""" |
| + |
| +import contextlib |
| +import fcntl |
| +import os |
| +import sys |
| +import tempfile |
| + |
| + |
| +@contextlib.contextmanager |
| +def _auto_closing_fd(*args, **kwargs): |
| + """Opens a file, yields its fd, and closes it when done.""" |
| + |
| + fd = os.open(*args, **kwargs) |
| + yield fd |
| + os.close(fd) |
| + |
| + |
| +@contextlib.contextmanager |
| +def flock(lockfile, lockdir=None): |
| + """Keeps a critical section from executing concurrently using a file lock. |
|
tandrii(chromium)
2015/04/23 13:02:47
You should document that this lock only gives excl
|
| + |
| + Implementation based on http://goo.gl/dNf7fv (see John Mudd's comment) and |
| + http://stackoverflow.com/a/18745264/3984761. This implementation creates the |
| + lockfile if it doesn't exist and removes it when the critical section exits. |
| + It yields True if the lock was acquired, and False if it wasn't. You must |
| + check if the yielded value is True if you want to prevent concurrent critical |
| + sections. |
| + |
| + Example usage: |
| + |
| + with daemon.flock('toaster') as acquired: |
|
tandrii(chromium)
2015/04/23 13:02:47
IMO, it's more Pythonic to raise exception instead
agable
2015/04/27 18:48:33
So then the construct would be
try:
with daemon
tandrii(chromium)
2015/04/27 21:17:09
Yep, and you don't even need "as acq" part :)
ghost stip (do not use)
2015/04/27 22:03:47
Done.
|
| + if acquired: |
| + put_bread_in_toaster() |
| + else: |
| + print 'toaster is occupied!' |
| + """ |
| + |
| + if sys.platform.startswith('win'): # pragma: no cover |
| + raise NotImplementedError |
| + |
| + lockdir = lockdir or tempfile.gettempdir() |
| + full_lockfile = os.path.join(lockdir, lockfile) |
| + lock_acquired = False |
| + |
| + with _auto_closing_fd( |
| + full_lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY) as fd: |
| + try: |
| + # Request exclusive (EX) non-blocking (NB) advisory lock. |
| + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) |
| + |
| + lock_acquired = True |
| + except IOError: |
| + # Could not obtain lock. |
| + lock_acquired = False |
| + |
| + if lock_acquired: |
| + try: |
| + held_inode = os.fstat(fd).st_ino |
| + file_inode = os.stat(full_lockfile).st_ino |
| + |
| + if held_inode != file_inode: |
| + # The file was deleted under us, another process has created it again |
| + # and may get a lock on it. That process doesn't know about the lock |
| + # we have on the (now deleted) file, so we need to bail. |
| + lock_acquired = False |
| + except OSError: |
| + # File has been deleted under us. We have to exit because another process |
| + # might try to create it and obtain a lock, not knowing that we had a |
| + # lock on the (now deleted) file. |
| + lock_acquired = False |
| + |
| + yield lock_acquired |
| + |
| + try: |
| + # The order of these two operations is very important. We need to delete |
| + # the file before we release the lock. If we release the lock before we |
| + # delete the file, we run the risk of another process obtaining a lock on |
| + # the file we're about to delete. If the delete happens while the other |
| + # critical section is running, a third process could create the file, get |
| + # a lock on it, and run a second critical section simultaneously. Deleting |
| + # before unlocking prevents this scenario. |
| + os.unlink(full_lockfile) |
| + fcntl.lockf(fd, fcntl.LOCK_UN) |
| + except OSError: |
| + # If the file was deleted for some other reason, don't sweat it. |
| + pass |
| + |
| + |
| +def add_timeout(cmd, timeout_secs): |
| + """Adds a timeout to a command.""" |
|
agable
2015/04/27 18:48:33
Note that it does so using the linux /bin/timeout
ghost stip (do not use)
2015/04/27 22:03:47
Done.
|
| + |
| + if sys.platform.startswith('win') or sys.platform.startswith('darwin'): |
| + raise NotImplementedError # pragma: no cover |
| + |
| + return ['timeout', str(timeout_secs)] + cmd |