Started the attempt to implement a multi process debugger. multi_processing

Sun, 26 Jan 2020 19:29:06 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 26 Jan 2020 19:29:06 +0100
branch
multi_processing
changeset 7372
021f0252afac
parent 7371
067f717c5a80
child 7373
d036d72f457c

Started the attempt to implement a multi process debugger.

eric6/DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
--- a/eric6/DebugClients/Python/DebugClientBase.py	Sun Jan 26 16:00:52 2020 +0100
+++ b/eric6/DebugClients/Python/DebugClientBase.py	Sun Jan 26 19:29:06 2020 +0100
@@ -223,6 +223,8 @@
         self.errorstream = None
         self.pollingDisabled = False
         
+        self.__debuggerId = ""
+        
         self.callTraceEnabled = None
         
         self.variant = 'You should not see this'
@@ -1110,6 +1112,18 @@
             "exceptions": exceptions,
         })
     
+    def sendDebuggerId(self, debuggerId):
+        """
+        Public method to send the debug client id.
+        
+        @param debuggerId id of this debug client instance (made up of
+            hostname and process ID)
+        @type str
+        """
+        self.sendJsonCommand("DebuggerId", {
+            "id": self.__debuggerId,
+        })
+    
     def __clientCapabilities(self):
         """
         Private method to determine the clients capabilities.
@@ -1273,7 +1287,9 @@
         elif "@@i" in remoteAddress:
             remoteAddress = remoteAddress.split("@@i")[0]
         sock = socket.create_connection((remoteAddress, port))
-
+        
+        self.__debuggerId = "{0}-{1}".format(socket.gethostname(), os.getpid())
+        
         self.readstream = AsyncFile(sock, sys.stdin.mode, sys.stdin.name)
         self.writestream = AsyncFile(sock, sys.stdout.mode, sys.stdout.name)
         self.errorstream = AsyncFile(sock, sys.stderr.mode, sys.stderr.name)
@@ -1286,6 +1302,8 @@
         
         # attach to the main thread here
         self.attachThread(mainThread=True)
+        
+        self.sendDebuggerId(self.__debuggerId)
 
     def __unhandled_exception(self, exctype, excval, exctb):
         """
--- a/eric6/Debugger/DebuggerInterfacePython.py	Sun Jan 26 16:00:52 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfacePython.py	Sun Jan 26 19:29:06 2020 +0100
@@ -60,6 +60,8 @@
         
         self.qsock = None
         self.queue = []
+        self.__connections = {}
+        self.__pendingConnections = []
         
         # set default values for capabilities of clients
         self.clientCapabilities = ClientDefaultCapabilities
@@ -501,27 +503,40 @@
         @param sock reference to the socket object (QTcpSocket)
         @return flag indicating success (boolean)
         """
-        # If we already have a connection, refuse this one.  It will be closed
-        # automatically.
-        if self.qsock is not None:
-            return False
+        sock.disconnected.connect(self.debugServer.startClient)
+        sock.readyRead.connect(lambda: self.__parseClientLine(sock))
         
-        sock.disconnected.connect(self.debugServer.startClient)
-        sock.readyRead.connect(self.__parseClientLine)
-        
-        self.qsock = 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()
         return True
     
+    def __assignDebuggerId(self, sock, debuggerId):
+        """
+        Private method to set the debugger id for a recent debugger connection
+        attempt.
+        
+        @param sock reference to the socket object
+        @type QTcpSocket
+        @param debuggerId id of the connected debug client
+        @type str
+        """
+        if sock in self.__pendingConnections:
+            self.__connections[debuggerId] = sock
+            self.__pendingConnections.remove(sock)
+    
     def flush(self):
         """
         Public slot to flush the queue.
         """
-        # Send commands that were waiting for the connection.
-        for cmd in self.queue:
-            self.__writeJsonCommandToSocket(cmd)
+        if self.qsock:
+            # Send commands that were waiting for the connection.
+            for cmd in self.queue:
+                self.__writeJsonCommandToSocket(cmd, self.qsock)
         
         self.queue = []
     
@@ -535,18 +550,24 @@
         if self.qsock is None:
             return
         
-        # do not want any slots called during shutdown
-        self.qsock.disconnected.disconnect(self.debugServer.startClient)
-        self.qsock.readyRead.disconnect(self.__parseClientLine)
+        for sock in (
+            list(self.__connections.values()) + self.__pendingConnections
+        ):
+            # do not want any slots called during shutdown
+            sock.disconnected.disconnect()
+            sock.readyRead.disconnect()
         
-        # close down socket, and shut down client as well.
-        self.__sendJsonCommand("RequestShutdown", {})
-        self.qsock.flush()
-        self.qsock.close()
+            # close down socket, and shut down client as well.
+            self.__sendJsonCommand("RequestShutdown", {}, sock=sock)
+            sock.flush()
+            sock.close()
         
         # reinitialize
         self.qsock = None
         self.queue = []
+        
+        self.__pendingConnections = []
+        self.__connections = {}
     
     def isConnected(self):
         """
