Profile Picture

Python API background threads crashing

Posted By midix 5 Years Ago
You don't have permission to rate!
Author
Message
midix
midix
Posted 5 Years Ago
View Quick Profile
Distinguished Member

Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)

Group: Forum Members
Last Active: Last Year
Posts: 59, Visits: 363
I have successfully created a UI form for my plugin, it loads fine and works as expected. Now I'm working on the core logic. It has some heavy task that user can launch from UI.

Being a good citizen, I don't want to freeze the UI, so I tried some standard multithreading techniques with QRunnable and also QThread. I could not find anything related to "thread" in Reallusion's wiki, so I tried some examples I found on the Internet.

Unfortunately, it always crashes iClone as soon as I start a thread. I have created a simplified minimal example script that crashes iClone by mere attempting to launch a thread that does only print().

To reproduce the issue, save the following code snippet as threads.py file as run it as usual with Script->Load plugin and then go to Plugins -> Python Samples -> Run thread.  

import RLPy
import PySide2

from PySide2 import *
from PySide2.shiboken2 import wrapInstance
from PySide2.QtCore import *

class DummyWorker(QThread) :
    def run(self) :
        print(f"worker finished.")
        
my_thread = None

def initialize_plugin() :
    global my_thread
    # Add menu
    ic_dlg = wrapInstance(int(RLPy.RUi.GetMainWindow()), QtWidgets.QMainWindow)
    plugin_menu = ic_dlg.menuBar().findChild(QtWidgets.QMenu, "pysample_menu")
    if (plugin_menu == None):
        plugin_menu = wrapInstance(int(RLPy.RUi.AddMenu("Python Samples", RLPy.EMenu_Plugins)), QtWidgets.QMenu)
        plugin_menu.setObjectName("pysample_menu")
        
    menu_actions = plugin_menu.actions()
    for a in menu_actions :
        if a.text() == "Run thread":
            plugin_menu.removeAction(a)    
                
    hello_world_action = plugin_menu.addAction("Run thread")
    hello_world_action.triggered.connect(run_thread)
    
    my_thread = DummyWorker()
    

def run_thread() :
    RLPy.RUi.ShowMessageBox("Python Plugin", "Will launch a thread, keep fingers crossed...", RLPy.EMsgButton_Ok)
    my_thread.start()
    

def run_script() :
    initialize_plugin() 


For me, it freezes iClone for a few seconds, and then iClone crashes. I couldn't even find any meaningful crash logs in appdata Reallusion folder - it's some encoded garbage like 61CEA8EAACB90F063C0DE4C ....

Has anyone any ideas? What is proper, safe way to run a heavy background task in iClone Python environment?
midix
midix
Posted 5 Years Ago
View Quick Profile
Distinguished Member

Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)Distinguished Member (1.1K reputation)

Group: Forum Members
Last Active: Last Year
Posts: 59, Visits: 363
After some investigation, I think I found the culprit:
it's print  :hehe:

I think the problem is that iClone is monitoring print (possibly redirected their stdout) to output to their UI console. And when another thread tries to print or logging.info etc., it attempts to output on UI thread, and that's a big no-no and can lead to weird race conditions and crashes.

So, it seems, we cannot use print statements in our background threads, 
unless Reallusion redesign their Python logging to be thread safe.

For now as a workaround I implement my own thread safe wrapper around print that dispatches it back to the main Qt thread through its Signal queue:

class DummyWorker(QThread) :
    
    # Signals must be class vars
    logger_emiter = Signal(str)
        
    def __init__(self):
        super().__init__()
        
        # send to the main thread explicitly
        self.logger_emiter.connect(self.main_log, Qt.QueuedConnection)
        print("DummyWorker created")
        
    @Slot(str) 
    def main_log(self, s):
        # logging must be on the main thread,
        # else iClone crashes because it logs to UI thread
        print(s)
    
    def run(self) :
        time.sleep(2)
        #print(f"worker finished.") crashes - no printing from another thread in iClone!
        self.logger_emiter.emit("DummyWorker slept")
SeanMac
SeanMac
Posted 5 Years Ago
View Quick Profile
Distinguished Member

Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)Distinguished Member (5.3K reputation)

Group: Forum Members
Last Active: 2 Years Ago
Posts: 416, Visits: 3.4K
Good stuff - Kudos!
delebash
delebash
Posted 5 Years Ago
View Quick Profile
Distinguished Member

Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)Distinguished Member (1.3K reputation)

Group: Forum Members
Last Active: Last Year
Posts: 41, Visits: 448
Life saver.  If possible could you help with another threading issue I am having.

I am using websockets to send data from a webclient to the reallusion client however I have a problems with it crashing on running a long process.

When data is sent from webclient to pythonclient event @sio.on('message)
   is fired and runs def message.  However when data is sent and that event fires iClone crashes.  I am not sure how to get the socketio @sio events to run on another thread.  Thanks.
class DummyWorker(QThread):
# Signals must be class vars
logger_emiter = Signal(str)

def __init__(self):
super().__init__()

# send to the main thread explicitly
self.logger_emiter.connect(self.main_log, Qt.QueuedConnection)
print("DummyWorker created")

@Slot(str)
def main_log(self, s):
# logging must be on the main thread,
# else iClone crashes because it logs to UI thread
print(s)

# def run(self):
# # time.sleep(2)
# # print(f"worker finished.") crashes - no printing from another thread in iClone!
# self.logger_emiter.emit("DummyWorker slept")
@staticmethod
def connectserver():
my_thread = DummyWorker()
my_thread.logger_emiter.emit("connecting")

if sio.sid:
my_thread.logger_emiter.emit("Already connected")
else:
sio.connect(url, transports=transport)

# Note: print statment crashes iClone main thread need to do print statment on different thread
# https://forum.reallusion.com/443699/Python-API-background-threads-crashing
@sio.on('connect')
def connect():
my_thread.logger_emiter.emit("connected")
sio.emit('room', myroom)
# print('Joined room ' + myroom)
@sio.on('message')
def message(data):
my_thread.logger_emiter.emit('data')
# # print('Received message!', data)
# proccessmocapdata(data)
# @sio.on('join')
# def join(room):
# print('Joined room ', room)
#
# @sio.on('connect_error')
# def connect_error():
# print("Connection failed!")
@sio.on('disconnect')
def disconnect():
my_thread.logger_emiter.emit("Disconnected")
sio.disconnect()



Reading This Topic