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.
This commit is contained in:
Joshua Harlow
2012-12-02 11:55:19 -08:00
parent c56a6a8903
commit b9b8cbe0fc

View File

@@ -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