zmirror: Allow duplicate zmirror processes to die gracefully.

Fixes #602.

I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler.  This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)

(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
This commit is contained in:
Jacob Hurwitz 2013-01-03 13:21:19 -05:00 committed by Tim Abbott
parent bd3bd8ca44
commit 9ebfa84385
2 changed files with 33 additions and 1 deletions

View file

@ -27,9 +27,16 @@ import time
import optparse import optparse
import os import os
import traceback import traceback
import signal
from zephyr_mirror_backend import parse_args from zephyr_mirror_backend import parse_args
def die(signal, frame):
# We actually want to exit, so run os._exit (so as not to be caught and restarted)
os._exit(1)
signal.signal(signal.SIGINT, die)
(options, args) = parse_args() (options, args) = parse_args()
args = [os.path.join(options.root_path, "user_root", "zephyr_mirror_backend.py")] args = [os.path.join(options.root_path, "user_root", "zephyr_mirror_backend.py")]

View file

@ -39,6 +39,10 @@ import tempfile
DEFAULT_SITE = "https://humbughq.com" DEFAULT_SITE = "https://humbughq.com"
class States:
Startup, HumbugToZephyr, ZephyrToHumbug, ChildSending = range(4)
CURRENT_STATE = States.Startup
def to_humbug_username(zephyr_username): def to_humbug_username(zephyr_username):
if "@" in zephyr_username: if "@" in zephyr_username:
(user, realm) = zephyr_username.split("@") (user, realm) = zephyr_username.split("@")
@ -334,6 +338,8 @@ def process_notice(notice, log):
log.flush() log.flush()
if os.fork() == 0: if os.fork() == 0:
global CURRENT_STATE
CURRENT_STATE = States.ChildSending
# Actually send the message in a child process, to avoid blocking. # Actually send the message in a child process, to avoid blocking.
try: try:
res = send_humbug(zeph) res = send_humbug(zeph)
@ -774,6 +780,21 @@ def parse_args():
default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key")) default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key"))
return parser.parse_args() return parser.parse_args()
def die_gracefully(signal, frame):
if CURRENT_STATE == States.HumbugToZephyr or CURRENT_STATE == States.ChildSending:
# this is a child process, so we want os._exit (no clean-up necessary)
os._exit(1)
if CURRENT_STATE == States.ZephyrToHumbug:
try:
# zephyr=>humbug processes may have added subs, so run cancelSubs
zephyr._z.cancelSubs()
except IOError:
# We don't care whether we failed to cancel subs properly, but we should log it
logging.exception("")
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
# Set the SIGCHLD handler back to SIG_DFL to prevent these errors # Set the SIGCHLD handler back to SIG_DFL to prevent these errors
# when importing the "requests" module after being restarted using # when importing the "requests" module after being restarted using
@ -783,6 +804,8 @@ if __name__ == "__main__":
# IOError: [Errno 10] No child processes # IOError: [Errno 10] No child processes
signal.signal(signal.SIGCHLD, signal.SIG_DFL) signal.signal(signal.SIGCHLD, signal.SIG_DFL)
signal.signal(signal.SIGINT, die_gracefully)
(options, args) = parse_args() (options, args) = parse_args()
# The 'api' directory needs to go first, so that 'import humbug' won't pick # The 'api' directory needs to go first, so that 'import humbug' won't pick
@ -842,7 +865,7 @@ or specify the --api-key-file option.""" % (options.api_key_file,)))
# Another copy of zephyr_mirror.py! Kill it. # Another copy of zephyr_mirror.py! Kill it.
print "Killing duplicate zephyr_mirror process %s" % (pid,) print "Killing duplicate zephyr_mirror process %s" % (pid,)
try: try:
os.kill(pid, signal.SIGKILL) os.kill(pid, signal.SIGINT)
except OSError: except OSError:
# We don't care if the target process no longer exists, so just print the error # We don't care if the target process no longer exists, so just print the error
traceback.print_exc() traceback.print_exc()
@ -856,6 +879,7 @@ or specify the --api-key-file option.""" % (options.api_key_file,)))
if options.forward_from_humbug: if options.forward_from_humbug:
child_pid = os.fork() child_pid = os.fork()
if child_pid == 0: if child_pid == 0:
CURRENT_STATE = States.HumbugToZephyr
# Run the humbug => zephyr mirror in the child # Run the humbug => zephyr mirror in the child
logger = configure_logger("humbug=>zephyr") logger = configure_logger("humbug=>zephyr")
zsig_fullname = fetch_fullname(options.user) zsig_fullname = fetch_fullname(options.user)
@ -863,6 +887,7 @@ or specify the --api-key-file option.""" % (options.api_key_file,)))
sys.exit(0) sys.exit(0)
else: else:
child_pid = None child_pid = None
CURRENT_STATE = States.ZephyrToHumbug
import zephyr import zephyr
while True: while True: