diff -r cc920e534ac0 -r 72a72fd56494 eric6/Debugger/DebuggerInterfacePython.py --- 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: