Author Topic: Menu with Submenu Causes Crash on Exit  (Read 176 times)

I've already submitted this as a bug report through the software, but just in case anyone has any suggestions I'm adding it here.

Running Substance Painter 6.2.1 on Windows 10

Using `substance_painter.ui.add_menu` to add a menu that contains a submenu will cause Substance to crash on exit. This happens regardless of whether the menu is added through the console or through a plugin and will happen even if the menu has been removed again.

It is not immediately obvious that this is happening, but on restard you will get the "Substance Painter didn't shut down correctly" bug report dialog.

Further, if there is an action on the submenu that removes the menu, Substance will crash instantly.

As an example if you instantiate the following class Substance will then crash on exit. If you click the "Delete Menu" menu item it will crash instantly

Code: [Select]
class Menu(object):
    def __init__(self):
        self.menu = QtWidgets.QMenu("Menu")
        sub_menu = QtWidgets.QMenu("Submenu")
        self.menu.addMenu(sub_menu)
        action = sub_menu.addAction("Delete Menu")
        substance_painter.ui.add_menu(self.menu)
        action.triggered.connect(self.remove_menu)
       
    def remove_menu(self):
        substance_painter.ui.delete_ui_element(self.menu)

I've found a workaround for both issues, though I'm pretty certain they're still bugs.

To prevent crashing you must:
  • destroy any sub-menus down to their very last molecule or they will cause a crash.
  • if you're removing a menu using an action on a submenu of that menu, set the signal connection type to Qt.QueuedConnection
  • on shutdown, cleanup the menu using the QCoreApplication.aboutToQuit signal and not the close_plugin method

For example this plugin will both crash if you click the menu item and it will crash on exit (note: it will not crash if you simply unload the plugin through the "Python" menu):
Code: [Select]
from PySide2 import QtWidgets
import substance_painter


menus = []

def start_plugin():
    """This method is called when the plugin is started."""
    menu = QtWidgets.QMenu("Menu")
    sub_menu = QtWidgets.QMenu("Submenu")
    menu.addMenu(sub_menu)
    action = sub_menu.addAction("Remove Menu")
    substance_painter.ui.add_menu(menu)
    action.triggered.connect(remove_menu)
    menus.append(menu)
    menus.append(sub_menu)

def remove_menu():
    menus[0].removeAction(menus[1].menuAction())
    menus[1].clear()
    menus[1].deleteLater()
    menus[1] = None
    menus[0].clear()
    substance_painter.ui.delete_ui_element(menus[0])
    menus[0] = None

def close_plugin():
    """This method is called when the plugin is stopped."""
    remove_menu()

While this plugin will work as expected in both situations (though, of course, this one can't be unloaded from the "Python" menu):
Code: [Select]
from PySide2 import QtWidgets, QtCore
import substance_painter


menus = []

def start_plugin():
    """This method is called when the plugin is started."""
    menu = QtWidgets.QMenu("Menu")
    sub_menu = QtWidgets.QMenu("Submenu")
    menu.addMenu(sub_menu)
    action = sub_menu.addAction("Remove Menu")
    substance_painter.ui.add_menu(menu)
    action.triggered.connect(remove_menu, QtCore.Qt.QueuedConnection)
    menus.append(menu)
    menus.append(sub_menu)
   
    app = QtCore.QCoreApplication.instance()
    app.aboutToQuit.connect(remove_menu)

def remove_menu():
    menus[0].removeAction(menus[1].menuAction())
    menus[1].clear()
    menus[1].deleteLater()
    menus[1] = None
    substance_painter.ui.delete_ui_element(menus[0])
    menus[0] = None

def close_plugin():
    """This method is called when the plugin is stopped."""
    pass

This means there is likely another issue where "close_plugin" is not run in a timely fashion and part of the menu has already been destructed, or something similar.