Search code examples
pythonqtpyqtpyqt5event-loop

"QCoreApplication::exec: The event loop is already running" except when stopping code at sys.exit(app.exec_())


I'm new to Qt and PyQt and I'm trying to connect an elaborate file parsing library written by someone else to a simple GUI. When I click on the "Process Data" button, I encounter the following error message repeatedly:

QCoreApplication::exec: The event loop is already running
QCoreApplication::exec: The event loop is already running
QCoreApplication::exec: The event loop is already running
QCoreApplication::exec: The event loop is already running
QCoreApplication::exec: The event loop is already running
...

I have noticed that when I debug the script, the issue doesn't occur if I put a breakpoint on the line sys.exit(app.exec_()) and only click on "Resume" after selecting the data. In this specific case, if I do this once and press "Resume" the program works in all subsequent attempts to load file(s) and folders.

Here is the relevant code:

import os
import sys
import traceback
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QTextEdit)

def ParsingFunction(directory=None, files=[], d_dataAlreadyIn=None):
# ...

# Import/outport function, mostly used for loading the .med files
from DC_Lib import DC_IO_lib 
from DC_Lib import DC_CV_Processing 
from DC_Lib import DC_IV_Processing

class DataProcessingApp(QWidget):
    def __init__(self):
        super().__init__()
        # ...
        self.process_button = QPushButton("Process Data")
        self.process_button.clicked.connect(self.process_data)
        self.layout.addWidget(self.process_button)
        # ...

    def process_data(self):
        if self.directory or self.files:
            self.text_edit.append("Processing data...")
            if self.directory:
                self.data = ParsingFunction(directory=self.directory)
            else:
                self.data = ParsingFunction(files=self.files)

            self.text_edit.append("Data processing complete.")
        else:
            self.text_edit.append("Please select a directory or files to process.")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DataProcessingApp()
    window.show()
    sys.exit(app.exec_())

If the question is too vague let me know, but I think this behavior with the exec_() method during debugging might ring a bell to someone with more experience with Qt. Any insights or suggestions on how to resolve this issue would be greatly appreciated.

Thank you!

[update]

To improve reproducibility here are the contents of the ParsingFunction and its subfunctions. I won't add more code because I'm pretty sure the problem is with the input() call in GetMeDopingType():

while True:
   inp = str(input())
   if inp.strip()[0].upper() == "C":
      C_IO.Abort()
   if inp.strip()[0].lower() == 'n' or inp.strip()[0].lower() == 'p':
      break
   GLOBAL_d_Dopingtypes[wafername] = inp.strip()[0].lower()

When I hardcode inp, I don't get the QCoreApplication::exec. Any ideas why not? This answer sounds like it has to do with my issue, but I haven't been able to translate into a fix for my case.

Here's the full code:

def ParsingFunction(directory=None, files=[], d_dataAlreadyIn=None):
    AllData = {}
    if d_dataAlreadyIn is not None:
        AllData = d_dataAlreadyIn
    WarnCount = 0

    if directory is not None:
        for root, dirs, files in os.walk(directory):
            for filename in files:
                WorkMagic_SubFunction(C_IO.EndInSlash(root), filename, AllData)
                WarnCount += 1
                if WarnCount % 100 == 0:
                    print(filename)
                if WarnCount > 10000:
                    print("Warning: " + str(WarnCount) + " files processed, could indicate an infinite loop!")
    else:
        for file in files:
            root = os.path.dirname(file)
            filename = os.path.basename(file)
            WorkMagic_SubFunction(C_IO.EndInSlash(root), filename, AllData)
            WarnCount += 1
            if WarnCount % 100 == 0:
                print(filename)
            if WarnCount > 10000:
                print("Warning: " + str(WarnCount) + " files processed, could indicate an infinite loop!")

    return AllData


