leapp/SOURCES/0014-Expose-tracebacks-from...

112 lines
4.6 KiB
Diff

From 3d453d180d9b7bf0e283e313d1b5e294a5b4aeeb Mon Sep 17 00:00:00 2001
From: Roman Prilipskii <rprilipskii@cloudlinux.com>
Date: Wed, 9 Nov 2022 07:22:54 +0400
Subject: [PATCH 14/16] Expose tracebacks from actor exceptions
Previously when leapp would be running actors, any exceptions raised by
those actors in child processes would only be output to stderr,
while the main process would only have the exit code to examine.
Consequently, there was no real way to log occuring exceptions -
only providing the aforementioned exit code was possible.
This patch adds a pipe between the main and the actor processes,
through which a formatted exception + traceback string is provided to
the main process.
This string is then packaged into the LeappRuntimeError thrown from the
process, to be utilized downstream in
workflow/command code.
---
leapp/exceptions.py | 4 +++-
leapp/repository/actor_definition.py | 31 +++++++++++++++++++++-------
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/leapp/exceptions.py b/leapp/exceptions.py
index 1bd4222..ef9d647 100644
--- a/leapp/exceptions.py
+++ b/leapp/exceptions.py
@@ -109,7 +109,9 @@ class UnknownCommandError(LeappError):
class LeappRuntimeError(LeappError):
- pass
+ def __init__(self, message, exception_info):
+ super().__init__(message)
+ self.exception_info = exception_info
class StopActorExecution(Exception):
diff --git a/leapp/repository/actor_definition.py b/leapp/repository/actor_definition.py
index 7c0beb9..2b5df1a 100644
--- a/leapp/repository/actor_definition.py
+++ b/leapp/repository/actor_definition.py
@@ -4,9 +4,10 @@ import logging
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
@@ -56,7 +57,7 @@ class ActorCallContext(object):
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)
@@ -69,7 +70,13 @@ class ActorCallContext(object):
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
@@ -96,15 +103,25 @@ class ActorCallContext(object):
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):
--
2.39.0