Continued with the multiprocess debugger. multi_processing

Tue, 11 Feb 2020 18:58:38 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 11 Feb 2020 18:58:38 +0100
branch
multi_processing
changeset 7409
1413bfe73d41
parent 7408
0d58e708f57b
child 7410
401791e6f50f

Continued with the multiprocess debugger.

eric6/DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/ModuleLoader.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/QProcessExtension.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugServer.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugUI.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
eric6/Debugger/StartDebugDialog.ui file | annotate | diff | comparison | revisions
eric6/Debugger/StartDialog.py file | annotate | diff | comparison | revisions
--- a/eric6/DebugClients/Python/DebugClientBase.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/DebugClients/Python/DebugClientBase.py	Tue Feb 11 18:58:38 2020 +0100
@@ -231,6 +231,7 @@
         self.running = None
         self.test = None
         self.debugging = False
+        self.multiprocessSupport = False
         
         self.fork_auto = False
         self.fork_child = False
@@ -327,6 +328,7 @@
             pass
 
         self.debugging = False
+        self.multiprocessSupport = False
         
         # make sure we close down our end of the socket
         # might be overkill as normally stdin, stdout and stderr
@@ -489,6 +491,7 @@
             
             self.running = sys.argv[0]
             self.debugging = True
+            self.multiprocessSupport = params["multiprocess"]
             
             self.fork_auto = params["autofork"]
             self.fork_child = params["forkChild"]
@@ -1308,7 +1311,8 @@
         if self.errorstream in wrdy:
             self.writeReady(self.errorstream)
     
-    def connectDebugger(self, port, remoteAddress=None, redirect=True):
+    def connectDebugger(self, port, remoteAddress=None, redirect=True,
+                        name=""):
         """
         Public method to establish a session with the debugger.
         
@@ -1316,11 +1320,15 @@
         stdout and stderr and saves these file objects in case the application
         being debugged redirects them itself.
         
-        @param port the port number to connect to (int)
+        @param port the port number to connect to
+        @type int
         @param remoteAddress the network address of the debug server host
-            (string)
+        @type str
         @param redirect flag indicating redirection of stdin, stdout and
-            stderr (boolean)
+            stderr
+        @type bool
+        @param name name to be attached to the debugger ID
+        @type str
         """
         if remoteAddress is None:
             remoteAddress = "127.0.0.1"
@@ -1328,7 +1336,11 @@
             remoteAddress = remoteAddress.split("@@i")[0]
         sock = socket.create_connection((remoteAddress, port))
         
-        self.__debuggerId = "{0}-{1}".format(socket.gethostname(), os.getpid())
+        if not name:
+            name = "main"
+        self.__debuggerId = "{0}-{1}-{2}".format(
+            socket.gethostname(), os.getpid(), name
+        )
         
         name = sys.stdin.name
         # Special case if in a multiprocessing.Process
@@ -2010,10 +2022,10 @@
                 comp = completer(text, state)
             except Exception:
                 comp = None
-
+    
     def startDebugger(self, filename=None, host=None, port=None,
                       enableTrace=True, exceptions=True, tracePython=False,
-                      redirect=True, passive=True):
+                      redirect=True, passive=True, multiprocessSupport=False):
         """
         Public method used to start the remote debugger.
         
@@ -2034,6 +2046,9 @@
         @type bool
         @param passive flag indicating a passive debugging session
         @type bool
+        @param multiprocessSupport flag indicating to enable multiprocess
+            debugging support
+        @type bool
         """
         if host is None:
             host = os.getenv('ERICHOST', 'localhost')
@@ -2041,7 +2056,11 @@
             port = os.getenv('ERICPORT', 42424)
         
         remoteAddress = self.__resolveHost(host)
-        self.connectDebugger(port, remoteAddress, redirect)
+        if filename is not None:
+            name = os.path.basename(filename)
+        else:
+            name = ""
+        self.connectDebugger(port, remoteAddress, redirect, name=name)
         if filename is not None:
             self.running = os.path.abspath(filename)
         else:
@@ -2075,7 +2094,8 @@
     
     def startProgInDebugger(self, progargs, wd='', host=None,
                             port=None, exceptions=True, tracePython=False,
-                            redirect=True, passive=True):
+                            redirect=True, passive=True,
+                            multiprocessSupport=False):
         """
         Public method used to start the remote debugger.
         
@@ -2092,6 +2112,9 @@
             stderr (boolean)
         @param passive flag indicating a passive debugging session
         @type bool
+        @param multiprocessSupport flag indicating to enable multiprocess
+            debugging support
+        @type bool
         """
         if host is None:
             host = os.getenv('ERICHOST', 'localhost')
@@ -2099,7 +2122,8 @@
             port = os.getenv('ERICPORT', 42424)
         
         remoteAddress = self.__resolveHost(host)
-        self.connectDebugger(port, remoteAddress, redirect)
+        name = os.path.basename(progargs[0])
+        self.connectDebugger(port, remoteAddress, redirect, name=name)
         
         self._fncache = {}
         self.dircache = []
@@ -2113,6 +2137,7 @@
         self.running = sys.argv[0]
         self.__setCoding(self.running)
         self.debugging = True
+        self.multiprocessSupport = multiprocessSupport
         
         self.passive = passive
         if passive:
@@ -2191,6 +2216,7 @@
             exceptions = True
             redirect = True
             passive = True
+            multiprocess = False
             while args[0]:
                 if args[0] == '-h':
                     host = args[1]
@@ -2227,6 +2253,9 @@
                 elif args[0] == '--no-passive':
                     passive = False
                     del args[0]
+                elif args[0] == '--multiprocess':
+                    multiprocess = True
+                    del args[0]
                 elif args[0] == '--':
                     del args[0]
                     break
@@ -2251,7 +2280,8 @@
                                          exceptions=exceptions,
                                          tracePython=tracePython,
                                          redirect=redirect,
-                                         passive=passive)
+                                         passive=passive,
+                                         multiprocessSupport=multiprocess)
         else:
             if sys.argv[1] == '--no-encoding':
                 self.noencoding = True
--- a/eric6/DebugClients/Python/ModuleLoader.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/DebugClients/Python/ModuleLoader.py	Tue Feb 11 18:58:38 2020 +0100
@@ -86,7 +86,7 @@
         ):
             module.eric6_patched = True
             self.__dbgClient.patchQThread(module)
-            patchQProcess(module, self.__dbgClient.startOptions)
+            patchQProcess(module, self.__dbgClient)
         
         self.__enableImportHooks = True
         return module
--- a/eric6/DebugClients/Python/QProcessExtension.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/DebugClients/Python/QProcessExtension.py	Tue Feb 11 18:58:38 2020 +0100
@@ -10,19 +10,19 @@
 
 import os
 
-_startOptions = []
+_debugClient = None
 
 
-def patchQProcess(module, startOptions):
+def patchQProcess(module, debugClient):
     """
     Function to patch the QtCore module's QProcess.
     
     @param module reference to the imported module to be patched
     @type module
-    @param startOptions reference to the saved start options
-    @type tuple
+    @param debugClient reference to the debug client object
+    @type DebugClient
     """     # __IGNORE_WARNING_D234__
-    global _startOptions
+    global _debugClient
     
     class QProcessWrapper(module.QProcess):
         """
@@ -34,7 +34,7 @@
             """
             super(QProcessWrapper, self).__init__(parent)
         
-        def __modifyArgs(self, arguments):
+        def __modifyArgs(self, arguments, multiprocessSupport):
             """
             Private method to modify the arguments given to the start method.
             
@@ -44,12 +44,13 @@
             @rtype list of str
             """
             (wd, host, port, exceptions, tracePython, redirect,
-             noencoding) = _startOptions[:7]
+             noencoding) = _debugClient.startOptions[:7]
+            
             modifiedArguments = [
                 os.path.join(os.path.dirname(__file__), "DebugClient.py"),
                 "-h", host,
                 "-p", str(port),
-                "--no-passive"
+                "--no-passive",
             ]
             
             if wd:
@@ -62,6 +63,8 @@
                 modifiedArguments.append("-n")
             if noencoding:
                 modifiedArguments.append("--no-encoding")
+            if multiprocessSupport:
+                modifiedArguments.append("--multiprocess")
             modifiedArguments.append("--")
             # end the arguments for DebugClient
             modifiedArguments.extend(arguments)