def WorkMagic_SubFunction(directory, filename, AllData):
    global DotArea
    global ExtentionToLookFor
    global INFER_PMA_FROM_FOLDER

    extention = filename.split('.')[-1]
    if extention == ExtentionToLookFor:
        tmp = C_IO.ReadInMedFile(directory + filename, ForceReadSecondDataSection=HasToHandleBTI)
        mestype = FigureOutWhatThisIs(tmp)
        if 'SiP' in filename:
            tmp["techinfo"]["doping"] = 'P'
        if mestype == "DropThisOne":
            return AllData
        if INFER_PMA_FROM_FOLDER and "_PMA" in directory:
            tmp["techinfo"]["wafer"] = tmp["techinfo"]["wafer"] + "A"
        Device_ID = tmp["techinfo"]["testchip"] + "-" + tmp["techinfo"]["testdevice"]
        if not "DotArea" in tmp["techinfo"]:
            guessarr = FilenameToArea(filename)
            if guessarr is None:
                tmp["techinfo"]["DotArea"] = str(DotArea)
            else:
                tmp["techinfo"]["DotArea"] = str(guessarr)
        else:
            tmp["techinfo"]["DotArea"] = str(tmp["techinfo"]["DotArea"])
        tmp["techinfo"]["ScriptName"] = str(SCRIPTNAME)
        tmp["techinfo"]["ScriptVersion"] = str(VERSION)
        if "devicemap" in tmp["techinfo"]:
            tmp["techinfo"]["devicemap"] = tmp["techinfo"]["devicemap"].replace(r"/imec/other/drec/medusa", r"\\unix\drec\medusa")
        if "chipmap" in tmp["techinfo"]:
            tmp["techinfo"]["chipmap"] = tmp["techinfo"]["chipmap"].replace(r"/imec/other/drec/medusa", r"\\unix\drec\medusa")
        key = tmp["techinfo"]["wafer"] + "___###____" + tmp["techinfo"]["chuck_temperature"]
        if not key in AllData:
            AllData[key] = {}
        if not mestype in AllData[key]:
            AllData[key][mestype] = {}
        if not Device_ID in AllData[key][mestype]:
            AllData[key][mestype][Device_ID] = []
        finalStruct = None
        if not (mestype == "NBTI" or mestype == "PBTI"):
            if HasToHandleBTI:
                techinfo = tmp["techinfo"]

                # call to the input()
                dop = GetMeDopingType(techinfo["wafer"], techinfo)

                techinfo['doping'] = dop
                tmp = C_IO.ReadInMedFile(directory + filename, ForceReadSecondDataSection=False)
                tmp["techinfo"] = techinfo
        if mestype == "MFCV":
            finalStruct = tmp
        elif mestype == "Hys":
            TheStruct = C_CV.CV_HYS_Curve(SampleName=tmp["techinfo"]["wafer"], DopingType=GetMeDopingType(tmp["techinfo"]["wafer"], tmp["techinfo"]), Temperature_K=300)
            TheStruct.Load___read_in_Med_structure(tmp)
            TheStruct.do_areanormalisation(1.0)
            finalStruct = TheStruct
        elif mestype == "NBTI" or mestype == "PBTI":
            TheStruct = C_CV.SFCV()
            TheStruct.Load___read_in_Med_Structure(tmp)
            TheStruct.do_areanormalisation(1.0)
            finalStruct = TheStruct
        elif mestype == "IgVg":
            TheStruct = C_IV.SFIV()
            TheStruct.Load___read_in_Med_Structure(tmp)
            finalStruct = TheStruct
        elif mestype == "CET":
            TheStruct = C_CV.SFCV()
            TheStruct.Load___read_in_Med_Structure(tmp)
            finalStruct = TheStruct
        else:
            print("Mestype not supported! '" + str(mestype) + "' in file:" + directory + filename)
        if not finalStruct is None:
            if not type(finalStruct) is dict:
                dop = GetMeDopingType(tmp["techinfo"]["wafer"], tmp["techinfo"])
                finalStruct._header['doping'] = dop
            AllData[key][mestype][Device_ID].append(finalStruct)
    return AllData

# The function I think is causing the problem
def GetMeDopingType(wafername, techinfo):
    global GLOBAL_d_Dopingtypes
    if "SiP" in wafername:
        return "p"
    if not wafername in GLOBAL_d_Dopingtypes:
        if techinfo is not None and "doping" in techinfo:
            GLOBAL_d_Dopingtypes[wafername] = techinfo["doping"].strip()[0].lower()
        else:
            print("Please prodide doping type for wafer: " + str(wafername) + " [n/p]: ")
            while True:
                inp = str(input())
                if inp.strip()[0].upper() == "C":
                    C_IO.Abort()
                if inp.strip()[0].lower() == 'n' or inp.strip()[0].lower() == 'p':
                    break
            GLOBAL_d_Dopingtypes[wafername] = inp.strip()[0].lower()
    return GLOBAL_d_Dopingtypes[wafername]


