BIND 10 trac642, updated. 1f7ecf93d1934e8920d8afa373f68e6b5bae9da8 Catch SIGHUP and a few other fatal signals and shutdown cleanly. Also adds tests for signal handling (which required a bit of refactoring of the boss process).
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Mar 16 21:52:22 UTC 2011
The branch, trac642 has been updated
via 1f7ecf93d1934e8920d8afa373f68e6b5bae9da8 (commit)
from 31b0982e2b3a7c2f4a3cd31712a996aa3519944e (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 1f7ecf93d1934e8920d8afa373f68e6b5bae9da8
Author: Shane Kerr <shane at isc.org>
Date: Wed Mar 16 22:51:13 2011 +0100
Catch SIGHUP and a few other fatal signals and shutdown cleanly.
Also adds tests for signal handling (which required a bit of
refactoring of the boss process).
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 6 ++
src/bin/bind10/bind10.py.in | 46 ++++++++++++++----
src/bin/bind10/tests/bind10_test.py.in | 84 +++++++++++++++++++++++++++++++-
3 files changed, 124 insertions(+), 12 deletions(-)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index f9055a2..19c5142 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+ XXX. [func] shane
+ Catch additional signals and perform clean shutdown. Most important
+ is SIGHUP, so BIND 10 will shut down cleanly if the terminal the
+ program is started in closes.
+ (Trac #642, git ........)
+
206. [func] shane
Add the ability to list the running BIND 10 processes using the
command channel. To try this, use "Boss show_processes".
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
index bd0cd82..3ae0976 100755
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -900,10 +900,10 @@ def unlink_pid_file(pid_file):
if error.errno is not errno.ENOENT:
raise
-
-def main():
+def setup(wakeup_pipe):
global options
global boss_of_bind
+
# Enforce line buffering on stdout, even when not a TTY
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
@@ -943,19 +943,29 @@ def main():
sys.stdout.write("%s\n" % VERSION)
# Create wakeup pipe for signal handlers
- wakeup_pipe = os.pipe()
signal.set_wakeup_fd(wakeup_pipe[1])
- # Set signal handlers for catching child termination, as well
- # as our own demise.
+ # Set signal handlers for catching child termination.
signal.signal(signal.SIGCHLD, reaper)
signal.siginterrupt(signal.SIGCHLD, False)
+
+ # Catch fatal signals so we can shutdown gracefully.
signal.signal(signal.SIGINT, fatal_signal)
signal.signal(signal.SIGTERM, fatal_signal)
-
- # Block SIGPIPE, as we don't want it to end this process
+ signal.signal(signal.SIGUSR1, fatal_signal)
+ signal.signal(signal.SIGUSR2, fatal_signal)
+ if hasattr(signal, 'SIGXCPU'):
+ signal.signal(signal.SIGXCPU, fatal_signal)
+ if hasattr(signal, 'SIGXFSZ'):
+ signal.signal(signal.SIGXFSZ, fatal_signal)
+
+ # Ignore SIGPIPE, as we don't want it to end this process
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+ # If we are ignoring SIGHUP, then let it be, otherwise catch it
+ if signal.getsignal(signal.SIGHUP) is not signal.SIG_IGN:
+ signal.signal(signal.SIGHUP, fatal_signal)
+
# Go bob!
boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
options.config_file, options.nocache, options.verbose,
@@ -964,9 +974,12 @@ def main():
if startup_result:
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
sys.exit(1)
- sys.stdout.write("[bind10] BIND 10 started\n")
dump_pid(options.pid_file)
+def report_boot_time(wakeup_pipe):
+ global options
+ global boss_of_bind
+
# send "bind10.boot_time" to b10-stats
time.sleep(1) # wait a second
if options.verbose:
@@ -978,6 +991,10 @@ def main():
})
boss_of_bind.cc_session.group_sendmsg(cmd, 'Stats')
+def main_loop(wakeup_pipe):
+ global options
+ global boss_of_bind
+
# In our main loop, we check for dead processes or messages
# on the c-channel.
wakeup_fd = wakeup_pipe[0]
@@ -1015,12 +1032,21 @@ def main():
elif fd == wakeup_fd:
os.read(wakeup_fd, 32)
- # shutdown
+def shutdown():
+ global boss_of_bind
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
boss_of_bind.shutdown()
- sys.stdout.write("[bind10] BIND 10 exiting\n");
unlink_pid_file(options.pid_file)
sys.exit(0)
+def main():
+ wakeup_pipe = os.pipe()
+ setup(wakeup_pipe)
+ sys.stdout.write("[bind10] BIND 10 started\n")
+ report_boot_time()
+ main_loop(wakeup_pipe)
+ sys.stdout.write("[bind10] BIND 10 exiting\n");
+ shutdown()
+
if __name__ == "__main__":
main()
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 368fdbd..be3f48b 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -13,7 +13,12 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file
+from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, \
+ setup
+
+# we need to import the bind10 namespace so that we can override
+# some of the behavior
+import bind10
# XXX: environment tests are currently disabled, due to the preprocessor
# setup that we have now complicating the environment
@@ -132,7 +137,9 @@ class TestBoB(unittest.TestCase):
# that the right processes are started depending on the configuration
# options.
class MockBob(BoB):
- def __init__(self):
+ def __init__(self, msgq_socket_file=None, data_path=None,
+ config_filename=None, nocache=False, verbose=False,
+ setuid=None, username=None, cmdctl_port=None):
BoB.__init__(self)
# Set flags as to which of the overridden methods has been run.
@@ -597,5 +604,78 @@ class TestPIDFile(unittest.TestCase):
self.assertRaises(IOError, dump_pid,
'nonexistent_dir' + os.sep + 'bind10.pid')
+class TestSignalHandling(unittest.TestCase):
+ # In these tests, we swap out the BoB in the bind10 class with our own.
+ # This allows us to run our setup code without actually starting
+ # the whole system.
+ def setUp(self):
+ self.save_bob_class = bind10.BoB
+ bind10.BoB = MockBob
+ self.wakeup_pipe = os.pipe()
+
+ def tearDown(self):
+ os.close(self.wakeup_pipe[0])
+ os.close(self.wakeup_pipe[1])
+ bind10.BoB = self.save_bob_class
+
+ def test_sighup(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGHUP)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ def test_sighup_nohup(self):
+ # simulate the behavior of the "nohup" command
+ signal.signal(signal.SIGHUP, signal.SIG_IGN)
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGHUP)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+
+ def test_sigint(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGINT)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ def test_sigterm(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGTERM)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ def test_sigpipe(self):
+ # SIGPIPE gets ignored
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGPIPE)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+
+ def test_sigusr1(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGUSR1)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ def test_sigusr2(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGUSR2)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ @unittest.skipUnless(hasattr(signal, 'SIGXCPU'), "no SIGXCPU on this OS")
+ def test_sigxcpu(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGXCPU)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
+ @unittest.skipUnless(hasattr(signal, 'SIGXFSZ'), "no SIGXFSZ on this OS")
+ def test_sigxfsz(self):
+ bind10.setup(self.wakeup_pipe)
+ self.assertTrue(bind10.boss_of_bind.runnable)
+ os.kill(os.getpid(), signal.SIGXFSZ)
+ self.assertFalse(bind10.boss_of_bind.runnable)
+
if __name__ == '__main__':
unittest.main()
More information about the bind10-changes
mailing list