Continued with the multiprocess debugger. multi_processing

Sat, 01 Feb 2020 19:48:21 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 01 Feb 2020 19:48:21 +0100
branch
multi_processing
changeset 7379
72a72fd56494
parent 7377
cc920e534ac0
child 7386
3de001de249c

Continued with the multiprocess debugger.

eric6/Debugger/CallTraceViewer.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/DebugViewer.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
eric6/Debugger/ExceptionLogger.py file | annotate | diff | comparison | revisions
eric6/Debugger/VariablesViewer.py file | annotate | diff | comparison | revisions
eric6/QScintilla/Shell.py file | annotate | diff | comparison | revisions
--- a/eric6/Debugger/CallTraceViewer.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/CallTraceViewer.py	Sat Feb 01 19:48:21 2020 +0100
@@ -30,17 +30,22 @@
     """
     sourceFile = pyqtSignal(str, int)
     
-    def __init__(self, debugServer, parent=None):
+    def __init__(self, debugServer, debugViewer, parent=None):
         """
         Constructor
         
-        @param debugServer reference to the debug server object (DebugServer)
-        @param parent reference to the parent widget (QWidget)
+        @param debugServer reference to the debug server object
+        @type DebugServer
+        @param debugViewer reference to the debug viewer object
+        @type DebugViewer
+        @param parent reference to the parent widget
+        @type QWidget
         """
         super(CallTraceViewer, self).__init__(parent)
         self.setupUi(self)
         
         self.__dbs = debugServer
+        self.__debugViewer = debugViewer
         
         self.startTraceButton.setIcon(
             UI.PixmapCache.getIcon("callTraceStart.png"))
@@ -62,6 +67,7 @@
         
         self.__projectMode = False
         self.__project = None
+        self.__tracedDebuggerId = ""
         
         stopOnExit = Preferences.toBool(
             Preferences.Prefs.settings.value("CallTrace/StopOnExit", True))
@@ -83,9 +89,14 @@
         """
         Private slot to set the call trace enabled status.
         
-        @param enabled flag indicating the new state (boolean)
+        @param enabled flag indicating the new state
+        @type bool
         """
-        self.__dbs.setCallTraceEnabled(enabled)
+        if enabled:
+            self.__tracedDebuggerId = (
+                self.__debugViewer.getSelectedDebuggerId()
+            )
+        self.__dbs.setCallTraceEnabled(self.__tracedDebuggerId, enabled)
         self.stopTraceButton.setEnabled(enabled)
         self.startTraceButton.setEnabled(not enabled)
         self.__callTraceEnabled = enabled
@@ -192,8 +203,10 @@
         """
         Private slot to open the double clicked file in an editor.
         
-        @param item reference to the double clicked item (QTreeWidgetItem)
-        @param column column that was double clicked (integer)
+        @param item reference to the double clicked item
+        @type QTreeWidgetItem
+        @param column column that was double clicked
+        @type int
         """
         if item is not None and column > 0:
             columnStr = item.text(column)
@@ -222,63 +235,86 @@
         In project mode the call trace info is shown with project relative
         path names.
         
-        @param enabled flag indicating to enable the project mode (boolean)
+        @param enabled flag indicating to enable the project mode
+        @type bool
         """
         self.__projectMode = enabled
         if enabled and self.__project is None:
             self.__project = e5App().getObject("Project")
     
     def __addCallTraceInfo(self, isCall, fromFile, fromLine, fromFunction,
-                           toFile, toLine, toFunction):
+                           toFile, toLine, toFunction, debuggerId):
         """
         Private method to add an entry to the call trace viewer.
         
-        @param isCall flag indicating a 'call' (boolean)
-        @param fromFile name of the originating file (string)
-        @param fromLine line number in the originating file (string)
-        @param fromFunction name of the originating function (string)
-        @param toFile name of the target file (string)
-        @param toLine line number in the target file (string)
-        @param toFunction name of the target function (string)
+        @param isCall flag indicating a 'call'
+        @type bool
+        @param fromFile name of the originating file
+        @type str
+        @param fromLine line number in the originating file
+        @type str
+        @param fromFunction name of the originating function
+        @type str
+        @param toFile name of the target file
+        @type str
+        @param toLine line number in the target file
+        @type str
+        @param toFunction name of the target function
+        @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        if isCall:
-            icon = UI.PixmapCache.getIcon("forward.png")
-        else:
-            icon = UI.PixmapCache.getIcon("back.png")
-        parentItem = (
-            self.__callStack[-1] if self.__callStack else self.callTrace)
-        
-        if self.__projectMode:
-            fromFile = self.__project.getRelativePath(fromFile)
-            toFile = self.__project.getRelativePath(toFile)
-        
-        itm = QTreeWidgetItem(
-            parentItem,
-            ["",
-             self.__entryFormat.format(fromFile, fromLine, fromFunction),
-             self.__entryFormat.format(toFile, toLine, toFunction)])
-        itm.setIcon(0, icon)
-        itm.setData(0, Qt.UserRole, isCall)
-        itm.setExpanded(True)
-        
-        if isCall:
-            self.__callStack.append(itm)
-        else:
-            if self.__callStack:
-                self.__callStack.pop(-1)
+        if debuggerId == self.__tracedDebuggerId:
+            if isCall:
+                icon = UI.PixmapCache.getIcon("forward.png")
+            else:
+                icon = UI.PixmapCache.getIcon("back.png")
+            parentItem = (
+                self.__callStack[-1] if self.__callStack else self.callTrace)
+            
+            if self.__projectMode:
+                fromFile = self.__project.getRelativePath(fromFile)
+                toFile = self.__project.getRelativePath(toFile)
+            
+            itm = QTreeWidgetItem(
+                parentItem,
+                ["",
+                 self.__entryFormat.format(fromFile, fromLine, fromFunction),
+                 self.__entryFormat.format(toFile, toLine, toFunction)])
+            itm.setIcon(0, icon)
+            itm.setData(0, Qt.UserRole, isCall)
+            itm.setExpanded(True)
+            
+            if isCall:
+                self.__callStack.append(itm)
+            else:
+                if self.__callStack:
+                    self.__callStack.pop(-1)
     
     def isCallTraceEnabled(self):
         """
         Public method to get the state of the call trace function.
         
-        @return flag indicating the state of the call trace function (boolean)
+        @return flag indicating the state of the call trace function
+        @rtype bool
         """
         return self.__callTraceEnabled
     
-    @pyqtSlot()
-    def __clientExit(self):
+    @pyqtSlot(int, str, bool, str)
+    def __clientExit(self, status, message, quiet, debuggerId):
         """
-        Private slot handling a client exiting.
+        Private slot to handle a debug client terminating.
+        
+        @param status exit code of the debugged program
+        @type int
+        @param message exit message of the debugged program
+        @type str
+        @param quiet flag indicating to suppress exit info display
+        @type bool
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        if self.stopCheckBox.isChecked():
-            self.__setCallTraceEnabled(False)
+        if debuggerId == self.__tracedDebuggerId:
+            if self.stopCheckBox.isChecked():
+                self.__setCallTraceEnabled(False)
+            self.__tracedDebuggerId = ""
--- a/eric6/Debugger/DebugServer.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/DebugServer.py	Sat Feb 01 19:48:21 2020 +0100
@@ -50,23 +50,28 @@
         executed a line of code
     @signal clientThreadList(currentId, threadList, debuggerId) emitted after
         a thread list has been received
-    @signal clientThreadSet() emitted after the client has acknowledged the
-        change of the current thread
-    @signal clientVariables(scope, variables) emitted after a variables dump
-        has been received
-    @signal clientVariable(scope, variables) emitted after a dump for one class
-        variable has been received
-    @signal clientStatement(bool) emitted after an interactive command has
-        been executed. The parameter is 0 to indicate that the command is
-        complete and 1 if it needs more input.
-    @signal clientException(exception) emitted after an exception occured on
-        the client side
-    @signal clientSyntaxError(exception) emitted after a syntax error has been
-        detected on the client side
-    @signal clientSignal(signal) emitted after a signal has been generated on
-        the client side
-    @signal clientExit(int, str, bool) emitted after the client has exited
-        giving the exit status, an exit message and an indication to be quiet
+    @signal clientThreadSet(debuggerId) emitted after the client has
+        acknowledged the change of the current thread
+    @signal clientVariables(scope, variables, debuggerId) emitted after a
+        variables dump has been received
+    @signal clientVariable(scope, variables, debuggerId) emitted after a dump
+        for one class variable has been received
+    @signal clientStatement(continue, debuggerId) emitted after an interactive
+        command has been executed. The parameter is False to indicate that the
+        command is complete and True if it needs more input.
+    @signal clientException(exceptionType, exceptionMessage, stackTrace,
+        debuggerId) emitted after an exception occured on the client side
+    @signal clientSyntaxError(message, filename, linenumber, characternumber,
+        debuggerId) emitted after a syntax error has been detected on the
+        client side
+    @signal clientSignal(message, filename, linenumber, function name,
+        function arguments, debuggerId) emitted after a signal has been
+        generated on the client side
+    @signal clientExit(int, str, bool, str) emitted after the client has exited
+        giving the exit status, an exit message, an indication to be quiet and
+        the ID of the exited client
+    @signal lastClientExited() emitted to indicate that the last connected
+        debug client has terminated
     @signal clientClearBreak(filename, lineno) emitted after the debug client
         has decided to clear a temporary breakpoint
     @signal clientBreakConditionError(fn, lineno) emitted after the client has
@@ -111,7 +116,7 @@
         reported an unexpected test success
     @signal callTraceInfo emitted after the client reported the call trace
         data (isCall, fromFile, fromLine, fromFunction, toFile, toLine,
-        toFunction)
+        toFunction, debuggerId)
     @signal appendStdout(msg) emitted when a passive debug connection is
         established or lost
     @signal clientDebuggerIds(debuggerIds) emitted to give the list of IDs of
@@ -127,14 +132,15 @@
     clientLine = pyqtSignal(str, int, str, bool)
     clientStack = pyqtSignal(list, str)
     clientThreadList = pyqtSignal(int, list, str)
