From b9b8cbe0fcaa314111fb4b3a6883ad5b24ef5981 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 2 Dec 2012 11:55:19 -0800 Subject: [PATCH] Additional comments and a file helper class. Improve the comments in the forking file as well as unify the forking related filenames under a helper class that also provides the converted pid as well as other util methods used in the main forking class. --- anvil/runners/fork.py | 170 ++++++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 64 deletions(-) diff --git a/anvil/runners/fork.py b/anvil/runners/fork.py index 1d5fe7ac..bc6a5162 100644 --- a/anvil/runners/fork.py +++ b/anvil/runners/fork.py @@ -21,13 +21,12 @@ from anvil import log as logging from anvil import runners as base from anvil import shell as sh from anvil import trace as tr +from anvil import utils from anvil.components import (STATUS_STARTED, STATUS_UNKNOWN) LOG = logging.getLogger(__name__) - -# Trace constants PID_FN = "PID_FN" STDOUT_FN = "STDOUT_FN" STDERR_FN = "STDERR_FN" @@ -36,93 +35,136 @@ NAME = "NAME" FORK_TEMPL = "%s.fork" -class ForkRunner(base.Runner): - def stop(self, app_name): - trace_dir = self.runtime.get_option('trace_dir') - if not sh.isdir(trace_dir): - msg = "No trace directory found from which to stop: %s" % (app_name) - raise excp.StopException(msg) - with sh.Rooted(True): - fn_name = FORK_TEMPL % (app_name) - (pid_file, stderr_fn, stdout_fn) = self._form_file_names(fn_name) - pid = self._extract_pid(pid_file) - if not pid: - msg = "Could not extract a valid pid from %s" % (pid_file) - raise excp.StopException(msg) - (killed, attempts) = sh.kill(pid) - # Trash the files if it worked - if killed: - LOG.debug("Killed pid %s after %s attempts." % (pid, attempts)) - LOG.debug("Removing pid file %s" % (pid_file)) - sh.unlink(pid_file) - LOG.debug("Removing stderr file %r" % (stderr_fn)) - sh.unlink(stderr_fn) - LOG.debug("Removing stdout file %r" % (stdout_fn)) - sh.unlink(stdout_fn) - trace_fn = tr.trace_filename(trace_dir, fn_name) - if sh.isfile(trace_fn): - LOG.debug("Removing %r trace file %r" % (app_name, trace_fn)) - sh.unlink(trace_fn) - else: - msg = "Could not stop %r after %s attempts" % (app_name, attempts) - raise excp.StopException(msg) +class ForkFiles(object): + def __init__(self, pid, stdout, stderr, trace=None): + self.pid = pid + self.stdout = stdout + self.stderr = stderr + self.trace = trace - def _extract_pid(self, filename): - if sh.isfile(filename): + def extract_pid(self): + # Load the pid file and take out the pid from it... + # + # Typically said file has a integer pid in it so load said file + # and covert its contents to an int or fail trying... + if self.pid and sh.isfile(self.pid): try: - return int(sh.load_file(filename).strip()) - except ValueError: + return int(sh.load_file(self.pid).strip()) + except (ValueError, TypeError, IOError): return None else: return None + def as_list(self): + possibles = [self.pid, self.stdout, self.stderr, self.trace] + return [i for i in possibles if i is not None] + + def as_dict(self): + return { + PID_FN: self.pid, + STDOUT_FN: self.stdout, + STDERR_FN: self.stderr, + } + + +class ForkRunner(base.Runner): + def stop(self, app_name): + # The location of the pid file should be in the attached + # runtimes trace directory, so see if we can find said file + # and then attempt to kill the pid that exists in that file + # which if succesffully will signal to the rest of this code + # that we can go through and cleanup the other remnants of said + # pid such as the stderr/stdout files that were being written to... + trace_dir = self.runtime.get_option('trace_dir') + if not sh.isdir(trace_dir): + msg = "No trace directory found from which to stop: %r" % (app_name) + raise excp.StopException(msg) + with sh.Rooted(True): + fork_fns = self._form_file_names(app_name) + pid = fork_fns.extract_pid() + if pid is None: + msg = "Could not extract a valid pid from %r" % (fork_fns.pid) + raise excp.StopException(msg) + (killed, attempts) = sh.kill(pid) + # Trash the files if it worked + if killed: + LOG.debug("Killed pid '%s' after %s attempts.", pid, attempts) + for leftover_fn in fork_fns.as_list(): + if sh.exists(leftover_fn): + LOG.debug("Removing forking related file %r", (leftover_fn)) + sh.unlink(leftover_fn) + else: + msg = "Could not stop %r after %s attempts" % (app_name, attempts) + raise excp.StopException(msg) + def status(self, app_name): + # Attempt to find the status of a given app by finding where that apps + # pid file is and loading said pids details (from stderr/stdout) files + # that should exist as well as by using shell utilities to determine + # if said pid is still running... trace_dir = self.runtime.get_option('trace_dir') if not sh.isdir(trace_dir): return (STATUS_UNKNOWN, '') - (pid_file, stderr_fn, stdout_fn) = self._form_file_names(FORK_TEMPL % (app_name)) - pid = self._extract_pid(pid_file) + fork_fns = self._form_file_names(app_name) + pid = fork_fns.extract_pid() stderr = '' try: - stderr = sh.load_file(stderr_fn) - except IOError: + stderr = sh.load_file(fork_fns.stderr) + except (IOError, ValueError, TypeError): pass stdout = '' try: - stdout = sh.load_file(stdout_fn) - except IOError: + stdout = sh.load_file(fork_fns.stdout) + except (IOError, ValueError, TypeError): pass - if pid and sh.is_running(pid): + if pid is not None and sh.is_running(pid): return (STATUS_STARTED, (stdout + stderr).strip()) else: return (STATUS_UNKNOWN, (stdout + stderr).strip()) - def _form_file_names(self, file_name): + def _form_file_names(self, app_name): + # Form all files names which should be connected to the given forked application name + fork_fn = FORK_TEMPL % (app_name) trace_dir = self.runtime.get_option('trace_dir') - return (sh.joinpths(trace_dir, file_name + ".pid"), - sh.joinpths(trace_dir, file_name + ".stderr"), - sh.joinpths(trace_dir, file_name + ".stdout")) - - def _do_trace(self, fn, kvs): - trace_dir = self.runtime.get_option('trace_dir') - run_trace = tr.TraceWriter(tr.trace_filename(trace_dir, fn)) - for (k, v) in kvs.items(): - run_trace.trace(k, v) - return run_trace.filename() + trace_fn = None + if trace_dir: + trace_fn = tr.trace_filename(trace_dir, fork_fn) + base_fork_fn = sh.joinpths(trace_dir, fork_fn) + return ForkFiles(pid=base_fork_fn + ".pid", + stdout=base_fork_fn + ".stdout", + stderr=base_fork_fn + ".stderr", + trace=trace_fn) def _begin_start(self, app_name, app_pth, app_wkdir, args): - fn_name = FORK_TEMPL % (app_name) - (pid_fn, stderr_fn, stdout_fn) = self._form_file_names(fn_name) - trace_info = dict() - trace_info[PID_FN] = pid_fn - trace_info[STDERR_FN] = stderr_fn - trace_info[STDOUT_FN] = stdout_fn - trace_info[ARGS] = json.dumps(args) - trace_fn = self._do_trace(fn_name, trace_info) + fork_fns = self._form_file_names(app_name) + trace_fn = fork_fns.trace + # Ensure all arguments for this app in string format + args = [str(i) for i in args if i is not None] + if trace_fn: + # Not needed, but useful to know where the files are located at + # + # TODO(harlowja): use this info instead of forming the filenames + # repeatly + trace_info = {} + trace_info.update(fork_fns.as_dict()) + # Useful to know what args were sent along + trace_info[ARGS] = json.dumps(args) + run_trace = tr.TraceWriter(trace_fn) + for (k, v) in trace_info.items(): + if v is not None: + run_trace.trace(k, v) LOG.debug("Forking %r by running command %r with args (%s)" % (app_name, app_pth, " ".join(args))) with sh.Rooted(True): - sh.fork(app_pth, app_wkdir, pid_fn, stdout_fn, stderr_fn, *args) + sh.fork(app_pth, app_wkdir, fork_fns.pid, fork_fns.stdout, fork_fns.stderr, *args) return trace_fn + def _post_start(self, app_name): + fork_fns = self._form_file_names(app_name) + utils.log_iterable(fork_fns.as_list(), + header="Forked %s with details in the following files" % (app_name), + logger=LOG) + def start(self, app_name, app_pth, app_dir, opts): - return self._begin_start(app_name, app_pth, app_dir, opts) + trace_fn = self._begin_start(app_name, app_pth, app_dir, opts) + self._post_start(app_name) + return trace_fn