Skip to content

Commit

Permalink
Merge pull request #5948 from drew2a/feature/stop_gui_crash
Browse files Browse the repository at this point in the history
Stop crash an application when GUI raise an exception
  • Loading branch information
drew2a committed Jan 23, 2021
2 parents 62cf37e + 30551b2 commit 451d6ad
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 76 deletions.
7 changes: 5 additions & 2 deletions src/tribler-gui/tribler_gui/core_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
import sys
import time
Expand Down Expand Up @@ -28,17 +29,19 @@ class CoreManager(QObject):
tribler_stopped = pyqtSignal()
core_state_update = pyqtSignal(str)

def __init__(self, api_port, api_key):
def __init__(self, api_port, api_key, error_handler):
QObject.__init__(self, None)

self._logger = logging.getLogger(self.__class__.__name__)

self.base_path = get_base_path()
if not is_frozen():
self.base_path = os.path.join(get_base_path(), "..")

self.core_process = None
self.api_port = api_port
self.api_key = api_key
self.events_manager = EventRequestManager(self.api_port, self.api_key)
self.events_manager = EventRequestManager(self.api_port, self.api_key, error_handler)

self.shutting_down = False
self.should_stop_on_shutdown = False
Expand Down
14 changes: 9 additions & 5 deletions src/tribler-gui/tribler_gui/dialogs/feedbackdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals
start_time,
sentry_event=None,
error_reporting_requires_user_consent=True,
stop_application_on_close=True,
):
QDialog.__init__(self, parent)

Expand All @@ -39,6 +40,7 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals
self.selected_item_index = 0
self.tribler_version = tribler_version
self.sentry_event = sentry_event
self.stop_application_on_close = stop_application_on_close

# Qt 5.2 does not have the setPlaceholderText property
if hasattr(self.comments_text_edit, "setPlaceholderText"):
Expand Down Expand Up @@ -98,6 +100,7 @@ def add_item_to_info_widget(key, value):

self.send_automatically = FeedbackDialog.can_send_automatically(error_reporting_requires_user_consent)
if self.send_automatically:
self.stop_application_on_close = True
self.on_send_clicked(True)

@staticmethod
Expand All @@ -123,13 +126,13 @@ def on_right_click_item(self, pos):
menu.exec_(self.env_variables_list.mapToGlobal(pos))

def on_cancel_clicked(self, checked):
QApplication.quit()
self.close()

def on_report_sent(self, response):
if not response:
return
if self.send_automatically:
QApplication.quit()
self.close()

sent = response['sent']

Expand All @@ -142,7 +145,7 @@ def on_report_sent(self, response):
box.setStyleSheet("QPushButton { color: white; }")
box.exec_()

QApplication.quit()
self.close()

def on_send_clicked(self, checked):
self.send_report_button.setEnabled(False)
Expand Down Expand Up @@ -180,5 +183,6 @@ def on_send_clicked(self, checked):
TriblerNetworkRequest(endpoint, self.on_report_sent, raw_data=tribler_urlencode(post_data), method='POST')

def closeEvent(self, close_event):
QApplication.quit()
close_event.ignore()
if self.stop_application_on_close:
QApplication.quit()
close_event.ignore()
87 changes: 87 additions & 0 deletions src/tribler-gui/tribler_gui/error_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import logging
import os
import traceback

from tribler_common.sentry_reporter.sentry_reporter import SentryReporter

from tribler_gui.dialogs.feedbackdialog import FeedbackDialog
from tribler_gui.event_request_manager import CoreConnectTimeoutError


class ErrorHandler:
def __init__(self, tribler_window):
logger_name = self.__class__.__name__
self._logger = logging.getLogger(logger_name)
SentryReporter.ignore_logger(logger_name)

self.tribler_window = tribler_window

self._handled_exceptions = set()
self._tribler_stopped = False

def gui_error(self, *exc_info):
if self._tribler_stopped:
return