-    clientThreadSet = pyqtSignal()
-    clientVariables = pyqtSignal(int, list)
-    clientVariable = pyqtSignal(int, list)
-    clientStatement = pyqtSignal(bool)
-    clientException = pyqtSignal(str, str, list)
-    clientSyntaxError = pyqtSignal(str, str, int, int)
-    clientSignal = pyqtSignal(str, str, int, str, str)
-    clientExit = pyqtSignal(int, str, bool)
+    clientThreadSet = pyqtSignal(str)
+    clientVariables = pyqtSignal(int, list, str)
+    clientVariable = pyqtSignal(int, list, str)
+    clientStatement = pyqtSignal(bool, str)
+    clientException = pyqtSignal(str, str, list, str)
+    clientSyntaxError = pyqtSignal(str, str, int, int, str)
+    clientSignal = pyqtSignal(str, str, int, str, str, str)
+    clientExit = pyqtSignal(int, str, bool, str)
+    lastClientExited = pyqtSignal()
     clientBreakConditionError = pyqtSignal(str, int)
     clientWatchConditionError = pyqtSignal(str)
     clientRawInput = pyqtSignal(str, bool)
@@ -154,7 +160,7 @@
     utTestSucceededUnexpected = pyqtSignal(str, str)
     utFinished = pyqtSignal()
     passiveDebugStarted = pyqtSignal(str, bool)
-    callTraceInfo = pyqtSignal(bool, str, str, str, str, str, str)
+    callTraceInfo = pyqtSignal(bool, str, str, str, str, str, str, str)
     appendStdout = pyqtSignal(str)
     
     def __init__(self, originalPathString, preventPassiveDebugging=False):
@@ -531,8 +537,6 @@
                 elif self.__autoClearShell:
                     self.__autoClearShell = False
                     self.remoteBanner()
-##                self.remoteClientVariables(0, [], 0)
-##                self.remoteClientVariables(1, [], 0)
             else:
                 if clType and self.lastClientType:
                     self.__setClientType(self.lastClientType)
@@ -586,7 +590,8 @@
                 index = self.breakpointModel.index(row, 0, parentIndex)
                 fn, lineno = (
                     self.breakpointModel.getBreakPointByIndex(index)[0:2])
-                self.remoteBreakpoint(fn, lineno, False)
+                # delete the breakpoints of all connected backends
+                self.remoteBreakpoint("", fn, lineno, False)
 
     def __changeBreakPoints(self, startIndex, endIndex):
         """
@@ -611,24 +616,31 @@
             self.__deleteBreakPoints(
                 QModelIndex(), startIndex.row(), endIndex.row())
         
-    def __addBreakPoints(self, parentIndex, start, end):
+    def __addBreakPoints(self, parentIndex, start, end, debuggerId=""):
         """
         Private slot to add breakpoints.
         
-        @param parentIndex index of parent item (QModelIndex)
-        @param start start row (integer)
-        @param end end row (integer)
+        @param parentIndex index of parent item
+        @type QModelIndex
+        @param start start row
+        @type int
+        @param end end row
+        @type int
+        @param debuggerId ID of the debugger backend to send to. If this is
+            empty, they will be broadcast to all connected backends.
+        @type str
         """
         if self.debugging:
             for row in range(start, end + 1):
                 index = self.breakpointModel.index(row, 0, parentIndex)
                 fn, line, cond, temp, enabled, ignorecount = (
                     self.breakpointModel.getBreakPointByIndex(index)[:6])
-                self.remoteBreakpoint(fn, line, True, cond, temp)
+                self.remoteBreakpoint(debuggerId, fn, line, True, cond, temp)
                 if not enabled:
-                    self.__remoteBreakpointEnable(fn, line, False)
+                    self.__remoteBreakpointEnable(debuggerId, fn, line, False)
                 if ignorecount:
-                    self.__remoteBreakpointIgnore(fn, line, ignorecount)
+                    self.__remoteBreakpointIgnore(
+                        debuggerId, fn, line, ignorecount)
 
     def __makeWatchCondition(self, cond, special):
         """
@@ -682,9 +694,12 @@
         """
         Private slot to delete watch expressions.
         
-        @param parentIndex index of parent item (QModelIndex)
-        @param start start row (integer)
-        @param end end row (integer)
+        @param parentIndex index of parent item
+        @type QModelIndex
+        @param start start row
+        @type int
+        @param end end row
+        @type int
         """
         if self.debugging:
             for row in range(start, end + 1):
@@ -692,27 +707,35 @@
                 cond, special = (
                     self.watchpointModel.getWatchPointByIndex(index)[0:2])
                 cond = self.__makeWatchCondition(cond, special)
-                self.__remoteWatchpoint(cond, False)
+                self.__remoteWatchpoint("", cond, False)
         
     def __watchPointDataAboutToBeChanged(self, startIndex, endIndex):
         """
         Private slot to handle the dataAboutToBeChanged signal of the
         watch expression model.
         
-        @param startIndex start index of the rows to be changed (QModelIndex)
-        @param endIndex end index of the rows to be changed (QModelIndex)
+        @param startIndex start index of the rows to be changed
+        @type QModelIndex
+        @param endIndex end index of the rows to be changed
+        @type QModelIndex
         """
         if self.debugging:
             self.__deleteWatchPoints(
                 QModelIndex(), startIndex.row(), endIndex.row())
         
-    def __addWatchPoints(self, parentIndex, start, end):
+    def __addWatchPoints(self, parentIndex, start, end, debuggerId=""):
         """
         Private slot to set a watch expression.
         
-        @param parentIndex index of parent item (QModelIndex)
-        @param start start row (integer)
-        @param end end row (integer)
+        @param parentIndex index of parent item
+        @type QModelIndex
+        @param start start row
+        @type int
+        @param end end row
+        @type int
+        @param debuggerId ID of the debugger backend to send to. If this is
+            empty, they will be broadcast to all connected backends.
+        @type str
         """
         if self.debugging:
             for row in range(start, end + 1):
@@ -720,11 +743,12 @@
                 cond, special, temp, enabled, ignorecount = (
                     self.watchpointModel.getWatchPointByIndex(index)[:5])
                 cond = self.__makeWatchCondition(cond, special)
-                self.__remoteWatchpoint(cond, True, temp)
+                self.__remoteWatchpoint(debuggerId, cond, True, temp)
                 if not enabled:
-                    self.__remoteWatchpointEnable(cond, False)
+                    self.__remoteWatchpointEnable(debuggerId, cond, False)
                 if ignorecount:
-                    self.__remoteWatchpointIgnore(cond, ignorecount)
+                    self.__remoteWatchpointIgnore(debuggerId, cond,
+                                                  ignorecount)
         
     def __changeWatchPoints(self, startIndex, endIndex):
         """
@@ -803,19 +827,22 @@
             self.__createDebuggerInterface(
                 Preferences.getDebugger("PassiveDbgType"))
         
-        accepted = self.debuggerInterface.newConnection(sock)
-        if accepted:
-            # Perform actions necessary, if client type has changed
-            if self.lastClientType != self.clientType:
-                self.lastClientType = self.clientType
-                self.remoteBanner()
-            elif self.__autoClearShell:
-                self.__autoClearShell = False
-                self.remoteBanner()
-            elif self.passive:
-                self.remoteBanner()
-            
-            self.debuggerInterface.flush()
+        self.debuggerInterface.newConnection(sock)
+    
+    def masterClientConnected(self):
+        """
+        Public method to perform actions after the master client has finally
+        established the connection.
+        """
+        # Perform actions necessary, if client type has changed
+        if self.lastClientType != self.clientType:
+            self.lastClientType = self.clientType
+            self.remoteBanner()
+        elif self.__autoClearShell:
+            self.__autoClearShell = False
+            self.remoteBanner()
+        elif self.passive:
+            self.remoteBanner()
 
     def shutdownServer(self):
         """
@@ -912,7 +939,7 @@
         self.startClient(False, forProject=forProject,
                          runInConsole=runInConsole, venvName=venvName)
         
