Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1282)

Side by Side Diff: infra/libs/service_utils/daemon.py

Issue 1096683003: Add flock and timeout to infra/libs. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Improve tests and coverage. Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | infra/libs/service_utils/test/daemon_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « no previous file | infra/libs/service_utils/test/daemon_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698