Wed, 07 Feb 2024 15:28:08 +0100
Changed the interface to the debug client to make it a bit more robust and harmonize it with other such interfaces.
(grafted from d80184d38152028028839a3e0ce75bacc698e3da)
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/AsyncFile.py --- a/src/eric7/DebugClients/Python/AsyncFile.py Fri Feb 02 19:29:29 2024 +0100 +++ b/src/eric7/DebugClients/Python/AsyncFile.py Wed Feb 07 15:28:08 2024 +0100 @@ -10,7 +10,9 @@ import contextlib import socket +import struct import threading +import zlib from DebugUtilities import prepareJsonCommand @@ -109,14 +111,16 @@ self.writeLock.acquire() while self.wpending: try: - buf = self.wpending.pop(0) + data = self.wpending.pop(0) except IndexError: break try: with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError): - buf = buf.encode("utf-8", "backslashreplace") - self.sock.sendall(buf) + data = data.encode("utf-8", "backslashreplace") + header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) + self.sock.sendall(header) + self.sock.sendall(data) self.nWriteErrors = 0 except OSError: self.nWriteErrors += 1 @@ -188,24 +192,27 @@ def readCommand(self): """ - Public method to read a length prefixed command string. + Public method to read a command string prefixed by a command header. @return command string @rtype str """ - # The command string is prefixed by a 9 character long length field. - length = self.sock.recv(9) - length = int(length) - data = b"" - while len(data) < length: - remaining = length - len(data) - newBytes = self.sock.recv(min(remaining, AsyncFile.CMD_BUFSIZE)) - data += newBytes - if newBytes[-1] == b"\n": - break + header = self.sock.recv(struct.calcsize(b"!II")) + if header: + length, datahash = struct.unpack(b"!II", header) - # step 2: convert the data - return data.decode("utf8", "backslashreplace") + length = int(length) + data = bytearray() + while len(data) < length: + newData = self.sock.recv(length - len(data)) + data += newData + if not newData: + break + + if data and zlib.adler32(data) & 0xFFFFFFFF == datahash: + return data.decode("utf8", "backslashreplace") + + return "" def readline_p(self, size=-1): """ @@ -348,14 +355,14 @@ # convert to string to send it s = repr(s) - cmd = prepareJsonCommand( + jsonCommand = prepareJsonCommand( "ClientOutput", { "text": s, "debuggerId": "", }, ) - self.wpending.append(cmd) + self.wpending.append(jsonCommand) self.flush() self.writeLock.release()
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/DebugClientBase.py --- a/src/eric7/DebugClients/Python/DebugClientBase.py Fri Feb 02 19:29:29 2024 +0100 +++ b/src/eric7/DebugClients/Python/DebugClientBase.py Wed Feb 07 15:28:08 2024 +0100 @@ -890,9 +890,9 @@ if "debuggerId" not in params: params["debuggerId"] = self.__debuggerId - cmd = prepareJsonCommand(method, params) + jsonCommand = prepareJsonCommand(method, params) - self.writestream.write_p(cmd) + self.writestream.write_p(jsonCommand) self.writestream.flush() def sendClearTemporaryBreakpoint(self, filename, lineno):
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/DebugClients/Python/DebugUtilities.py --- a/src/eric7/DebugClients/Python/DebugUtilities.py Fri Feb 02 19:29:29 2024 +0100 +++ b/src/eric7/DebugClients/Python/DebugUtilities.py Wed Feb 07 15:28:08 2024 +0100 @@ -154,7 +154,7 @@ "method": method, "params": params, } - return json.dumps(commandDict) + "\n" + return json.dumps(commandDict) ###########################################################################
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/Debugger/DebuggerInterfaceNone.py --- a/src/eric7/Debugger/DebuggerInterfaceNone.py Fri Feb 02 19:29:29 2024 +0100 +++ b/src/eric7/Debugger/DebuggerInterfaceNone.py Wed Feb 07 15:28:08 2024 +0100 @@ -34,7 +34,7 @@ self.passive = passive self.qsock = None - self.queue = [] + self.__commandQueue = [] # set default values for capabilities of clients self.clientCapabilities = ClientDefaultCapabilities @@ -139,7 +139,7 @@ (Needed on Win OS) """ self.qsock = None - self.queue = [] + self.__commandQueue.clear() def isConnected(self): """
diff -r 70762114c59a -r c5b0c8a5fa7d src/eric7/Debugger/DebuggerInterfacePython.py --- a/src/eric7/Debugger/DebuggerInterfacePython.py Fri Feb 02 19:29:29 2024 +0100 +++ b/src/eric7/Debugger/DebuggerInterfacePython.py Wed Feb 07 15:28:08 2024 +0100 @@ -12,6 +12,8 @@ import logging import os import shlex +import struct +import zlib from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, QTimer @@ -53,7 +55,7 @@ self.process = None self.__startedVenv = "" - self.queue = [] + self.__commandQueue = [] self.__mainDebugger = None self.__connections = {} self.__pendingConnections = [] @@ -208,7 +210,7 @@ else str(Preferences.getDebugger("Python3Redirect")) ) noencoding = ( - Preferences.getDebugger("Python3NoEncoding") and "--no-encoding" or "" + "--no-encoding" if Preferences.getDebugger("Python3NoEncoding") else "" ) multiprocessEnabled = ( "--multiprocess" if Preferences.getDebugger("MultiProcessEnabled") else "" @@ -706,10 +708,10 @@ if self.__mainDebugger: # Send commands that were waiting for the connection. conn = self.__connections[self.__mainDebugger] - for cmd in self.queue: - self.__writeJsonCommandToSocket(cmd, conn) + for jsonStr in self.__commandQueue: + self.__writeJsonCommandToSocket(jsonStr, conn) - self.queue = [] + self.__commandQueue.clear() def shutdown(self): """ @@ -732,7 +734,7 @@ self.__shutdownSocket(sock) # reinitialize - self.queue = [] + self.__commandQueue.clear() self.__mainDebugger = None @@ -1401,14 +1403,27 @@ @param sock reference to the socket to read data from @type QTcpSocket """ - while sock and sock.canReadLine(): - qs = sock.readLine() - line = bytes(qs).decode(encoding=Preferences.getSystem("StringEncoding")) + while sock and sock.bytesAvailable(): + header = sock.read(struct.calcsize(b"!II")) + length, datahash = struct.unpack(b"!II", header) + + data = bytearray() + while len(data) < length: + maxSize = length - len(data) + if sock.bytesAvailable() < maxSize: + sock.waitForReadyRead(50) + data += sock.read(maxSize) - logging.debug("<Debug-Server> %s", line) - ##print("Server: ", line) ## debug # __IGNORE_WARNING_M891__ + if zlib.adler32(data) & 0xFFFFFFFF != datahash: + # corrupted data -> discard and continue + continue - self.__handleJsonCommand(line, sock) + jsonStr = data.decode("utf-8", "backslashreplace") + + logging.debug("<Debug-Server> %s", jsonStr) + ##print("Server: ", jsonStr) ## debug # __IGNORE_WARNING_M891__ + + self.__handleJsonCommand(jsonStr, sock) def __handleJsonCommand(self, jsonStr, sock): """ @@ -1646,29 +1661,30 @@ "method": command, "params": params, } - cmd = json.dumps(commandDict) + "\n" + jsonStr = json.dumps(commandDict) if debuggerId and debuggerId in self.__connections: sock = self.__connections[debuggerId] elif sock is None and self.__mainDebugger is not None: sock = self.__connections[self.__mainDebugger] if sock is not None: - self.__writeJsonCommandToSocket(cmd, sock) + self.__writeJsonCommandToSocket(jsonStr, sock) else: - self.queue.append(cmd) + self.__commandQueue.append(jsonStr) - def __writeJsonCommandToSocket(self, cmd, sock): + def __writeJsonCommandToSocket(self, jsonCommand, sock): """ Private method to write a JSON command to the socket. - @param cmd JSON command to be sent + @param jsonCommand JSON encoded 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)) - sock.write(length.encode() + data) + data = jsonCommand.encode("utf8", "backslashreplace") + header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) + sock.write(header) + sock.write(data) sock.flush()