-        self.setCallTraceEnabled(enableCallTrace)
+        self.setCallTraceEnabled("", enableCallTrace)
         self.remoteEnvironment(env)
         
         self.debuggerInterface.remoteLoad(fn, argv, wd, tracePython,
@@ -1106,123 +1133,172 @@
         self.debugging = False
         self.running = True
 
-    def remoteStatement(self, stmt):
+    def remoteStatement(self, debuggerId, stmt):
         """
         Public method to execute a Python statement.
         
-        @param stmt the Python statement to execute (string). It
-              should not have a trailing newline.
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param stmt the Python statement to execute.
+        @type str
         """
-        self.debuggerInterface.remoteStatement(stmt)
+        self.debuggerInterface.remoteStatement(debuggerId, stmt.rstrip())
 
-    def remoteStep(self):
+    def remoteStep(self, debuggerId):
         """
         Public method to single step the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debuggerInterface.remoteStep()
+        self.debuggerInterface.remoteStep(debuggerId)
 
-    def remoteStepOver(self):
+    def remoteStepOver(self, debuggerId):
         """
         Public method to step over the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debuggerInterface.remoteStepOver()
+        self.debuggerInterface.remoteStepOver(debuggerId)
 
-    def remoteStepOut(self):
+    def remoteStepOut(self, debuggerId):
         """
         Public method to step out the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debuggerInterface.remoteStepOut()
+        self.debuggerInterface.remoteStepOut(debuggerId)
 
-    def remoteStepQuit(self):
+    def remoteStepQuit(self, debuggerId):
         """
         Public method to stop the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debuggerInterface.remoteStepQuit()
+        self.debuggerInterface.remoteStepQuit(debuggerId)
 
-    def remoteContinue(self, special=False):
+    def remoteContinue(self, debuggerId, special=False):
         """
         Public method to continue the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param special flag indicating a special continue operation
         """
-        self.debuggerInterface.remoteContinue(special)
+        self.debuggerInterface.remoteContinue(debuggerId, special)
 
-    def remoteMoveIP(self, line):
+    def remoteMoveIP(self, debuggerId, line):
         """
         Public method to move the instruction pointer to a different line.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param line the new line, where execution should be continued
+        @type int
         """
-        self.debuggerInterface.remoteMoveIP(line)
+        self.debuggerInterface.remoteMoveIP(debuggerId, line)
 
-    def remoteBreakpoint(self, fn, line, setBreakpoint, cond=None, temp=False):
+    def remoteBreakpoint(self, debuggerId, fn, line, setBreakpoint, cond=None,
+                         temp=False):
         """
         Public method to set or clear a breakpoint.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
         @param setBreakpoint flag indicating setting or resetting a breakpoint
-            (boolean)
-        @param cond condition of the breakpoint (string)
-        @param temp flag indicating a temporary breakpoint (boolean)
+        @type bool
+        @param cond condition of the breakpoint
+        @type str
+        @param temp flag indicating a temporary breakpoint
+        @type bool
         """
-        self.debuggerInterface.remoteBreakpoint(fn, line, setBreakpoint, cond,
-                                                temp)
+        self.debuggerInterface.remoteBreakpoint(
+            debuggerId, fn, line, setBreakpoint, cond, temp)
         
-    def __remoteBreakpointEnable(self, fn, line, enable):
+    def __remoteBreakpointEnable(self, debuggerId, fn, line, enable):
         """
         Private method to enable or disable a breakpoint.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
         @param enable flag indicating enabling or disabling a breakpoint
-            (boolean)
+        @type bool
         """
-        self.debuggerInterface.remoteBreakpointEnable(fn, line, enable)
+        self.debuggerInterface.remoteBreakpointEnable(
+            debuggerId, fn, line, enable)
         
-    def __remoteBreakpointIgnore(self, fn, line, count):
+    def __remoteBreakpointIgnore(self, debuggerId, fn, line, count):
         """
         Private method to ignore a breakpoint the next couple of occurrences.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
-        @param count number of occurrences to ignore (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
+        @param count number of occurrences to ignore
+        @type int
         """
-        self.debuggerInterface.remoteBreakpointIgnore(fn, line, count)
+        self.debuggerInterface.remoteBreakpointIgnore(
+            debuggerId, fn, line, count)
         
-    def __remoteWatchpoint(self, cond, setWatch, temp=False):
+    def __remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False):
         """
         Private method to set or clear a watch expression.
         
-        @param cond expression of the watch expression (string)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
         @param setWatch flag indicating setting or resetting a watch expression
-            (boolean)
-        @param temp flag indicating a temporary watch expression (boolean)
+        @type bool
+        @param temp flag indicating a temporary watch expression
+        @type bool
         """
         # cond is combination of cond and special (s. watch expression viewer)
-        self.debuggerInterface.remoteWatchpoint(cond, setWatch, temp)
+        self.debuggerInterface.remoteWatchpoint(debuggerId, cond, setWatch,
+                                                temp)
     
-    def __remoteWatchpointEnable(self, cond, enable):
+    def __remoteWatchpointEnable(self, debuggerId, cond, enable):
         """
         Private method to enable or disable a watch expression.
         
-        @param cond expression of the watch expression (string)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
         @param enable flag indicating enabling or disabling a watch expression
-            (boolean)
+        @type bool
         """
         # cond is combination of cond and special (s. watch expression viewer)
-        self.debuggerInterface.remoteWatchpointEnable(cond, enable)
+        self.debuggerInterface.remoteWatchpointEnable(debuggerId, cond, enable)
     
-    def __remoteWatchpointIgnore(self, cond, count):
+    def __remoteWatchpointIgnore(self, debuggerId, cond, count):
         """
         Private method to ignore a watch expression the next couple of
         occurrences.
         
-        @param cond expression of the watch expression (string)
-        @param count number of occurrences to ignore (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
+        @param count number of occurrences to ignore
+        @type int
         """
         # cond is combination of cond and special (s. watch expression viewer)
-        self.debuggerInterface.remoteWatchpointIgnore(cond, count)
+        self.debuggerInterface.remoteWatchpointIgnore(debuggerId, cond, count)
     
     def remoteRawInput(self, s):
         """
@@ -1233,64 +1309,74 @@
         self.debuggerInterface.remoteRawInput(s)
         self.clientRawInputSent.emit()
         
-    def remoteThreadList(self, debuggerId=""):
+    def remoteThreadList(self, debuggerId):
         """
         Public method to request the list of threads from the client.
         
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.debuggerInterface.remoteThreadList(debuggerId=debuggerId)
+        self.debuggerInterface.remoteThreadList(debuggerId)
         
-    def remoteSetThread(self, tid, debuggerId=""):
+    def remoteSetThread(self, debuggerId, tid):
         """
         Public method to request to set the given thread as current thread.
         
-        @param tid id of the thread
-        @type int
         @param debuggerId ID of the debugger backend
         @type str
+        @param tid id of the thread
+        @type int
         """
-        self.debuggerInterface.remoteSetThread(tid, debuggerId=debuggerId)
+        self.debuggerInterface.remoteSetThread(debuggerId, tid)
     
-    def remoteClientStack(self, debuggerId=""):
+    def remoteClientStack(self, debuggerId):
         """
         Public method to request the stack of the main thread.
         
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.debuggerInterface.remoteClientStack(debuggerId=debuggerId)
+        self.debuggerInterface.remoteClientStack(debuggerId)
     
-    def remoteClientVariables(self, scope, filterList, framenr=0,
-                              debuggerId=""):
+    def remoteClientVariables(self, debuggerId, scope, filterList, framenr=0):
         """
         Public method to request the variables of the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param scope the scope of the variables (0 = local, 1 = global)
         @type int
         @param filterList list of variable types to filter out
         @type list of int
         @param framenr framenumber of the variables to retrieve
         @type int
-        @param debuggerId ID of the debugger backend
-        @type str
         """
         self.debuggerInterface.remoteClientVariables(
-            scope, filterList, framenr, self.__maxVariableSize,
-            debuggerId=debuggerId)
+            debuggerId, scope, filterList, framenr, self.__maxVariableSize)
         
-    def remoteClientVariable(self, scope, filterList, var, framenr=0):
+    def remoteClientVariable(self, debuggerId, scope, filterList, var,
+                             framenr=0, maxSize=0):
         """
         Public method to request the variables of the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param scope the scope of the variables (0 = local, 1 = global)
-        @param filterList list of variable types to filter out (list of int)
-        @param var list encoded name of variable to retrieve (string)
-        @param framenr framenumber of the variables to retrieve (int)
+        @type int
+        @param filterList list of variable types to filter out
+        @type list of int
+        @param var list encoded name of variable to retrieve
+        @type list of str
+        @param framenr framenumber of the variables to retrieve
+        @type int
+        @param maxSize maximum size the formatted value of a variable will
+            be shown. If it is bigger than that, a 'too big' indication will
+            be given (@@TOO_BIG_TO_SHOW@@).
+        @type int
         """
         self.debuggerInterface.remoteClientVariable(
-            scope, filterList, var, framenr, self.__maxVariableSize)
+            debuggerId, scope, filterList, var, framenr,
+            self.__maxVariableSize)
         
     def remoteClientSetFilter(self, scope, filterStr):
         """
@@ -1302,13 +1388,16 @@
         """
         self.debuggerInterface.remoteClientSetFilter(scope, filterStr)
         
-    def setCallTraceEnabled(self, on):
+    def setCallTraceEnabled(self, debuggerId, on):
         """
         Public method to set the call trace state.
         
-        @param on flag indicating to enable the call trace function (boolean)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param on flag indicating to enable the call trace function
+        @type bool
         """
-        self.debuggerInterface.setCallTraceEnabled(on)
+        self.debuggerInterface.setCallTraceEnabled(debuggerId, on)
         
     def remoteBanner(self):
         """
@@ -1516,71 +1605,96 @@
         """
         self.clientThreadList.emit(currentId, threadList, debuggerId)
         
-    def signalClientThreadSet(self):
+    def signalClientThreadSet(self, debuggerId):
         """
         Public method to handle the change of the client thread.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.clientThreadSet.emit()
+        self.clientThreadSet.emit(debuggerId)
         
-    def signalClientVariables(self, scope, variables):
+    def signalClientVariables(self, scope, variables, debuggerId):
         """
         Public method to process the client variables info.
         
         @param scope scope of the variables (-1 = empty global, 1 = global,
             0 = local)
+        @type int
         @param variables the list of variables from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.clientVariables.emit(scope, variables)
+        self.clientVariables.emit(scope, variables, debuggerId)
         
-    def signalClientVariable(self, scope, variables):
+    def signalClientVariable(self, scope, variables, debuggerId):
         """
         Public method to process the client variable info.
         
         @param scope scope of the variables (-1 = empty global, 1 = global,
             0 = local)
+        @type int
         @param variables the list of members of a classvariable from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.clientVariable.emit(scope, variables)
+        self.clientVariable.emit(scope, variables, debuggerId)
         
-    def signalClientStatement(self, more):
+    def signalClientStatement(self, more, debuggerId):
         """
         Public method to process the input response from the client.
         
         @param more flag indicating that more user input is required
+        @type bool
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.clientStatement.emit(more)
+        self.clientStatement.emit(more, debuggerId)
         
     def signalClientException(self, exceptionType, exceptionMessage,
-                              stackTrace):
+                              stackTrace, debuggerId):
         """
         Public method to process the exception info from the client.
         
-        @param exceptionType type of exception raised (string)
-        @param exceptionMessage message given by the exception (string)
+        @param exceptionType type of exception raised
+        @type str
+        @param exceptionMessage message given by the exception
+        @type str
         @param stackTrace list of stack entries with the exception position
             first. Each stack entry is a list giving the filename and the
             linenumber.
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         if self.running:
             self.clientException.emit(exceptionType, exceptionMessage,
-                                      stackTrace)
+                                      stackTrace, debuggerId)
         