@@ -82,9 +85,11 @@
             @type dict
             """
             if (
-                (len(args) >= 2 and isinstance(args[1], list)) or
-                (len(args) == 1 and not isinstance(args[0], str)) or
-                len(args) == 0
+                _debugClient.debugging and
+                _debugClient.multiprocessSupport and
+                ((len(args) >= 2 and isinstance(args[1], list)) or
+                 (len(args) == 1 and not isinstance(args[0], str)) or
+                 len(args) == 0)
             ):
                 # TODO: implement this
                 if len(args) >= 2:
@@ -101,14 +106,16 @@
                         mode = args[0]
                     else:
                         mode = module.QIODevice.ReadWrite
-                if "python" in program.lower():
+                if "python" in program.lower() and arguments[0] != "-m":
                     # assume a Python script is to be started
-                    newArgs = self.__modifyArgs(arguments)
+                    # '-m' option to python is not (yet) supported
+                    newArgs = self.__modifyArgs(
+                        arguments, _debugClient.multiprocessSupport)
                     super(QProcessWrapper, self).start(program, newArgs, mode)
                 else:
                     super(QProcessWrapper, self).start(*args, **kwargs)
             else:
                 super(QProcessWrapper, self).start(*args, **kwargs)
     
-    _startOptions = startOptions[:]
+    _debugClient = debugClient
     module.QProcess = QProcessWrapper
--- a/eric6/Debugger/DebugServer.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/Debugger/DebugServer.py	Tue Feb 11 18:58:38 2020 +0100
@@ -926,7 +926,8 @@
     def remoteLoad(self, venvName, fn, argv, wd, env, autoClearShell=True,
                    tracePython=False, autoContinue=True, forProject=False,
                    runInConsole=False, autoFork=False, forkChild=False,
-                   clientType="", enableCallTrace=False):
+                   clientType="", enableCallTrace=False,
+                   enableMultiprocess=False):
         """
         Public method to load a new program to debug.
         
@@ -963,6 +964,9 @@
         @keyparam enableCallTrace flag indicating to enable the call trace
             function
         @type bool
+        @param enableMultiprocess flag indicating to perform multiprocess
+            debugging
+        @type bool
         """
         self.__autoClearShell = autoClearShell
         
@@ -992,8 +996,10 @@
         self.setCallTraceEnabled("", enableCallTrace)
         self.remoteEnvironment(env)
         
-        self.debuggerInterface.remoteLoad(fn, argv, wd, tracePython,
-                                          autoContinue, autoFork, forkChild)
+        self.debuggerInterface.remoteLoad(
+            fn, argv, wd, tracePython, autoContinue, autoFork, forkChild,
+            enableMultiprocess=enableMultiprocess
+        )
         self.debugging = True
         self.running = True
         self.__restoreBreakpoints()
--- a/eric6/Debugger/DebugUI.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/Debugger/DebugUI.py	Tue Feb 11 18:58:38 2020 +0100
@@ -108,6 +108,9 @@
                 'DebugInfo/ForkAutomatically', False))
         self.forkIntoChild = Preferences.toBool(
             Preferences.Prefs.settings.value('DebugInfo/ForkIntoChild', False))
+        self.enableMultiprocess = Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                'DebugInfo/EnableMultiprocess', False))
         
         self.lastDebuggedFile = None
         self.lastStartAction = 0    # 0=None, 1=Script, 2=Project
@@ -956,6 +959,8 @@
             'DebugInfo/ForkAutomatically', self.forkAutomatically)
         Preferences.Prefs.settings.setValue(
             'DebugInfo/ForkIntoChild', self.forkIntoChild)
+        Preferences.Prefs.settings.setValue(
+            'DebugInfo/EnableMultiprocess', self.enableMultiprocess)
         
     def shutdownServer(self):
         """
@@ -2029,12 +2034,13 @@
             self.envHistory, self.exceptions, self.ui, 0,
             tracePython=self.tracePython, autoClearShell=self.autoClearShell,
             autoContinue=self.autoContinue, autoFork=self.forkAutomatically,