info_type, info_error, tb = exc_info
if info_type in self._handled_exceptions:
return
self._handled_exceptions.add(info_type)

text = "".join(traceback.format_exception(info_type, info_error, tb))

if info_type is CoreConnectTimeoutError:
text = text + self.tribler_window.core_manager.core_traceback
self._stop_tribler(text)

self._logger.error(text)

FeedbackDialog(
parent=self.tribler_window,
exception_text=text,
tribler_version=self.tribler_window.tribler_version,
start_time=self.tribler_window.start_time,
sentry_event=SentryReporter.event_from_exception(info_error),
error_reporting_requires_user_consent=True,
stop_application_on_close=self._tribler_stopped
).show()

def core_error(self, text, core_event):
if self._tribler_stopped:
return

self._logger.error(text)

self._stop_tribler(text)

FeedbackDialog(
parent=self.tribler_window,
exception_text=text,
tribler_version=self.tribler_window.tribler_version,
start_time=self.tribler_window.start_time,
sentry_event=core_event['sentry_event'],
error_reporting_requires_user_consent=core_event['error_reporting_requires_user_consent'],
stop_application_on_close=self._tribler_stopped
).show()

def _stop_tribler(self, text):
if self._tribler_stopped:
return

self._tribler_stopped = True

self.tribler_window.tribler_crashed.emit(text)
self.tribler_window.delete_tray_icon()

# Stop the download loop
self.tribler_window.downloads_page.stop_loading_downloads()

# Add info about whether we are stopping Tribler or not
os.environ['TRIBLER_SHUTTING_DOWN'] = str(self.tribler_window.core_manager.shutting_down).upper()
if not self.tribler_window.core_manager.shutting_down:
self.tribler_window.core_manager.stop(stop_app_on_shutdown=False)

self.tribler_window.setHidden(True)

if self.tribler_window.debug_window:
self.tribler_window.debug_window.setHidden(True)
11 changes: 5 additions & 6 deletions src/tribler-gui/tribler_gui/event_request_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class EventRequestManager(QNetworkAccessManager):
change_loading_text = pyqtSignal(str)
config_error_signal = pyqtSignal(str)

def __init__(self, api_port, api_key):
def __init__(self, api_port, api_key, error_handler):
QNetworkAccessManager.__init__(self)
url = QUrl("http://localhost:%d/events" % api_port)
self.request = QNetworkRequest(url)
Expand All @@ -49,6 +49,7 @@ def __init__(self, api_port, api_key):
self.current_event_string = ""
self.reply = None
self.shutting_down = False
self.error_handler = error_handler
self._logger = logging.getLogger('TriblerGUI')
self.reactions_dict = {
NTFY.CHANNEL_ENTITY_UPDATED.value: self.node_info_updated.emit,
Expand Down Expand Up @@ -121,13 +122,11 @@ def on_read_data(self):
elif event_type == NTFY.TRIBLER_EXCEPTION.value:
text = json_dict["event"]["text"]
backend_event = {
'backend_event': {
'sentry_event': json_dict['sentry_event'],
'error_reporting_requires_user_consent': json_dict['error_reporting_requires_user_consent'],
}
'sentry_event': json_dict['sentry_event'],
'error_reporting_requires_user_consent': json_dict['error_reporting_requires_user_consent'],
}

raise RuntimeError(text, backend_event)
self.error_handler.core_error(text, backend_event)
self.current_event_string = ""

def on_finished(self):
Expand Down
68 changes: 5 additions & 63 deletions src/tribler-gui/tribler_gui/tribler_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import signal
import sys
import time
import traceback
from base64 import b64encode
from urllib.parse import unquote, urlparse

Expand Down Expand Up @@ -38,8 +37,6 @@
QTreeWidget,
)

from tribler_common.sentry_reporter.sentry_reporter import SentryReporter