-    def signalClientSyntaxError(self, message, filename, lineNo, characterNo):
+    def signalClientSyntaxError(self, message, filename, lineNo, characterNo,
+                                debuggerId):
         """
-        Public method to process the syntax error info from the client.
+        Public method to process a syntax error info from the client.
         
-        @param message message of the syntax error (string)
+        @param message message of the syntax error
+        @type str
         @param filename translated filename of the syntax error position
-            (string)
-        @param lineNo line number of the syntax error position (integer)
+        @type str
+        @param lineNo line number of the syntax error position
+        @type int
         @param characterNo character number of the syntax error position
-            (integer)
+        @type int
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         if self.running:
-            self.clientSyntaxError.emit(message, filename, lineNo, characterNo)
+            self.clientSyntaxError.emit(message, filename, lineNo, characterNo,
+                                        debuggerId)
         
     def signalClientSignal(self, message, filename, lineNo,
-                           funcName, funcArgs):
+                           funcName, funcArgs, debuggerId):
         """
         Public method to process a signal generated on the client side.
         
@@ -1594,12 +1708,14 @@
         @type str
         @param funcArgs function arguments
         @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         if self.running:
             self.clientSignal.emit(message, filename, lineNo,
-                                   funcName, funcArgs)
+                                   funcName, funcArgs, debuggerId)
         
-    def signalClientExit(self, status, message=""):
+    def signalClientExit(self, status, message, debuggerId):
         """
         Public method to process the client exit status.
         
@@ -1607,10 +1723,18 @@
         @type int
         @param message message sent with the exit
         @type str
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        self.clientExit.emit(int(status), message, False, debuggerId)
+    
+    def signalLastClientExited(self):
+        """
+        Public method to process the last client exit event.
         """
         if self.passive:
             self.__passiveShutDown()
-        self.clientExit.emit(int(status), message, False)
+        self.lastClientExited.emit()
         if Preferences.getDebugger("AutomaticReset") or (self.running and
                                                          not self.debugging):
             self.debugging = False
@@ -1618,9 +1742,9 @@
         if self.passive:
             self.__createDebuggerInterface("None")
             self.signalClientOutput(self.tr('\nNot connected\n'))
-            self.signalClientStatement(False)
+            self.signalClientStatement(False, "")
         self.running = False
-        
+    
     def signalClientClearBreak(self, filename, lineno):
         """
         Public method to process the client clear breakpoint command.
@@ -1707,21 +1831,30 @@
         self.clientCompletionList.emit(completionList, text)
         
     def signalClientCallTrace(self, isCall, fromFile, fromLine, fromFunction,
-                              toFile, toLine, toFunction):
+                              toFile, toLine, toFunction, debuggerId):
         """
         Public method to process the client call trace data.
         
-        @param isCall flag indicating a 'call' (boolean)
-        @param fromFile name of the originating file (string)
-        @param fromLine line number in the originating file (string)
-        @param fromFunction name of the originating function (string)
-        @param toFile name of the target file (string)
-        @param toLine line number in the target file (string)
-        @param toFunction name of the target function (string)
+        @param isCall flag indicating a 'call'
+        @type bool
+        @param fromFile name of the originating file
+        @type str
+        @param fromLine line number in the originating file
+        @type str
+        @param fromFunction name of the originating function
+        @type str
+        @param toFile name of the target file
+        @type str
+        @param toLine line number in the target file
+        @type str
+        @param toFunction name of the target function
+        @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         self.callTraceInfo.emit(
             isCall, fromFile, fromLine, fromFunction,
-            toFile, toLine, toFunction)
+            toFile, toLine, toFunction, debuggerId)
     
     def clientUtDiscovered(self, testCases, exceptionType, exceptionValue):
         """
@@ -1819,7 +1952,7 @@
         """
         self.utFinished.emit()
         
-        self.clientExit.emit(int(status), "", True)
+        self.clientExit.emit(int(status), "", True, "")
         self.debugging = False
         self.running = False
         
@@ -1846,21 +1979,31 @@
         self.shutdownServer()
         self.appendStdout.emit(self.tr("Passive debug connection closed\n"))
         
-    def __restoreBreakpoints(self):
+    def __restoreBreakpoints(self, debuggerId=""):
         """
         Private method to restore the breakpoints after a restart.
+        
+        @param debuggerId ID of the debugger backend to send to. If this is
+            empty, they will be broadcast to all connected backends.
+        @type str
         """
         if self.debugging:
             self.__addBreakPoints(
-                QModelIndex(), 0, self.breakpointModel.rowCount() - 1)
+                QModelIndex(), 0, self.breakpointModel.rowCount() - 1,
+                debuggerId)
     
-    def __restoreWatchpoints(self):
+    def __restoreWatchpoints(self, debuggerId=""):
         """
         Private method to restore the watch expressions after a restart.
+        
+        @param debuggerId ID of the debugger backend to send to. If this is
+            empty, they will be broadcast to all connected backends.
+        @type str
         """
         if self.debugging:
             self.__addWatchPoints(
-                QModelIndex(), 0, self.watchpointModel.rowCount() - 1)
+                QModelIndex(), 0, self.watchpointModel.rowCount() - 1,
+                debuggerId)
     
     def getBreakPointModel(self):
         """
@@ -1927,3 +2070,13 @@
             return self.debuggerInterface.getDebuggerIds()
         else:
             return []
+    
+    def initializeClient(self, debuggerId):
+        """
+        Public method to initialize a freshly connected debug client.
+        
+        @param debuggerId ID of the connected debugger
+        @type str
+        """
+        self.__restoreBreakpoints(debuggerId)
+        self.__restoreWatchpoints(debuggerId)
--- a/eric6/Debugger/DebugUI.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/DebugUI.py	Sat Feb 01 19:48:21 2020 +0100
@@ -10,7 +10,7 @@
 
 import os
 
-from PyQt5.QtCore import pyqtSignal, QObject, Qt
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt
 from PyQt5.QtGui import QKeySequence
 from PyQt5.QtWidgets import QMenu, QToolBar, QApplication, QDialog
 
@@ -1014,10 +1014,11 @@
         self.viewmanager.setFileLine(fn, line)
         if not forStack:
             self.__getThreadList()
-            self.__getClientVariables()
+            self.__getClientVariables(debuggerId)
 
         self.debugActGrp.setEnabled(True)
-        
+    
+    @pyqtSlot(int, str, bool)
     def __clientExit(self, status, message, quiet):
         """
         Private method to handle the debugged program terminating.
@@ -1130,7 +1131,7 @@
                 .format(filename, message, lineNo, characterNo))
         
     def __clientException(self, exceptionType, exceptionMessage, stackTrace,
-                          debuggerId=""):
+                          debuggerId):
         """
         Private method to handle an exception of the debugged program.
         
@@ -1226,7 +1227,7 @@
                 for fn, ln, func, args in stackTrace:
                     stack.append((fn, ln, func, args))
                 self.clientStack.emit(stack, debuggerId)
-                self.__getClientVariables()
+                self.__getClientVariables(debuggerId)
                 self.ui.setDebugProfile()
                 self.debugActGrp.setEnabled(True)
                 return
@@ -1242,7 +1243,8 @@
         else:
             self.__continue()
         
-    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs):
+    def __clientSignal(self, message, filename, lineNo, funcName, funcArgs,
+                       debuggerId):
         """
         Private method to handle a signal generated on the client side.
         
@@ -1256,6 +1258,8 @@
         @type str
         @param funcArgs function arguments
         @type str
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         self.ui.raise_()
         self.ui.activateWindow()
@@ -1287,15 +1291,19 @@
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.debugServer.remoteThreadList(debuggerId=debuggerId)
+        self.debugServer.remoteThreadList(debuggerId)
         
-    def __clientThreadSet(self):
+    def __clientThreadSet(self, debuggerId):
         """
         Private method to handle a change of the client's current thread.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.debugServer.remoteClientVariables(0, self.localsVarFilter)
+        self.debugServer.remoteClientVariables(
+            debuggerId, 0, self.localsVarFilter)
         
-    def __getClientVariables(self, debuggerId=""):
+    def __getClientVariables(self, debuggerId):
         """
         Private method to request the global and local variables.
         
@@ -1307,44 +1315,56 @@
         @type str
         """
         # get globals first
-        self.debugServer.remoteClientVariables(1, self.globalsVarFilter,
-                                               debuggerId=debuggerId)
+        self.debugServer.remoteClientVariables(
+            debuggerId, 1, self.globalsVarFilter)
         # the local variables are requested once we have received the globals
         
-    def __clientVariables(self, scope, variables):
+    def __clientVariables(self, scope, variables, debuggerId):
         """
         Private method to write the clients variables to the user interface.
         
         @param scope scope of the variables (-1 = empty locals, 1 = global,
             0 = local)
+        @type int
         @param variables the list of variables from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.ui.activateDebugViewer()
-        if scope > 0:
-            self.debugViewer.showVariables(variables, True)
-            if scope == 1:
-                # now get the local variables
-                self.debugServer.remoteClientVariables(0, self.localsVarFilter)
-        elif scope == 0:
-            self.debugViewer.showVariables(variables, False)
-        elif scope == -1:
-            vlist = [(self.tr('No locals available.'), '', '')]
-            self.debugViewer.showVariables(vlist, False)
+        if debuggerId == self.getSelectedDebuggerId():
+            self.ui.activateDebugViewer()
+            if scope > 0:
+                self.debugViewer.showVariables(variables, True)
+                if scope == 1:
+                    # now get the local variables
+                    self.debugServer.remoteClientVariables(
+                        self.getSelectedDebuggerId(),
+                        0, self.localsVarFilter)
+            elif scope == 0:
+                self.debugViewer.showVariables(variables, False)
+            elif scope == -1:
+                vlist = [(self.tr('No locals available.'), '', '')]
+                self.debugViewer.showVariables(vlist, False)
         
-    def __clientVariable(self, scope, variables):
+    def __clientVariable(self, scope, variables, debuggerId):
         """
         Private method to write the contents of a clients classvariable to
         the user interface.
         
