Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose tracebacks from actor exceptions #802

Merged
merged 3 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions leapp/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ class CommandDefinitionError(LeappError):

class UnknownCommandError(LeappError):
def __init__(self, command):
super().__init__('Unknown command: {}'.format(command))
super(UnknownCommandError, self).__init__('Unknown command: {}'.format(command))
self.requested = command


class LeappRuntimeError(LeappError):
pass
def __init__(self, message, exception_info=None):
super(LeappRuntimeError, self).__init__(message)
self.exception_info = exception_info


class StopActorExecution(Exception):
Expand Down
31 changes: 24 additions & 7 deletions leapp/repository/actor_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import os
import pkgutil
import sys
import traceback
import warnings
from io import UnsupportedOperation
from multiprocessing import Process, Queue
from multiprocessing import Process, Queue, Pipe

import leapp.libraries.actor # noqa # pylint: disable=unused-import
from leapp.actors import get_actor_metadata, get_actors
Expand Down Expand Up @@ -56,7 +57,7 @@ def __init__(self, definition, logger, messaging, config_model, skip_dialogs):
self.skip_dialogs = skip_dialogs

@staticmethod
def _do_run(stdin, logger, messaging, definition, config_model, skip_dialogs, args, kwargs):
def _do_run(stdin, logger, messaging, definition, config_model, skip_dialogs, error_pipe, args, kwargs):
if stdin is not None:
try:
sys.stdin = os.fdopen(stdin)
Expand All @@ -69,7 +70,13 @@ def _do_run(stdin, logger, messaging, definition, config_model, skip_dialogs, ar
target_actor = [actor for actor in get_actors() if actor.name == definition.name][0]
actor_instance = target_actor(logger=logger, messaging=messaging, config_model=config_model,
skip_dialogs=skip_dialogs)
actor_instance.run(*args, **kwargs)
try:
actor_instance.run(*args, **kwargs)
except Exception:
# Send the exception data string to the parent process
# and reraise.
error_pipe.send(traceback.format_exc())
raise
try:
# By this time this is no longer set, so we have to get it back
os.environ['LEAPP_CURRENT_ACTOR'] = actor_instance.name
Expand All @@ -96,15 +103,25 @@ def run(self, *args, **kwargs):
stdin = sys.stdin.fileno()
except UnsupportedOperation:
stdin = None

pipe_receiver, pipe_sender = Pipe()
p = Process(target=self._do_run,
args=(stdin, self.logger, self.messaging, self.definition, self.config_model,
self.skip_dialogs, args, kwargs))
self.skip_dialogs, pipe_sender, args, kwargs))
p.start()
p.join()
if p.exitcode != 0:
raise LeappRuntimeError(
'Actor {actorname} unexpectedly terminated with exit code: {exitcode}'
.format(actorname=self.definition.name, exitcode=p.exitcode))
err_message = "Actor {actorname} unexpectedly terminated with exit code: {exitcode}".format(
actorname=self.definition.name, exitcode=p.exitcode)

exception_info = None
# If there's data in the pipe, it's formatted exception info.
if pipe_receiver.poll():
exception_info = pipe_receiver.recv()

# This LeappRuntimeError will contain an exception traceback
# in addition to the above message.
raise LeappRuntimeError(err_message, exception_info)


class ActorDefinition(object):
Expand Down