from tribler_core.modules.process_checker import ProcessChecker
from tribler_core.utilities.unicode import hexlify
from tribler_core.version import version_id
Expand All @@ -64,10 +61,9 @@
from tribler_gui.dialogs.addtopersonalchanneldialog import AddToChannelDialog
from tribler_gui.dialogs.confirmationdialog import ConfirmationDialog
from tribler_gui.dialogs.createtorrentdialog import CreateTorrentDialog
from tribler_gui.dialogs.feedbackdialog import FeedbackDialog
from tribler_gui.dialogs.new_channel_dialog import NewChannelDialog
from tribler_gui.dialogs.startdownloaddialog import StartDownloadDialog
from tribler_gui.event_request_manager import CoreConnectTimeoutError
from tribler_gui.error_handler import ErrorHandler
from tribler_gui.tribler_action_menu import TriblerActionMenu
from tribler_gui.tribler_request_manager import TriblerNetworkRequest, TriblerRequestManager, request_manager
from tribler_gui.utilities import (
Expand Down Expand Up @@ -101,61 +97,6 @@ class TriblerWindow(QMainWindow):
tribler_crashed = pyqtSignal(str)
received_search_completions = pyqtSignal(object)

def on_exception(self, *exc_info):
if self.exception_handler_called:
# We only show one feedback dialog, even when there are two consecutive exceptions.
return

self.exception_handler_called = True
info_type, info_error, _ = exc_info
exception_text = "".join(traceback.format_exception(*exc_info))

sentry_event = None
error_reporting_requires_user_consent = True
for arg in info_error.args:
if not isinstance(arg, dict) or 'backend_event' not in arg:
continue

backend_event = arg['backend_event']
sentry_event = backend_event['sentry_event']
error_reporting_requires_user_consent = backend_event['error_reporting_requires_user_consent']

if not sentry_event:
# then the exception occured in GUI
sentry_event = SentryReporter.event_from_exception(info_error)

logging.error(exception_text)

self.tribler_crashed.emit(exception_text)
self.delete_tray_icon()

# Stop the download loop
self.downloads_page.stop_loading_downloads()

# Add info about whether we are stopping Tribler or not
os.environ['TRIBLER_SHUTTING_DOWN'] = str(self.core_manager.shutting_down)

if not self.core_manager.shutting_down:
self.core_manager.stop(stop_app_on_shutdown=False)

self.setHidden(True)

if self.debug_window:
self.debug_window.setHidden(True)

if info_type is CoreConnectTimeoutError:
exception_text = exception_text + self.core_manager.core_traceback

dialog = FeedbackDialog(
self,
exception_text,
self.tribler_version,
self.start_time,
sentry_event,
error_reporting_requires_user_consent,
)
dialog.show()

def __init__(self, core_args=None, core_env=None, api_port=None, api_key=None):
QMainWindow.__init__(self)
self._logger = logging.getLogger(self.__class__.__name__)
Expand All @@ -177,7 +118,9 @@ def __init__(self, core_args=None, core_env=None, api_port=None, api_key=None):
# TODO: move version_id to tribler_common and get core version in the core crash message
self.tribler_version = version_id
self.debug_window = None
self.core_manager = CoreManager(api_port, api_key)

self.error_handler = ErrorHandler(self)
self.core_manager = CoreManager(api_port, api_key, self.error_handler)
self.pending_requests = {}
self.pending_uri_requests = []
self.download_uri = None
Expand All @@ -191,12 +134,11 @@ def __init__(self, core_args=None, core_env=None, api_port=None, api_key=None):
self.last_search_query = None
self.last_search_time = None
self.start_time = time.time()
self.exception_handler_called = False
self.token_refresh_timer = None
self.shutdown_timer = None
self.add_torrent_url_dialog_active = False

sys.excepthook = self.on_exception
sys.excepthook = self.error_handler.gui_error

uic.loadUi(get_ui_file_path('mainwindow.ui'), self)
TriblerRequestManager.window = self
Expand Down

0 comments on commit 451d6ad

Please sign in to comment.