-        @param scope scope of the variables (-1 = empty global, 1 = global,
+        @param scope scope of the variables (-1 = empty locals, 1 = global,
             0 = local)
-        @param variables the list of members of a classvariable from the client
+        @type int
+        @param variables the list of variables from the client
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.ui.activateDebugViewer()
-        if scope == 1:
-            self.debugViewer.showVariable(variables, True)
-        elif scope == 0:
-            self.debugViewer.showVariable(variables, False)
+        if debuggerId == self.getSelectedDebuggerId():
+            self.ui.activateDebugViewer()
+            if scope == 1:
+                self.debugViewer.showVariable(variables, True)
+            elif scope == 0:
+                self.debugViewer.showVariable(variables, False)
             
     def __clientBreakConditionError(self, filename, lineno):
         """
@@ -2259,7 +2279,7 @@
         """
         self.lastAction = 0
         self.__enterRemote()
-        self.debugServer.remoteContinue()
+        self.debugServer.remoteContinue(self.getSelectedDebuggerId())
         self.__getThreadList()
 
     def __specialContinue(self):
@@ -2268,7 +2288,7 @@
         """
         self.lastAction = 2
         self.__enterRemote()
-        self.debugServer.remoteContinue(1)
+        self.debugServer.remoteContinue(self.getSelectedDebuggerId(), 1)
         self.__getThreadList()
 
     def __step(self):
@@ -2277,7 +2297,7 @@
         """
         self.lastAction = 1
         self.__enterRemote()
-        self.debugServer.remoteStep()
+        self.debugServer.remoteStep(self.getSelectedDebuggerId())
 
     def __stepOver(self):
         """
@@ -2285,7 +2305,7 @@
         """
         self.lastAction = 2
         self.__enterRemote()
-        self.debugServer.remoteStepOver()
+        self.debugServer.remoteStepOver(self.getSelectedDebuggerId())
         self.__getThreadList()
 
     def __stepOut(self):
@@ -2294,7 +2314,7 @@
         """
         self.lastAction = 3
         self.__enterRemote()
-        self.debugServer.remoteStepOut()
+        self.debugServer.remoteStepOut(self.getSelectedDebuggerId())
         self.__getThreadList()
 
     def __stepQuit(self):
@@ -2303,7 +2323,7 @@
         """
         self.lastAction = 4
         self.__enterRemote()
-        self.debugServer.remoteStepQuit()
+        self.debugServer.remoteStepQuit(self.getSelectedDebuggerId())
         self.__resetUI()
 
     def __runToCursor(self):
@@ -2315,8 +2335,9 @@
         line = aw.getCursorPosition()[0] + 1
         self.__enterRemote()
         self.debugServer.remoteBreakpoint(
+            self.getSelectedDebuggerId(),
             aw.getFileName(), line, 1, None, 1)
-        self.debugServer.remoteContinue()
+        self.debugServer.remoteContinue(self.getSelectedDebuggerId())
         self.__getThreadList()
 
     def __moveInstructionPointer(self):
@@ -2326,7 +2347,7 @@
         self.lastAction = 0
         aw = self.viewmanager.activeWindow()
         line = aw.getCursorPosition()[0] + 1
-        self.debugServer.remoteMoveIP(line)
+        self.debugServer.remoteMoveIP(self.getSelectedDebuggerId(), line)
 
     def __enterRemote(self):
         """
@@ -2359,3 +2380,12 @@
         self.debugServer.remoteClientStack(debuggerId)
         self.__getThreadList(debuggerId)
         self.__getClientVariables(debuggerId)
+    
+    def getSelectedDebuggerId(self):
+        """
+        Public method to get the currently selected debugger ID.
+        
+        @return selected debugger ID
+        @rtype str
+        """
+        return self.debugViewer.getSelectedDebuggerId()
--- a/eric6/Debugger/DebugViewer.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/DebugViewer.py	Sat Feb 01 19:48:21 2020 +0100
@@ -62,7 +62,7 @@
         self.debugServer = debugServer
         self.debugUI = None
         
-        self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
+        self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
         
         self.__mainLayout = QVBoxLayout()
         self.__mainLayout.setContentsMargins(0, 0, 0, 0)
@@ -124,7 +124,7 @@
         
         index = self.__tabWidget.addTab(
             self.glvWidget,
-            UI.PixmapCache.getIcon("globalVariables.png"), '')
+            UI.PixmapCache.getIcon("globalVariables"), '')
         self.__tabWidget.setTabToolTip(index, self.globalsViewer.windowTitle())
         
         self.setGlobalsFilterButton.clicked.connect(
@@ -178,7 +178,7 @@
         
         index = self.__tabWidget.addTab(
             self.lvWidget,
-            UI.PixmapCache.getIcon("localVariables.png"), '')
+            UI.PixmapCache.getIcon("localVariables"), '')
         self.__tabWidget.setTabToolTip(index, self.localsViewer.windowTitle())
         
         self.sourceButton.clicked.connect(self.__showSource)
@@ -196,7 +196,7 @@
         self.callStackViewer = CallStackViewer(self.debugServer)
         index = self.__tabWidget.addTab(
             self.callStackViewer,
-            UI.PixmapCache.getIcon("step.png"), "")
+            UI.PixmapCache.getIcon("step"), "")
         self.__tabWidget.setTabToolTip(
             index, self.callStackViewer.windowTitle())
         self.callStackViewer.sourceFile.connect(self.sourceFile)
@@ -205,10 +205,10 @@
         
         from .CallTraceViewer import CallTraceViewer
         # add the call trace viewer
-        self.callTraceViewer = CallTraceViewer(self.debugServer)
+        self.callTraceViewer = CallTraceViewer(self.debugServer, self)
         index = self.__tabWidget.addTab(
             self.callTraceViewer,
-            UI.PixmapCache.getIcon("callTrace.png"), "")
+            UI.PixmapCache.getIcon("callTrace"), "")
         self.__tabWidget.setTabToolTip(
             index, self.callTraceViewer.windowTitle())
         self.callTraceViewer.sourceFile.connect(self.sourceFile)
@@ -219,7 +219,7 @@
         self.breakpointViewer.setModel(self.debugServer.getBreakPointModel())
         index = self.__tabWidget.addTab(
             self.breakpointViewer,
-            UI.PixmapCache.getIcon("breakpoints.png"), '')
+            UI.PixmapCache.getIcon("breakpoints"), '')
         self.__tabWidget.setTabToolTip(
             index, self.breakpointViewer.windowTitle())
         self.breakpointViewer.sourceFile.connect(self.sourceFile)
@@ -230,7 +230,7 @@
         self.watchpointViewer.setModel(self.debugServer.getWatchPointModel())
         index = self.__tabWidget.addTab(
             self.watchpointViewer,
-            UI.PixmapCache.getIcon("watchpoints.png"), '')
+            UI.PixmapCache.getIcon("watchpoints"), '')
         self.__tabWidget.setTabToolTip(
             index, self.watchpointViewer.windowTitle())
         
@@ -239,7 +239,7 @@
         self.exceptionLogger = ExceptionLogger()
         index = self.__tabWidget.addTab(
             self.exceptionLogger,
-            UI.PixmapCache.getIcon("exceptions.png"), '')
+            UI.PixmapCache.getIcon("exceptions"), '')
         self.__tabWidget.setTabToolTip(
             index, self.exceptionLogger.windowTitle())
         
@@ -278,6 +278,10 @@
             self.handleDebuggingStarted)
         self.debugServer.clientLine.connect(
             self.__clientLine)
+        self.debugServer.clientSyntaxError.connect(
+            self.__clientSyntaxError)
+        self.debugServer.clientException.connect(
+            self.__clientException)
         
         self.debugServer.clientException.connect(
             self.exceptionLogger.addException)
@@ -432,7 +436,50 @@
             index = self.__debuggersCombo.findText(debuggerId, Qt.MatchExactly)
             if index >= 0:
                 self.__debuggersCombo.setItemIcon(
-                    index, UI.PixmapCache.getIcon("exceptions.png"))
+                    index, UI.PixmapCache.getIcon("exceptions"))
+    
+    # TODO: Refactor the icon setting code into a method
+    def __clientSyntaxError(self, message, filename, lineNo, characterNo,
+                            debuggerId):
+        """
+        Private method to handle a syntax error in the debugged program.
+        
+        @param message message of the syntax error
+        @type str
+        @param filename translated filename of the syntax error position
+        @type str
+        @param lineNo line number of the syntax error position
+        @type int
+        @param characterNo character number of the syntax error position
+        @type int
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        if debuggerId:
+            index = self.__debuggersCombo.findText(debuggerId, Qt.MatchExactly)
+            if index >= 0:
+                self.__debuggersCombo.setItemIcon(
+                    index, UI.PixmapCache.getIcon("syntaxError22"))
+    
+    def __clientException(self, exceptionType, exceptionMessage, stackTrace,
+                          debuggerId):
+        """
+        Private method to handle an exception of the debugged program.
+        
+        @param exceptionType type of exception raised
+        @type str
+        @param exceptionMessage message given by the exception
+        @type (str
+        @param stackTrace list of stack entries
+        @type list of str
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        if debuggerId:
+            index = self.__debuggersCombo.findText(debuggerId, Qt.MatchExactly)
+            if index >= 0:
+                self.__debuggersCombo.setItemIcon(
+                    index, UI.PixmapCache.getIcon("exceptions"))
     
     def setVariablesFilter(self, globalsFilter, localsFilter):
         """
@@ -465,7 +512,7 @@
             self.framenr = frmnr
             if self.debugServer.isDebugging():
                 self.debugServer.remoteClientVariables(
-                    0, self.localsFilter, frmnr)
+                    self.getSelectedDebuggerId(), 0, self.localsFilter, frmnr)
             
             if self.__autoViewSource:
                 self.__showSource()
@@ -477,7 +524,8 @@
         if self.debugServer.isDebugging():
             filterStr = self.globalsFilterEdit.text()
             self.debugServer.remoteClientSetFilter(1, filterStr)
