We need to get exception handling to the main thread in both graphical
and textual user interface. This patch adds the code necessary to get
exception handling to the main thread in text mode by using the async
handling based on the communication queue. We just need to distinguish
between exception in the main thread or in a non-main thread.
If the exception appears in a non-main thread a message with the
exception data is sent to the queue instead of running the exception
handling. When such message is handled (which happens in the main
thread), the exception handler is run again and this time it runs the
exception handling.
We need to tell python-meh to use our raw_input because the standard one can be
called only once (the other calls are queued) and thus the one called by the TUI
will block the python-meh's one. And then the same applies to Pdb session run
from the python-meh and so on.
Also use namedtuples as python-meh does now.
---
pyanaconda/exception.py | 76 ++++++++++++++++++++++++------------
pyanaconda/ui/tui/__init__.py | 27 ++++++++++++-
pyanaconda/ui/tui/simpleline/base.py | 36 ++++++++++++-----
3 files changed, 105 insertions(+), 34 deletions(-)
diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py
index 0119ebc..81f2a87 100644
--- a/pyanaconda/exception.py
+++ b/pyanaconda/exception.py
@@ -33,7 +33,10 @@ import time
from flags import flags
import kickstart
import blivet.errors
+from pyanaconda.ui import communication
from pyanaconda.constants import ROOT_PATH
+from pyanaconda.threads import threadMgr
+# pylint: disable-msg=E0611
from gi.repository import GLib
import logging
@@ -55,23 +58,29 @@ class AnacondaExceptionHandler(ExceptionHandler):
ExceptionHandler.__init__(self, confObj, intfClass, exnClass)
self._intf_tty_num = tty_num
- def handleException(self, (ty, value, tb), obj):
+ def run_handleException(self, dump_info):
+ """
+ Helper method with one argument only so that it can be registered
+ with GLib.idle_add() to run on idle or called from a handler.
+
+ @type dump_info: an instance of the meh.DumpInfo class
- def run_handleException_on_idle(args_tuple):
- """
- Helper function with one argument only so that it can be registered
- with GLib.idle_add() to run on idle.
+ """
- @param args_tuple: ((ty, value, tb), obj)
+ super(AnacondaExceptionHandler, self).handleException(dump_info)
+ return False
+
+ def handleException(self, dump_info):
+ """
+ Our own handleException method doing some additional stuff before
+ calling the original python-meh's one.
- """
+ @type dump_info: an instance of the meh.DumpInfo class
+ @see: python-meh's ExceptionHandler.handleException
- trace, obj = args_tuple
- ty, value, tb = trace
+ """
- super(AnacondaExceptionHandler, self).handleException((ty, value, tb),
- obj)
- return False
+ ty = dump_info.exc_info.type
if issubclass(ty, blivet.errors.StorageError) and value.hardware_fault:
hw_error_msg = _("The installation was stopped due to what "
@@ -82,25 +91,45 @@ class AnacondaExceptionHandler(ExceptionHandler):
sys.exit(0)
else:
try:
+ # pylint: disable-msg=E0611
from gi.repository import Gtk
+ # XXX: Gtk stopped raising RuntimeError if it fails to
+ # initialize. Horay! But will it stay like this? Let's be
+ # cautious and raise the exception on our own to work in both
+ # cases
+ (initialized, args) = Gtk.init_check(None)
+ if not initialized:
+ raise RuntimeError()
+
if Gtk.main_level() > 0:
# main loop is running, don't crash it by running another one
# potentially from a different thread
- GLib.idle_add(run_handleException_on_idle,
- ((ty, value, tb), obj))
+ GLib.idle_add(self.run_handleException, dump_info)
else:
+ log.info("running handler")
super(AnacondaExceptionHandler, self).handleException(
- (ty, value, tb), obj)
+ dump_info)
except RuntimeError:
# X not running (Gtk cannot be initialized)
- print "An unknown error has occured, look at the "\
- "/tmp/anaconda-tb* file(s) for more details"
- super(AnacondaExceptionHandler, self).handleException(
- (ty, value, tb), obj)
+ if threadMgr.in_main_thread():
+ print "An unknown error has occured, look at the "\
+ "/tmp/anaconda-tb* file(s) for more details"
+ # in the main thread, run exception handler
+ super(AnacondaExceptionHandler, self).handleException(
+ dump_info)
+ else:
+ # not in the main thread, just send message with exception
+ # data and let message handler run the exception handler in
+ # the main thread
+ exc_info = dump_info.exc_info
+ communication.send_exception((exc_info.type, exc_info.value,
+ exc_info.stack))
+
+ def postWriteHook(self, dump_info):
+ anaconda = dump_info.object
- def postWriteHook(self, (ty, value, tb), anaconda):
# See if /mnt/sysimage is present and put exception there as well
if os.access("/mnt/sysimage/root", os.X_OK):
try:
@@ -116,7 +145,7 @@ class AnacondaExceptionHandler(ExceptionHandler):
except:
pass
- def runDebug(self, (ty, value, tb)):
+ def runDebug(self, exc_info):
if self._intf_tty_num != 1:
iutil.vtActivate(1)
@@ -146,7 +175,7 @@ class AnacondaExceptionHandler(ExceptionHandler):
print("Use 'continue' command to quit the debugger and get back to
"\
"the main window")
import pdb
- pdb.post_mortem (tb)
+ pdb.post_mortem(exc_info.stack)
if self._intf_tty_num != 1:
iutil.vtActivate(self._intf_tty_num)
@@ -228,8 +257,7 @@ f%s(msg, non_ascii)
msg = "NOTABUG: testing exception handling"
# raise exception from a separate thread
- # XXX: may create a circular dependency if imported globally
- from pyanaconda.threads import AnacondaThread, threadMgr
+ from pyanaconda.threads import AnacondaThread
threadMgr.add(AnacondaThread(name="AnaExceptionHandlingTest",
target=raise_exception,
args=(msg, non_ascii)))
diff --git a/pyanaconda/ui/tui/__init__.py b/pyanaconda/ui/tui/__init__.py
index 3a76045..e23c588 100644
--- a/pyanaconda/ui/tui/__init__.py
+++ b/pyanaconda/ui/tui/__init__.py
@@ -21,6 +21,7 @@
from pyanaconda import ui
from pyanaconda.ui import common
+from pyanaconda.ui.communication import hubQ, HUB_CODE_EXCEPTION
from pyanaconda.flags import flags
import simpleline as tui
from hubs.summary import SummaryHub
@@ -29,12 +30,30 @@ from spokes import StandaloneSpoke
from tuiobject import YesNoDialog, ErrorDialog
import os
+import sys
import site
import meh.ui.text
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
+def exception_msg_handler(event, data):
+ """
+ Handler for the HUB_CODE_EXCEPTION message in the hubQ.
+
+ @param event: event data
+ @type event: (event_type, message_data)
+ @param data: additional data
+ @type data: any
+
+ """
+
+ # get data from the event data structure
+ (event_type, msg_data) = event
+
+ # msg_data is a list
+ sys.excepthook(*msg_data[0])
+
class TextUserInterface(ui.UserInterface):
"""This is the main class for Text user interface."""
@@ -118,7 +137,13 @@ class TextUserInterface(ui.UserInterface):
This method must be provided by all subclasses.
"""
self._app = tui.App(self.productTitle, yes_or_no_question = YesNoDialog,
- quit_message = self.quitMessage)
+ quit_message = self.quitMessage, queue = hubQ)
+
+ # tell python-meh it should use our raw_input
+
self._meh_interface.set_io_handler(meh.ui.text.IOHandler(in_func=self._app.raw_input))
+
+ # register handler for the exception messages
+ self._app.register_event_handler(HUB_CODE_EXCEPTION, exception_msg_handler)
_hubs = self._list_hubs()
# First, grab a list of all the standalone spokes.
diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py
index 0aabe67..cd0869b 100644
--- a/pyanaconda/ui/tui/simpleline/base.py
+++ b/pyanaconda/ui/tui/simpleline/base.py
@@ -21,9 +21,11 @@
__all__ = ["App", "UIScreen", "Widget"]
+import sys
import readline
import Queue
import getpass
+import threading
from pyanaconda.threads import threadMgr, AnacondaThread
from pyanaconda.ui.communication import HUB_CODE_EXCEPTION, HUB_CODE_INPUT
@@ -31,6 +33,8 @@ import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
N_ = lambda x: x
+RAW_INPUT_LOCK = threading.Lock()
+
def send_exception(queue, ex):
queue.put((HUB_CODE_EXCEPTION, [ex]))
@@ -91,6 +95,9 @@ class App(object):
# ensure unique thread names
self._in_thread_counter = 0
+ # do not run multiple raw_inputs (doesn't work)
+ self._raw_input_running = False
+
# event handlers
# key: event id
# value: list of tuples (callback, data)
@@ -145,7 +152,18 @@ class App(object):
if hidden:
data = getpass.getpass(prompt)
else:
- data = raw_input(prompt)
+ sys.stdout.write(prompt)
+ sys.stdout.flush()
+ # XXX: only one raw_input can run at a time, don't schedule another
+ # one as it would cause weird behaviour and block other packages'
+ # raw_inputs
+ if not RAW_INPUT_LOCK.acquire(False):
+ # raw_input is already running
+ return
+ else:
+ # lock acquired, we can run raw_input
+ data = raw_input()
+ RAW_INPUT_LOCK.release()
queue.put((HUB_CODE_INPUT, [data]))
@@ -269,8 +287,8 @@ class App(object):
self._redraw = False
except ExitMainLoop:
raise
- except Exception as ex:
- send_exception(self.queue, ex)
+ except Exception:
+ send_exception(self.queue, sys.exc_info())
return False
else:
@@ -328,8 +346,8 @@ class App(object):
prompt = last_screen.prompt(self._screens[-1][1])
except ExitMainLoop:
raise
- except Exception as ex:
- send_exception(self.queue, ex)
+ except Exception:
+ send_exception(self.queue, sys.exc_info())
continue
# None means prompt handled the input by itself
@@ -385,8 +403,8 @@ class App(object):
handler(event, data)
except ExitMainLoop:
raise
- except Exception as ex:
- send_exception(self.queue, ex)
+ except Exception:
+ send_exception(self.queue, sys.exc_info())
def raw_input(self, prompt, hidden=False):
"""This method reads one input from user. Its basic form has only
one
@@ -425,8 +443,8 @@ class App(object):
return True
except ExitMainLoop:
raise
- except Exception as ex:
- send_exception(self.queue, ex)
+ except Exception:
+ send_exception(self.queue, sys.exc_info())
return False
# global close command
--
1.7.11.7