-            forkChild=self.forkIntoChild)
+            forkChild=self.forkIntoChild,
+            enableMultiprocess=self.enableMultiprocess)
         if dlg.exec_() == QDialog.Accepted:
             (lastUsedVenvName, argv, wd, env, exceptions, clearShell,
              console) = dlg.getData()
-            tracePython, autoContinue, forkAutomatically, forkIntoChild = (
-                dlg.getDebugData())
+            (tracePython, autoContinue, forkAutomatically, forkIntoChild,
+             enableMultiprocess) = dlg.getDebugData()
             
             if debugProject:
                 fn = self.project.getMainScript(True)
@@ -2109,6 +2115,9 @@
             self.forkAutomatically = forkAutomatically
             self.forkIntoChild = forkIntoChild
             
+            # Save the multiprocess debugging flag
+            self.enableMultiprocess = enableMultiprocess
+            
             # Hide all error highlights
             self.viewmanager.unhighlight()
             
@@ -2134,7 +2143,8 @@
                     autoContinue=autoContinue, forProject=debugProject,
                     runInConsole=console, autoFork=forkAutomatically,
                     forkChild=forkIntoChild, clientType=self.clientType,
-                    enableCallTrace=enableCallTrace)
+                    enableCallTrace=enableCallTrace,
+                    enableMultiprocess=enableMultiprocess)
                 
                 if (
                     self.debugServer.isClientProcessUp() and
@@ -2219,7 +2229,8 @@
                     autoFork=self.forkAutomatically,
                     forkChild=self.forkIntoChild,
                     clientType=self.clientType,
-                    enableCallTrace=enableCallTrace)
+                    enableCallTrace=enableCallTrace,
+                    enableMultiprocess=self.enableMultiprocess)
                 
                 # Signal that we have started a debugging session
                 self.debuggingStarted.emit(fn)
--- a/eric6/Debugger/DebuggerInterfacePython.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfacePython.py	Tue Feb 11 18:58:38 2020 +0100
@@ -547,6 +547,20 @@
                 self.debugServer.masterClientConnected()
             
             self.debugServer.initializeClient(debuggerId)
+            
+            # perform auto-continue except for master
+            if (
+                self.__autoContinue and
+                debuggerId != self.__master and
+                debuggerId not in self.__autoContinued
+            ):
+                self.__autoContinued.append(debuggerId)
+                QTimer.singleShot(
+                    0, lambda: self.remoteContinue(debuggerId))
+                # TODO: maybe the debugger ID should not be registered here but
+                # only in user_line. Seems the debugger stops in a frame
+                # outside of the debugged script and this continue moves it to
+                # the first line of the script.
     
     def __socketDisconnected(self, sock):
         """
@@ -560,6 +574,8 @@
                 del self.__connections[debuggerId]
                 if debuggerId == self.__master:
                     self.__master = None
+                if debuggerId in self.__autoContinued:
+                    self.__autoContinued.remove(debuggerId)
                 break
         else:
             if sock in self.__pendingConnections:
@@ -568,6 +584,7 @@
         if not self.__connections:
             # no active connections anymore
             self.debugServer.signalLastClientExited()
+            self.__autoContinued.clear()
     
     def getDebuggerIds(self):
         """
@@ -653,7 +670,8 @@
                                    self.__master)
     
     def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
-                   autoContinue=True, autoFork=False, forkChild=False):
+                   autoContinue=True, autoFork=False, forkChild=False,
+                   enableMultiprocess=False):
         """
         Public method to load a new program to debug.
         
@@ -673,6 +691,9 @@
         @type bool
         @param forkChild flag indicating to debug the child after forking
         @type bool
+        @param enableMultiprocess flag indicating to perform multiprocess
+            debugging
+        @type bool
         """
         self.__autoContinue = autoContinue
         self.__scriptName = os.path.abspath(fn)
@@ -686,6 +707,7 @@
             "traceInterpreter": traceInterpreter,
             "autofork": autoFork,
             "forkChild": forkChild,
+            "multiprocess": enableMultiprocess,
         }, self.__master)
     
     def remoteRun(self, fn, argv, wd, autoFork=False, forkChild=False):