def FilenameToArea(filename):
    global ScannedSquareSizes_in_um
    size = None
    for i in ScannedSquareSizes_in_um:
        if "_" + str(i) + "x" + str(i) + "_" in filename:
            size = (float(i) * 1E-4) ** 2
            break
    return size


def FigureOutWhatThisIs(d_info):
    if "targetrole" in d_info["techinfo"]:
        TarRol = d_info["techinfo"]["targetrole"]
        if TarRol == "VFBTEST":
            return "DropThisOne"
        elif TarRol == "CET_EXTRACTION" or TarRol == "REFERENCE":
            return "CET"
        elif TarRol == "FREQ_DISPERSION":
            return "MFCV"
        elif TarRol == "PBTI_MES" or TarRol == "PBTI_Reference" or TarRol == "PBTI_REFERENCE" or TarRol == "PBTI_AFTERCHECK":
            return "PBTI"
        elif TarRol == "NBTI_MES" or TarRol == "NBTI_Reference" or TarRol == "NBTI_REFERENCE" or TarRol == "NBTI_AFTERCHECK":
            return "NBTI"
        elif TarRol == "CVHYS":
            return "Hys"
        elif TarRol == "IgVg_leakage":
            return "IgVg"
        else:
            print("Unknown target role:" + str(TarRol))
            input("just hit enter to continue")
            return "TechniqueNotKnown"
    if "instrument" in d_info["techinfo"]:
        if "Keithley Instruments" in d_info["techinfo"]["instrument"]:
            return "IgVg"
    if "filename" in d_info["techinfo"]:
        tmp = d_info["techinfo"]["filename"]
        if "Vmax" in tmp and not "_f" in tmp:
            return "Hys"
        if not "Vmax" in tmp and "_f" in tmp:
            return "MFCV"
    return "TechniqueNotKnown"

Regarding the debugging information I'm using Python 3.7.4 in a venv because part of the project has a dependency on PySide2.


Solution

  • The input() function within the GetMeDopingType() function seems to cause conflicts with the event loop. When the script is running normally, the event loop is already active, so using input() inside it triggers the error.

    However, when you add a breakpoint on sys.exit(app.exec_()) and click "resume" after selecting the data, the event loop hasn't started yet when you reach the input() call. That's why it works without any errors in that case.

    A solution can be to modify your code to avoid using input() within the event loop. Try, for example, to use a separate dialog or input box to gather user input instead of relying on console input.

    from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton, QApplication
    
    # ...
    
    def GetMeDopingType(wafername, techinfo):
        global GLOBAL_d_Dopingtypes
    
        if "SiP" in wafername:
            return "p"
    
        if not wafername in GLOBAL_d_Dopingtypes:
            if techinfo is not None and "doping" in techinfo:
                GLOBAL_d_Dopingtypes[wafername] = techinfo["doping"].strip()[0].lower()
            else:
                app = QApplication.instance()
                dialog = QDialog()
                layout = QVBoxLayout()
                label = QLabel("Please provide doping type for wafer: " + wafername + " [n/p]:")
                input_box = QLineEdit()
                layout.addWidget(label)
                layout.addWidget(input_box)
    
                ok_button = QPushButton("OK")
                ok_button.clicked.connect(dialog.accept)
                cancel_button = QPushButton("Cancel")
                cancel_button.clicked.connect(app.exit)  # Abort the processing
    
                layout.addWidget(ok_button)
                layout.addWidget(cancel_button)
                dialog.setLayout(layout)
                dialog.setWindowTitle("Doping Type Input")
    
                if dialog.exec_() == QDialog.Accepted:
                    inp = input_box.text()
                    if inp.strip()[0].upper() == "C":
                        C_IO.Abort()
                    GLOBAL_d_Dopingtypes[wafername] = inp.strip()[0].lower()
                else:
                    C_IO.Abort()
    
        return GLOBAL_d_Dopingtypes[wafername]