@@ -1045,12 +1066,15 @@
                 "target": "child",
             })
     
-    def __parseClientLine(self):
+    def __parseClientLine(self, sock):
         """
         Private method to handle data from the client.
+        
+        @param sock reference to the socket to read data from
+        @type QTcpSocket
         """
-        while self.qsock and self.qsock.canReadLine():
-            qs = self.qsock.readLine()
+        while sock and sock.canReadLine():
+            qs = sock.readLine()
             if self.codec is not None:
                 line = self.codec.toUnicode(qs)
             else:
@@ -1059,10 +1083,10 @@
             logging.debug("<Debug-Server> %s", line)
 ##            print("Server: ", line)          ##debug
             
-            self.__handleJsonCommand(line)
+            self.__handleJsonCommand(line, sock)
             continue
     
-    def __handleJsonCommand(self, jsonStr):
+    def __handleJsonCommand(self, jsonStr, sock):
         """
         Private method to handle a command or response serialized as a
         JSON string.
@@ -1070,6 +1094,8 @@
         @param jsonStr string containing the command or response received
             from the debug backend
         @type str
+        @param sock reference to the socket the data was received from
+        @type QTcpSocket
         """
         import json
         
@@ -1093,7 +1119,10 @@
         method = commandDict["method"]
         params = commandDict["params"]
         
-        if method == "ClientOutput":
+        if method == "DebuggerId":
+            self.__assignDebuggerId(sock, params["id"])
+        
+        elif method == "ClientOutput":
             self.debugServer.signalClientOutput(params["text"])
         
         elif method in ["ResponseLine", "ResponseStack"]:
@@ -1269,7 +1298,7 @@
         elif method == "RequestForkTo":
             self.__askForkTo()
     
-    def __sendJsonCommand(self, command, params):
+    def __sendJsonCommand(self, command, params, debuggerId="", sock=None):
         """
         Private method to send a single command to the client.
         
@@ -1277,6 +1306,11 @@
         @type str
         @param params dictionary of named parameters for the command
         @type dict
+        @param debuggerId id of the debug client to send the command to
+        @type str
+        @param sock reference to the socket object to be used (only used if
+            debuggerId is not given)
+        @type QTcpSocket
         """
         import json
         
@@ -1286,22 +1320,29 @@
             "params": params,
         }
         cmd = json.dumps(commandDict) + '\n'
-        if self.qsock is not None:
-            self.__writeJsonCommandToSocket(cmd)
+        
+        if debuggerId and debuggerId in self.__connections:
+            sock = self.__connections[debuggerId]
+        elif sock is None and self.qsock is not None:
+            sock = self.qsock
+        if sock is not None:
+            self.__writeJsonCommandToSocket(cmd, sock)
         else:
             self.queue.append(cmd)
     
-    def __writeJsonCommandToSocket(self, cmd):
+    def __writeJsonCommandToSocket(self, cmd, sock):
         """
         Private method to write a JSON command to the socket.
         
         @param cmd JSON command to be sent
         @type str
+        @param sock reference to the socket to write to
+        @type QTcpSocket
         """
         data = cmd.encode('utf8', 'backslashreplace')
         length = "{0:09d}".format(len(data))
-        self.qsock.write(length.encode() + data)
-        self.qsock.flush()
+        sock.write(length.encode() + data)
+        sock.flush()
 
 
 def createDebuggerInterfacePython2(debugServer, passive):

eric ide

mercurial