-            self.debugServer.remoteClientVariables(2, self.globalsFilter)
+            self.debugServer.remoteClientVariables(
+                self.getSelectedDebuggerId(), 2, self.globalsFilter)
         
     def setLocalsFilter(self):
         """
@@ -488,7 +536,8 @@
             self.debugServer.remoteClientSetFilter(0, filterStr)
             if self.currentStack:
                 self.debugServer.remoteClientVariables(
-                    0, self.localsFilter, self.framenr)
+                    self.getSelectedDebuggerId(), 0, self.localsFilter,
+                    self.framenr)
         
     def handleDebuggingStarted(self):
         """
@@ -536,16 +585,16 @@
             for thread in threadList:
                 if thread.get('except', False):
                     state = self.tr("waiting at exception")
-                    icon = "exceptions.png"
+                    icon = "exceptions"
                     debugStatus = 1
                 elif thread['broken']:
                     state = self.tr("waiting at breakpoint")
-                    icon = "mediaPlaybackPause.png"
+                    icon = "mediaPlaybackPause"
                     if debugStatus < 1:
                         debugStatus = 0
                 else:
                     state = self.tr("running")
-                    icon = "mediaPlaybackStart.png"
+                    icon = "mediaPlaybackStart"
                 itm = QTreeWidgetItem(self.__threadList,
                                       ["{0:d}".format(thread['id']),
                                        thread['name'], state])
@@ -570,11 +619,11 @@
                         debugStatus = 0
         
         if debugStatus == -1:
-            icon = "mediaPlaybackStart.png"
+            icon = "mediaPlaybackStart"
         elif debugStatus == 0:
-            icon = "mediaPlaybackPause.png"
+            icon = "mediaPlaybackPause"
         else:
-            icon = "exceptions.png"
+            icon = "exceptions"
         self.__debuggersCombo.setItemIcon(self.__debuggersCombo.currentIndex(),
                                           UI.PixmapCache.getIcon(icon))
     
@@ -588,7 +637,7 @@
         """
         if current is not None and self.__doThreadListUpdate:
             tid = int(current.text(0))
-            self.debugServer.remoteSetThread(tid)
+            self.debugServer.remoteSetThread(self.getSelectedDebuggerId(), tid)
     
     def __callStackFrameSelected(self, frameNo):
         """
--- a/eric6/Debugger/DebuggerInterfacePython.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfacePython.py	Sat Feb 01 19:48:21 2020 +0100
@@ -58,8 +58,8 @@
         self.__variant = pythonVariant
         self.__startedVenv = ""
         
-        self.qsock = None
         self.queue = []
+        self.__master = None
         self.__connections = {}
         self.__pendingConnections = []
         
@@ -487,7 +487,7 @@
             self.__startedVenv = venvName
         
         return process, self.__isNetworked, interpreter
-
+    
     def getClientCapabilities(self):
         """
         Public method to retrieve the debug clients capabilities.
@@ -500,19 +500,16 @@
         """
         Public slot to handle a new connection.
         
-        @param sock reference to the socket object (QTcpSocket)
-        @return flag indicating success (boolean)
+        @param sock reference to the socket object
+        @type QTcpSocket
+        @return flag indicating success
+        @rtype bool
         """
-        sock.disconnected.connect(self.debugServer.startClient)
-        sock.readyRead.connect(lambda: self.__parseClientLine(sock))
-        
-        if self.qsock is None:
-            # first connection is the main one
-            self.qsock = sock
         self.__pendingConnections.append(sock)
         
-        # Get the remote clients capabilities
-        self.remoteCapabilities()
+        sock.readyRead.connect(lambda: self.__parseClientLine(sock))
+        sock.disconnected.connect(lambda: self.__socketDisconnected(sock))
+        
         return True
     
     def __assignDebuggerId(self, sock, debuggerId):
@@ -529,8 +526,38 @@
             self.__connections[debuggerId] = sock
             self.__pendingConnections.remove(sock)
             
+            if self.__master is None:
+                self.__master = debuggerId
+                # Get the remote clients capabilities
+                self.remoteCapabilities()
+            
             self.debugServer.signalClientDebuggerIds(
                 sorted(self.__connections.keys()))
+            
+            if debuggerId == self.__master:
+                self.__flush()
+                self.debugServer.masterClientConnected()
+            
+            self.debugServer.initializeClient(debuggerId)
+    
+    def __socketDisconnected(self, sock):
+        """
+        Private slot handling a socket disconnecting.
+        
+        @param sock reference to the disconnected socket
+        @type QTcpSocket
+        """
+        for debuggerId in self.__connections:
+            if self.__connections[debuggerId] is sock:
+                del self.__connections[debuggerId]
+                break
+        else:
+            if sock in self.__pendingConnections:
+                self.__pendingConnections.remove(sock)
+        
+        if not self.__connections:
+            # no active connections anymore => restart the backend
+            self.debugServer.startClient()
     
     def getDebuggerIds(self):
         """
@@ -541,14 +568,15 @@
         """
         return sorted(self.__connections.keys())
     
-    def flush(self):
+    def __flush(self):
         """
-        Public slot to flush the queue.
+        Private slot to flush the queue.
         """
-        if self.qsock:
+        if self.__master:
             # Send commands that were waiting for the connection.
             for cmd in self.queue:
-                self.__writeJsonCommandToSocket(cmd, self.qsock)
+                self.__writeJsonCommandToSocket(
+                    cmd, self.__connections[self.__master])
         
         self.queue = []
     
@@ -556,38 +584,53 @@
         """
         Public method to cleanly shut down.
         
-        It closes our socket and shuts down
-        the debug client. (Needed on Win OS)
+        It closes our sockets and shuts down the debug clients.
+        (Needed on Win OS)
         """
-        if self.qsock is None:
+        if not self.__master:
             return
         
-        for sock in (
-            list(self.__connections.values()) + self.__pendingConnections
-        ):
-            # do not want any slots called during shutdown
-            sock.disconnected.disconnect()
-            sock.readyRead.disconnect()
+        while self.__connections:
+            debuggerId, sock = self.__connections.popitem()
+            self.__shutdownSocket(sock)
         
-            # close down socket, and shut down client as well.
-            self.__sendJsonCommand("RequestShutdown", {}, sock=sock)
-            sock.flush()
-            sock.close()
+        while self.__pendingConnections:
+            sock = self.__pendingConnections.pop()
+            self.__shutdownSocket(sock)
         
         # reinitialize
-        self.qsock = None
         self.queue = []
         
-        self.__pendingConnections = []
-        self.__connections = {}
+        self.__master = None
+
+    def __shutdownSocket(self, sock):
+        """
+        Private slot to shut down a socket.
+        
+        @param sock reference to the socket
+        @type QTcpSocket
+        """
+        # do not want any slots called during shutdown
+        sock.readyRead.disconnect()
+        sock.disconnected.disconnect()
+
+        # close down socket, and shut down client as well.
+        self.__sendJsonCommand("RequestShutdown", {}, sock=sock)
+        sock.flush()
+        sock.close()
+        
+        sock.setParent(None)
+        sock.deleteLater()
+        del sock
     
     def isConnected(self):
         """
         Public method to test, if a debug client has connected.
         
-        @return flag indicating the connection status (boolean)
+        @return flag indicating the connection status
+        @rtype bool
         """
-        return self.qsock is not None
+        return bool(self.__connections)
     
     def remoteEnvironment(self, env):
         """
@@ -595,7 +638,9 @@
         
         @param env environment settings (dictionary)
         """
-        self.__sendJsonCommand("RequestEnvironment", {"environment": env})
+        if self.__master:
+            self.__sendJsonCommand("RequestEnvironment", {"environment": env},
+                                   self.__master)
     
     def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
                    autoContinue=True, autoFork=False, forkChild=False):
@@ -625,7 +670,7 @@
             "traceInterpreter": traceInterpreter,
             "autofork": autoFork,
             "forkChild": forkChild,
-        })
+        }, self.__master)
     
     def remoteRun(self, fn, argv, wd, autoFork=False, forkChild=False):
         """
@@ -648,7 +693,7 @@
             "argv": Utilities.parseOptionString(argv),
             "autofork": autoFork,
             "forkChild": forkChild,
-        })
+        }, self.__master)
     
     def remoteCoverage(self, fn, argv, wd, erase=False):
         """
@@ -669,7 +714,7 @@
             "filename": fn,
             "argv": Utilities.parseOptionString(argv),
             "erase": erase,
-        })
+        }, self.__master)
 
     def remoteProfile(self, fn, argv, wd, erase=False):
         """
@@ -690,155 +735,236 @@
             "filename": fn,
             "argv": Utilities.parseOptionString(argv),
             "erase": erase,
-        })
-
-    def remoteStatement(self, stmt):
+        }, self.__master)
+    
+    def remoteStatement(self, debuggerId, stmt):
         """
         Public method to execute a Python statement.
         
-        @param stmt the Python statement to execute (string). It
-              should not have a trailing newline.
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param stmt the Python statement to execute.
+        @type str
         """
         self.__sendJsonCommand("ExecuteStatement", {
             "statement": stmt,
-        })
-
-    def remoteStep(self):
+        }, debuggerId)
+    
+    def remoteStep(self, debuggerId):
         """
         Public method to single step the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.__sendJsonCommand("RequestStep", {})
-
-    def remoteStepOver(self):
+        self.__sendJsonCommand("RequestStep", {}, debuggerId)
+    
+    def remoteStepOver(self, debuggerId):
         """
         Public method to step over the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.__sendJsonCommand("RequestStepOver", {})
-
-    def remoteStepOut(self):
+        self.__sendJsonCommand("RequestStepOver", {}, debuggerId)
+    
+    def remoteStepOut(self, debuggerId):
         """
         Public method to step out the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.__sendJsonCommand("RequestStepOut", {})
-
-    def remoteStepQuit(self):
+        self.__sendJsonCommand("RequestStepOut", {}, debuggerId)
+    
+    def remoteStepQuit(self, debuggerId):
         """
         Public method to stop the debugged program.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.__sendJsonCommand("RequestStepQuit", {})
-
-    def remoteContinue(self, special=False):
+        self.__sendJsonCommand("RequestStepQuit", {}, debuggerId)
+    
+    def remoteContinue(self, debuggerId, special=False):
         """
         Public method to continue the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param special flag indicating a special continue operation
+        @type bool
         """
         self.__sendJsonCommand("RequestContinue", {
             "special": special,
-        })
-
-    def remoteMoveIP(self, line):
+        }, debuggerId)
+    
+    def remoteMoveIP(self, debuggerId, line):
         """
         Public method to move the instruction pointer to a different line.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param line the new line, where execution should be continued
+        @type int
         """
         self.__sendJsonCommand("RequestMoveIP", {
             "newLine": line,
-        })
-
-    def remoteBreakpoint(self, fn, line, setBreakpoint, cond=None, temp=False):
+        }, debuggerId)
+    
+    def remoteBreakpoint(self, debuggerId, fn, line, setBreakpoint, cond=None,
+                         temp=False):
         """
         Public method to set or clear a breakpoint.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
-        @param setBreakpoint flag indicating setting or resetting a
-            breakpoint (boolean)
-        @param cond condition of the breakpoint (string)
-        @param temp flag indicating a temporary breakpoint (boolean)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
+        @param setBreakpoint flag indicating setting or resetting a breakpoint
+        @type bool
+        @param cond condition of the breakpoint
+        @type str
+        @param temp flag indicating a temporary breakpoint
+        @type bool
         """
-        self.__sendJsonCommand("RequestBreakpoint", {
-            "filename": self.translate(fn, False),
-            "line": line,
-            "temporary": temp,
-            "setBreakpoint": setBreakpoint,
-            "condition": cond,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            self.__sendJsonCommand("RequestBreakpoint", {
+                "filename": self.translate(fn, False),
+                "line": line,
+                "temporary": temp,
+                "setBreakpoint": setBreakpoint,
+                "condition": cond,
+            }, debuggerId)
     
-    def remoteBreakpointEnable(self, fn, line, enable):
+    def remoteBreakpointEnable(self, debuggerId, fn, line, enable):
         """
         Public method to enable or disable a breakpoint.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
         @param enable flag indicating enabling or disabling a breakpoint
-            (boolean)
+        @type bool
         """
-        self.__sendJsonCommand("RequestBreakpointEnable", {
-            "filename": self.translate(fn, False),
-            "line": line,
-            "enable": enable,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            self.__sendJsonCommand("RequestBreakpointEnable", {
+                "filename": self.translate(fn, False),
+                "line": line,
+                "enable": enable,
+            }, debuggerId)
     
-    def remoteBreakpointIgnore(self, fn, line, count):
+    def remoteBreakpointIgnore(self, debuggerId, fn, line, count):
         """
         Public method to ignore a breakpoint the next couple of occurrences.
         
-        @param fn filename the breakpoint belongs to (string)
-        @param line linenumber of the breakpoint (int)
-        @param count number of occurrences to ignore (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param fn filename the breakpoint belongs to
+        @type str
+        @param line linenumber of the breakpoint
+        @type int
+        @param count number of occurrences to ignore
+        @type int
         """
-        self.__sendJsonCommand("RequestBreakpointIgnore", {
-            "filename": self.translate(fn, False),
-            "line": line,
-            "count": count,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            self.__sendJsonCommand("RequestBreakpointIgnore", {
+                "filename": self.translate(fn, False),
+                "line": line,
+                "count": count,
+            }, debuggerId)
     
-    def remoteWatchpoint(self, cond, setWatch, temp=False):
+    def remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False):
         """
         Public method to set or clear a watch expression.
         
-        @param cond expression of the watch expression (string)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
         @param setWatch flag indicating setting or resetting a watch expression
-            (boolean)
-        @param temp flag indicating a temporary watch expression (boolean)
+        @type bool
+        @param temp flag indicating a temporary watch expression
+        @type bool
         """
-        # cond is combination of cond and special (s. watch expression viewer)
-        self.__sendJsonCommand("RequestWatch", {
-            "temporary": temp,
-            "setWatch": setWatch,
-            "condition": cond,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            # cond is combination of cond and special (s. watch expression
+            # viewer)
+            self.__sendJsonCommand("RequestWatch", {
+                "temporary": temp,
+                "setWatch": setWatch,
+                "condition": cond,
+            }, debuggerId)
     
-    def remoteWatchpointEnable(self, cond, enable):
+    def remoteWatchpointEnable(self, debuggerId, cond, enable):
         """
         Public method to enable or disable a watch expression.
         
-        @param cond expression of the watch expression (string)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
         @param enable flag indicating enabling or disabling a watch expression
-            (boolean)
+        @type bool
         """
-        # cond is combination of cond and special (s. watch expression viewer)
-        self.__sendJsonCommand("RequestWatchEnable", {
-            "condition": cond,
-            "enable": enable,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            # cond is combination of cond and special (s. watch expression
+            # viewer)
+            self.__sendJsonCommand("RequestWatchEnable", {
+                "condition": cond,
+                "enable": enable,
+            }, debuggerId)
     
-    def remoteWatchpointIgnore(self, cond, count):
+    def remoteWatchpointIgnore(self, debuggerId, cond, count):
         """
         Public method to ignore a watch expression the next couple of
         occurrences.
         
-        @param cond expression of the watch expression (string)
-        @param count number of occurrences to ignore (int)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param cond expression of the watch expression
+        @type str
+        @param count number of occurrences to ignore
+        @type int
         """
-        # cond is combination of cond and special (s. watch expression viewer)
-        self.__sendJsonCommand("RequestWatchIgnore", {
-            "condition": cond,
-            "count": count,
-        })
+        if debuggerId:
+            debuggerList = [debuggerId]
+        else:
+            debuggerList = list(self.__connections.keys())
+        for debuggerId in debuggerList:
+            # cond is combination of cond and special (s. watch expression
+            # viewer)
+            self.__sendJsonCommand("RequestWatchIgnore", {
+                "condition": cond,
+                "count": count,
+            }, debuggerId)
     
+    # TODO: add debuggerId
     def remoteRawInput(self, s):
         """
         Public method to send the raw input to the debugged program.
@@ -849,42 +975,44 @@
             "input": s,
         })
     
-    def remoteThreadList(self, debuggerId=""):
+    def remoteThreadList(self, debuggerId):
         """
         Public method to request the list of threads from the client.
         
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.__sendJsonCommand("RequestThreadList", {}, debuggerId=debuggerId)
+        self.__sendJsonCommand("RequestThreadList", {}, debuggerId)
         
-    def remoteSetThread(self, tid, debuggerId=""):
+    def remoteSetThread(self, debuggerId, tid):
         """
         Public method to request to set the given thread as current thread.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param tid id of the thread
         @type int
-        @param debuggerId ID of the debugger backend
-        @type str
         """
         self.__sendJsonCommand("RequestThreadSet", {
             "threadID": tid,
-        }, debuggerId=debuggerId)
+        }, debuggerId)
     
-    def remoteClientStack(self, debuggerId=""):
+    def remoteClientStack(self, debuggerId):
         """
         Public method to request the stack of the main thread.
         
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.__sendJsonCommand("RequestStack", {}, debuggerId=debuggerId)
+        self.__sendJsonCommand("RequestStack", {}, debuggerId)
     
-    def remoteClientVariables(self, scope, filterList, framenr=0, maxSize=0,
-                              debuggerId=""):
+    def remoteClientVariables(self, debuggerId, scope, filterList, framenr=0,
+                              maxSize=0):
         """
         Public method to request the variables of the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param scope the scope of the variables (0 = local, 1 = global)
         @type int
         @param filterList list of variable types to filter out
@@ -895,21 +1023,21 @@
             be shown. If it is bigger than that, a 'too big' indication will
             be given (@@TOO_BIG_TO_SHOW@@).
         @type int
-        @param debuggerId ID of the debugger backend
-        @type str
         """
         self.__sendJsonCommand("RequestVariables", {
             "frameNumber": framenr,
             "scope": scope,
             "filters": filterList,
             "maxSize": maxSize,
-        }, debuggerId=debuggerId)
+        }, debuggerId)
     
-    def remoteClientVariable(self, scope, filterList, var, framenr=0,
-                             maxSize=0):
+    def remoteClientVariable(self, debuggerId, scope, filterList, var,
+                             framenr=0, maxSize=0):
         """
         Public method to request the variables of the debugged program.
         
+        @param debuggerId ID of the debugger backend
+        @type str
         @param scope the scope of the variables (0 = local, 1 = global)
         @type int
         @param filterList list of variable types to filter out
@@ -929,8 +1057,9 @@
             "scope": scope,
             "filters": filterList,
             "maxSize": maxSize,
-        })
+        }, debuggerId)
     
+    # TODO: add debuggerId
     def remoteClientSetFilter(self, scope, filterStr):
         """
         Public method to set a variables filter list.
@@ -944,15 +1073,18 @@
             "filter": filterStr,
         })
     
-    def setCallTraceEnabled(self, on):
+    def setCallTraceEnabled(self, debuggerId, on):
         """
         Public method to set the call trace state.
         
-        @param on flag indicating to enable the call trace function (boolean)
+        @param debuggerId ID of the debugger backend
+        @type str
+        @param on flag indicating to enable the call trace function
+        @type bool
         """
         self.__sendJsonCommand("RequestCallTrace", {
             "enable": on,
-        })
+        }, debuggerId)
     
     def remoteBanner(self):
         """
@@ -960,11 +1092,14 @@
         """
         self.__sendJsonCommand("RequestBanner", {})
     
-    def remoteCapabilities(self):
+    def remoteCapabilities(self, debuggerId):
         """
         Public slot to get the debug clients capabilities.
+        
+        @param debuggerId ID of the debugger backend
+        @type str
         """
-        self.__sendJsonCommand("RequestCapabilities", {})
+        self.__sendJsonCommand("RequestCapabilities", {}, debuggerId)
     
     def remoteCompletion(self, text):
         """
@@ -1159,14 +1294,15 @@
             # Check if obsolet thread was clicked
             if params["stack"] == []:
                 # Request updated list
-                self.remoteThreadList()
+                self.remoteThreadList(params["debuggerId"])
                 return
             for s in params["stack"]:
                 s[0] = self.translate(s[0], True)
             cf = params["stack"][0]
             if self.__autoContinue:
                 self.__autoContinue = False
-                QTimer.singleShot(0, self.remoteContinue)
+                QTimer.singleShot(
+                    0, lambda: self.remoteContinue(params["debuggerId"]))
             else:
                 self.debugServer.signalClientLine(
                     cf[0], int(cf[1]), params["debuggerId"],
@@ -1183,15 +1319,17 @@
                 fromInfo["filename"], str(fromInfo["linenumber"]),
                 fromInfo["codename"],
                 toInfo["filename"], str(toInfo["linenumber"]),
-                toInfo["codename"])
+                toInfo["codename"],
+                params["debuggerId"])
         
         elif method == "ResponseVariables":
             self.debugServer.signalClientVariables(
-                params["scope"], params["variables"])
+                params["scope"], params["variables"], params["debuggerId"])
         
         elif method == "ResponseVariable":
             self.debugServer.signalClientVariable(
-                params["scope"], [params["variable"]] + params["variables"])
+                params["scope"], [params["variable"]] + params["variables"],
+                params["debuggerId"])
         
         elif method == "ResponseThreadList":
             self.debugServer.signalClientThreadList(
@@ -1199,94 +1337,106 @@
                 params["debuggerId"])
         
         elif method == "ResponseThreadSet":
