Merge lp:~notmyname/swift/daemon_lock into lp:~hudson-openstack/swift/trunk

Proposed by John Dickinson
Status: Work in progress
Proposed branch: lp:~notmyname/swift/daemon_lock
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 141 lines (+51/-9)
4 files modified
bin/swift-log-stats-collector (+4/-2)
etc/log-processing.conf-sample (+1/-0)
swift/common/daemon.py (+30/-7)
test/unit/common/test_daemon.py (+16/-0)
To merge this branch: bzr merge lp:~notmyname/swift/daemon_lock
Reviewer Review Type Date Requested Status
gholt (community) Disapprove
Review via email: mp+43589@code.launchpad.net

Description of the change

Added optional flock to daemon to prevent processes overrunning themselves.

Added this option to swift-log-stats-collector

To post a comment you must log in.
Revision history for this message
gholt (gholt) wrote :

I'm a bit confused on this. Isn't this what swift-init tries to accomplish already with its pid files?

Revision history for this message
gholt (gholt) wrote :

This has been sitting here a while, so I figured I'd update folks that don't sit in our office:

My general complaint on this is that previously the only things that used run_daemon were actually daemons and launched via swift-init, which does pid file style locking including stale pid file checking, 'reload' support, etc.

This change does something quite similar, but not quite, for a thing that isn't a daemon anyway, with no record of the previous pid (for sending it signals or whatever).

review: Disapprove

Unmerged revisions

148. By John Dickinson

made locking in daemon better (including tests)

147. By John Dickinson

added lock_file argument to swift-log-stats-collector

146. By John Dickinson

added locking (flock) to daemon.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/swift-log-stats-collector'
2--- bin/swift-log-stats-collector 2010-11-11 22:41:07 +0000
3+++ bin/swift-log-stats-collector 2010-12-14 03:55:09 +0000
4@@ -15,12 +15,14 @@
5 # limitations under the License.
6
7 from swift.stats.log_processor import LogProcessorDaemon
8-from swift.common.utils import parse_options
9+from swift.common.utils import parse_options, readconf
10 from swift.common.daemon import run_daemon
11
12 if __name__ == '__main__':
13 conf_file, options = parse_options()
14 # currently the LogProcessorDaemon only supports run_once
15 options['once'] = True
16+ conf = readconf(conf_file, 'log-processor')
17+ lock_file = conf.get('lock_file', '/var/run/swift/stats.lock')
18 run_daemon(LogProcessorDaemon, conf_file, section_name=None,
19- log_name='log-stats-collector', **options)
20+ log_name='log-stats-collector', lock_file=lock_file, **options)
21
22=== modified file 'etc/log-processing.conf-sample'
23--- etc/log-processing.conf-sample 2010-10-04 21:12:43 +0000
24+++ etc/log-processing.conf-sample 2010-12-14 03:55:09 +0000
25@@ -9,6 +9,7 @@
26 # lookback_hours = 120
27 # lookback_window = 120
28 # user = swift
29+# lock_file = /var/run/swift/stats.lock
30
31 [log-processor-access]
32 # log_dir = /var/log/swift/
33
34=== modified file 'swift/common/daemon.py'
35--- swift/common/daemon.py 2010-11-19 18:15:41 +0000
36+++ swift/common/daemon.py 2010-12-14 03:55:09 +0000
37@@ -17,9 +17,29 @@
38 import sys
39 import signal
40 from re import sub
41+import contextlib
42+import fcntl
43 from swift.common import utils
44
45
46+@contextlib.contextmanager
47+def flock(path, logger):
48+ if path:
49+ fd = os.open(path, os.O_CREAT | os.O_RDWR)
50+ try:
51+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
52+ except IOError:
53+ logger.error('Already running... cooling the jets now.')
54+ sys.exit(2)
55+ try:
56+ yield fd
57+ finally:
58+ os.close(fd)
59+ os.unlink(path)
60+ else:
61+ yield None
62+
63+
64 class Daemon(object):
65 """Daemon base class"""
66
67@@ -35,7 +55,7 @@
68 """Override this to run forever"""
69 raise NotImplementedError('run_forever not implemented')
70
71- def run(self, once=False, **kwargs):
72+ def run(self, once=False, lock_file=None, **kwargs):
73 """Run the daemon"""
74 utils.validate_configuration()
75 utils.capture_stdio(self.logger, **kwargs)
76@@ -48,14 +68,16 @@
77
78 signal.signal(signal.SIGTERM, kill_children)
79
80- if once:
81- self.run_once()
82- else:
83- self.run_forever()
84+ with flock(lock_file, self.logger):
85+ # if lock_file evaluates to false, nothing will be locked
86+ if once:
87+ self.run_once()
88+ else:
89+ self.run_forever()
90
91
92 def run_daemon(klass, conf_file, section_name='',
93- once=False, **kwargs):
94+ once=False, lock_file=None, **kwargs):
95 """
96 Loads settings from conf, then instantiates daemon "klass" and runs the
97 daemon with the specified once kwarg. The section_name will be derived
98@@ -66,6 +88,7 @@
99 :param conf_file: Path to configuration file
100 :param section_name: Section name from conf file to load config from
101 :param once: Passed to daemon run method
102+ :param lock_file: path to file to use as a lock file
103 """
104 # very often the config section_name is based on the class name
105 # the None singleton will be passed through to readconf as is
106@@ -85,7 +108,7 @@
107 logger = utils.get_logger(conf, conf.get('log_name', section_name),
108 log_to_console=kwargs.pop('verbose', False))
109 try:
110- klass(conf).run(once=once, **kwargs)
111+ klass(conf).run(once=once, lock_file=lock_file, **kwargs)
112 except KeyboardInterrupt:
113 logger.info('User quit')
114 logger.info('Exited')
115
116=== modified file 'test/unit/common/test_daemon.py'
117--- test/unit/common/test_daemon.py 2010-11-19 18:15:41 +0000
118+++ test/unit/common/test_daemon.py 2010-12-14 03:55:09 +0000
119@@ -89,6 +89,22 @@
120 daemon.run_daemon(MyDaemon, conf_file, once=True)
121 self.assertEquals(MyDaemon.once_called, True)
122
123+ # test lock file (to make sure that code path doesn't break)
124+ old_flock = daemon.fcntl.flock
125+
126+ def new_flock_no_error(*a):
127+ pass
128+
129+ def new_flock_error(*a):
130+ raise IOError
131+ daemon.fcntl.flock = new_flock_no_error
132+ daemon.run_daemon(MyDaemon, conf_file,
133+ lock_file='/tmp/foo_lock_test')
134+ daemon.fcntl.flock = new_flock_error
135+ self.assertRaises(SystemExit, daemon.run_daemon, MyDaemon,
136+ conf_file, lock_file='/tmp/foo_lock_test')
137+ daemon.fcntl.flock = old_flock
138+
139 # test raise in daemon code
140 MyDaemon.run_once = MyDaemon.run_raise
141 self.assertRaises(OSError, daemon.run_daemon, MyDaemon,