--- a/eric6/Debugger/StartDebugDialog.ui	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/Debugger/StartDebugDialog.ui	Tue Feb 11 18:58:38 2020 +0100
@@ -157,7 +157,7 @@
     </layout>
    </item>
    <item>
-    <layout class="QGridLayout" name="gridLayout_2">
+    <layout class="QGridLayout" name="gridLayout">
      <item row="0" column="0">
       <widget class="QCheckBox" name="exceptionCheckBox">
        <property name="toolTip">
@@ -237,6 +237,19 @@
        </property>
       </widget>
      </item>
+     <item row="3" column="0">
+      <widget class="QCheckBox" name="multiprocessEnableCheckBox">
+       <property name="toolTip">
+        <string>Select this enable multi process debugging.</string>
+       </property>
+       <property name="whatsThis">
+        <string>&lt;b&gt;Enable Multi Process Debugging&lt;/b&gt;&lt;p&gt;This enables debugging capability for multi process programs. Each started Python program is started within a new debugger.&lt;/p&gt;</string>
+       </property>
+       <property name="text">
+        <string>Enable Multi Process Debugging</string>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
@@ -310,6 +323,7 @@
   <tabstop>consoleCheckBox</tabstop>
   <tabstop>tracePythonCheckBox</tabstop>
   <tabstop>autoContinueCheckBox</tabstop>
+  <tabstop>multiprocessEnableCheckBox</tabstop>
   <tabstop>forkModeCheckBox</tabstop>
   <tabstop>forkChildCheckBox</tabstop>
  </tabstops>
--- a/eric6/Debugger/StartDialog.py	Mon Feb 10 19:49:45 2020 +0100
+++ b/eric6/Debugger/StartDialog.py	Tue Feb 11 18:58:38 2020 +0100
@@ -29,7 +29,7 @@
                  exceptions,
                  parent=None, dialogType=0, modfuncList=None,
                  tracePython=False, autoClearShell=True, autoContinue=True,
-                 autoFork=False, forkChild=False):
+                 autoFork=False, forkChild=False, enableMultiprocess=False):
         """
         Constructor
         
@@ -56,20 +56,23 @@
                 <li>3 = start profile dialog</li>
                 </ul>
         @type int (0 to 3)
-        @keyparam modfuncList history list of module functions
+        @param modfuncList history list of module functions
         @type list of str
-        @keyparam tracePython flag indicating if the Python library should
+        @param tracePython flag indicating if the Python library should
             be traced as well
         @type bool
-        @keyparam autoClearShell flag indicating, that the interpreter window
+        @param autoClearShell flag indicating, that the interpreter window
             should be cleared automatically
         @type bool
-        @keyparam autoContinue flag indicating, that the debugger should not
+        @param autoContinue flag indicating, that the debugger should not
             stop at the first executable line
         @type bool
-        @keyparam autoFork flag indicating the automatic fork mode
+        @param autoFork flag indicating the automatic fork mode
         @type bool
-        @keyparam forkChild flag indicating to debug the child after forking
+        @param forkChild flag indicating to debug the child after forking
+        @type bool
+        @param enableMultiprocess flag indicating the support for multi process
+            debugging
         @type bool
         """
         super(StartDialog, self).__init__(parent)
@@ -132,6 +135,7 @@
             self.ui.autoContinueCheckBox.setChecked(autoContinue)
             self.ui.forkModeCheckBox.setChecked(autoFork)
             self.ui.forkChildCheckBox.setChecked(forkChild)
+            self.ui.multiprocessEnableCheckBox.setChecked(enableMultiprocess)
         
         if dialogType == 1:       # start run dialog
             self.ui.forkModeCheckBox.setChecked(autoFork)
@@ -191,9 +195,10 @@
             return (self.ui.tracePythonCheckBox.isChecked(),
                     self.ui.autoContinueCheckBox.isChecked(),
                     self.ui.forkModeCheckBox.isChecked(),
-                    self.ui.forkChildCheckBox.isChecked())
+                    self.ui.forkChildCheckBox.isChecked(),
+                    self.ui.multiprocessEnableCheckBox.isChecked())
         else:
-            return (False, False, False, False)
+            return (False, False, False, False, False)
         
     def getRunData(self):
         """

eric ide

mercurial