-            self.debugServer.signalClientThreadSet()
+            self.debugServer.signalClientThreadSet(params["debuggerId"])
         
         elif method == "ResponseCapabilities":
             self.clientCapabilities = params["capabilities"]
-            self.debugServer.signalClientCapabilities(
-                params["capabilities"],
-                params["clientType"],
-                self.__startedVenv,
-            )
+            if params["debuggerId"] == self.__master:
+                # signal only for the master connection
+                self.debugServer.signalClientCapabilities(
+                    params["capabilities"],
+                    params["clientType"],
+                    self.__startedVenv,
+                )
         
         elif method == "ResponseBanner":
-            self.debugServer.signalClientBanner(
-                params["version"],
-                params["platform"],
-                params["dbgclient"],
-                self.__startedVenv,
-            )
+            if params["debuggerId"] == self.__master:
+                # signal only for the master connection
+                self.debugServer.signalClientBanner(
+                    params["version"],
+                    params["platform"],
+                    params["dbgclient"],
+                    self.__startedVenv,
+                )
         
         elif method == "ResponseOK":
-            self.debugServer.signalClientStatement(False)
+            self.debugServer.signalClientStatement(False, params["debuggerId"])
         
         elif method == "ResponseContinue":
-            self.debugServer.signalClientStatement(True)
+            self.debugServer.signalClientStatement(True, params["debuggerId"])
         
+        # TODO: add debuggerId
         elif method == "RequestRaw":
             self.debugServer.signalClientRawInput(
                 params["prompt"], params["echo"])
         
+        # TODO: add debuggerId
         elif method == "ResponseBPConditionError":
             fn = self.translate(params["filename"], True)
             self.debugServer.signalClientBreakConditionError(
                 fn, params["line"])
         
+        # TODO: add debuggerId
         elif method == "ResponseClearBreakpoint":
             fn = self.translate(params["filename"], True)
             self.debugServer.signalClientClearBreak(fn, params["line"])
         
+        # TODO: add debuggerId
         elif method == "ResponseWatchConditionError":
             self.debugServer.signalClientWatchConditionError(
                 params["condition"])
         
+        # TODO: add debuggerId
         elif method == "ResponseClearWatch":
             self.debugServer.signalClientClearWatch(params["condition"])
         
         elif method == "ResponseException":
-            if params:
-                exctype = params["type"]
-                excmessage = params["message"]
-                stack = params["stack"]
-                if stack:
+            exctype = params["type"]
+            excmessage = params["message"]
+            stack = params["stack"]
+            if stack:
+                for stackEntry in stack:
+                    stackEntry[0] = self.translate(stackEntry[0], True)
+                if stack[0] and stack[0][0] == "<string>":
                     for stackEntry in stack:
-                        stackEntry[0] = self.translate(stackEntry[0], True)
-                    if stack[0] and stack[0][0] == "<string>":
-                        for stackEntry in stack:
-                            if stackEntry[0] == "<string>":
-                                stackEntry[0] = self.__scriptName
-                            else:
-                                break
-            else:
-                exctype = ''
-                excmessage = ''
-                stack = []
+                        if stackEntry[0] == "<string>":
+                            stackEntry[0] = self.__scriptName
+                        else:
+                            break
             
             self.debugServer.signalClientException(
-                exctype, excmessage, stack)
+                exctype, excmessage, stack, params["debuggerId"])
         
         elif method == "ResponseSyntax":
             self.debugServer.signalClientSyntaxError(
                 params["message"], self.translate(params["filename"], True),
-                params["linenumber"], params["characternumber"])
+                params["linenumber"], params["characternumber"],
+                params["debuggerId"])
         
         elif method == "ResponseSignal":
             self.debugServer.signalClientSignal(
                 params["message"], self.translate(params["filename"], True),
-                params["linenumber"], params["function"], params["arguments"])
+                params["linenumber"], params["function"], params["arguments"],
+                params["debuggerId"])
         
         elif method == "ResponseExit":
             self.__scriptName = ""
             self.debugServer.signalClientExit(
-                params["status"], params["message"])
+                params["status"], params["message"], params["debuggerId"])
         
         elif method == "PassiveStartup":
             self.debugServer.passiveStartUp(
                 self.translate(params["filename"], True), params["exceptions"])
         
         elif method == "ResponseCompletion":
-            self.debugServer.signalClientCompletionList(
-                params["completions"], params["text"])
+            if params["debuggerId"] == self.__master:
+                # signal only for the master connection
+                self.debugServer.signalClientCompletionList(
+                    params["completions"], params["text"])
+        
+        ###################################################################
+        ## Unit test related stuff is not done with multi processing
+        ###################################################################
         
         elif method == "ResponseUTDiscover":
             self.debugServer.clientUtDiscovered(
@@ -1327,6 +1477,7 @@
             self.debugServer.clientUtTestSucceededUnexpected(
                 params["testname"], params["id"])
         
+        # TODO: add debuggerId
         elif method == "RequestForkTo":
             self.__askForkTo()
     
@@ -1355,8 +1506,8 @@
         
         if debuggerId and debuggerId in self.__connections:
             sock = self.__connections[debuggerId]
-        elif sock is None and self.qsock is not None:
-            sock = self.qsock
+        elif sock is None and self.__master is not None:
+            sock = self.__connections[self.__master]
         if sock is not None:
             self.__writeJsonCommandToSocket(cmd, sock)
         else:
--- a/eric6/Debugger/ExceptionLogger.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/ExceptionLogger.py	Sat Feb 01 19:48:21 2020 +0100
@@ -87,25 +87,34 @@
         else:
             self.menu.popup(coord)
             
-    def addException(self, exceptionType, exceptionMessage, stackTrace):
+    def addException(self, exceptionType, exceptionMessage, stackTrace,
+                     debuggerId):
         """
         Public slot to handle the arrival of a new exception.
         
-        @param exceptionType type of exception raised (string)
-        @param exceptionMessage message given by the exception (string)
-        @param stackTrace list of stack entries.
+        @param exceptionType type of exception raised
+        @type str
+        @param exceptionMessage message given by the exception
+        @type str
+        @param stackTrace list of stack entries
+        @type list
+        @param debuggerId ID of the debugger backend
+        @type str
         """
         itm = QTreeWidgetItem(self)
         if exceptionType is None:
             itm.setText(
-                0, self.tr('An unhandled exception occured.'
-                           ' See the shell window for details.'))
+                0, self.tr('{0}: An unhandled exception occured.'
+                           ' See the shell window for details.')
+                .format(debuggerId))
             return
         
-        if exceptionMessage == '':
-            itm.setText(0, "{0}".format(exceptionType))
+        if not exceptionMessage:
+            itm.setText(0, self.tr("{0}: {1}").format(
+                debuggerId, exceptionType))
         else:
-            itm.setText(0, "{0}, {1}".format(exceptionType, exceptionMessage))
+            itm.setText(0, self.tr("{0}: {1}, {2}").format(
+                debuggerId, exceptionType, exceptionMessage))
         
         # now add the call stack, most recent call first
         for entry in stackTrace:
--- a/eric6/Debugger/VariablesViewer.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/Debugger/VariablesViewer.py	Sat Feb 01 19:48:21 2020 +0100
@@ -792,7 +792,9 @@
         # step 4: request the variable from the debugger
         variablesFilter = e5App().getObject("DebugUI").variablesFilter(
             self.__globalScope)
+        # TODO: refactor this statement
         e5App().getObject("DebugServer").remoteClientVariable(
+            e5App().getObject("DebugUI").debugViewer.getSelectedDebuggerId(),
             self.__globalScope, variablesFilter, pathlist, self.framenr)
     
     def setExpanded(self, index, state):
--- a/eric6/QScintilla/Shell.py	Thu Jan 30 19:37:03 2020 +0100
+++ b/eric6/QScintilla/Shell.py	Sat Feb 01 19:48:21 2020 +0100
@@ -862,7 +862,8 @@
         """
         Private method to handle the response from the debugger client.
         
-        @param more flag indicating that more user input is required (boolean)
+        @param more flag indicating that more user input is required
+        @type bool
         """
         if not self.inRawMode:
             self.inContinue = more
@@ -1044,7 +1045,7 @@
         self.setFocus()
         self.inRawMode = True
         self.echoInput = echo
-        self.__write(s)
+        self.__writeQueued(s)
         line, col = self.__getEndPos()
         self.setCursorPosition(line, col)
         buf = self.text(line)
@@ -1842,7 +1843,10 @@
                 self.vm.quit()
                 return
             
-            self.dbs.remoteStatement(cmd)
+            self.dbs.remoteStatement(
+                e5App().getObject("DebugUI").getSelectedDebuggerId(),
+                cmd
+            )
             while self.inCommandExecution:
                 try:
                     QApplication.processEvents()

eric ide

mercurial