Skip to content

Commit

Permalink
improve handling of exceptions in plugins
Browse files Browse the repository at this point in the history
Exceptions generated when invoking a plugin's collect() method were
being lost when concurrent.futures.Future.result() was invoked, since
the caught exception would have a traceback relative to the parent
thread, not the child.  So we store the original traceback on the
exception within the child thread before re-raising, and then extract
and display that traceback when the exception is finally caught by the
parent.

We also now abort when the exception is found.
  • Loading branch information
Adam Spiers committed Jun 17, 2014
1 parent 30faafb commit 9366bfb
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 5 deletions.
13 changes: 13 additions & 0 deletions rapport/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ def __str__(self):
"""
return rapport.util.camelcase_to_underscores(self.__class__.__name__).rsplit("_plugin")[0]

def try_collect(self, timeframe):
"""
Run the plugin's collect() method, and if an exception was caught,
store the traceback before re-raising, in order that it doesn't
get lost when concurrent.futures.Future.result() is invoked.
"""
try:
self.collect(timeframe)
except Exception as e:
exc_type, exc_val, exc_tb = sys.exc_info()
e.original_traceback = exc_tb
raise

def collect(self, timeframe):
raise NotImplementedError()

Expand Down
15 changes: 10 additions & 5 deletions rapport/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,25 @@ def create_report(plugins, timeframe):
# Execute all plugins in parallel and join on results:
results = {}
with futures.ThreadPoolExecutor(max_workers=4) as executor:
plugin_futures = dict((executor.submit(p.collect, timeframe), p) for p in plugins)
plugin_futures = dict((executor.submit(p.try_collect, timeframe), p) for p in plugins)
for future in futures.as_completed(plugin_futures):
plugin = plugin_futures[future]
try:
res = future.result()
if rapport.config.get_int("rapport", "verbosity") >= 2:
print("Result for {0}: {1}".format(plugin.alias, future.result()))
print("Result for {0}: {1}".format(plugin.alias, res))
tmpl = rapport.template.get_template(plugin, "text")
if tmpl:
results[plugin] = tmpl.render(future.result())
results[plugin] = tmpl.render(res)
except jinja2.TemplateSyntaxError as e:
print >>sys.stderr, "Syntax error in plugin {0} at {1} line {2}: {3}".format(plugin, e.name, e.lineno, e.message)
except Exception as e:
print("Failed plugin {0}:{1}: {2}!".format(plugin, plugin.alias, e), file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
exc_type, exc_val, exc_tb = sys.exc_info()
traceback.print_tb(e.original_traceback, file=sys.stderr)
print("Failed plugin {0}:{1}: {2}: {3}" \
.format(plugin, plugin.alias, e.__class__.__name__, e),
file=sys.stderr)
sys.exit(1)

results_dict = {"login": rapport.config.get("user", "login"),
"date": report_date_string,
Expand Down

0 comments on commit 9366bfb

Please sign in to comment.