def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first." ) # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption( "external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first." ) # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() sleep(0.1) Logger.log("d", "SOCKET CLOSED !!!") self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode( sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _createSocket(self, protocol_file): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # If the error occured due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _createSocket(self): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) self._socket.listen("127.0.0.1", self._port) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port)
def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode(sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _createSocket(self, protocol_file): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # If the error occured due to parsing, both connections believe that connection is okay. So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _createSocket(self): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) self._socket.listen("127.0.0.1", self._port)
class Backend(PluginObject): def __init__(self): super().__init__() # Call super to make multiple inheritance work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._process = None # type: Optional[subprocess.Popen] self._backend_log = [] self._backend_log_max_lines = None self._backend_state = BackendState.NotStarted UM.Application.Application.getInstance().callLater(self._createSocket) processingProgress = Signal() backendStateChange = Signal() backendConnected = Signal() backendQuit = Signal() def setState(self, new_state): if new_state != self._backend_state: self._backend_state = new_state self.backendStateChange.emit(self._backend_state) ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opened & ready to accept connections def startEngine(self): command = self.getEngineCommand() if not command: self._createSocket() return if not self._backend_log_max_lines: self._backend_log = [] # Double check that the old process is indeed killed. if self._process is not None: self._process.terminate() Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) self._process = self._runEngineProcess(command) if self._process is None: # Failed to start engine. return Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0]) self._backendLog( bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout, )) t.daemon = True t.start() t = threading.Thread(target=self._storeStderrToLogThread, args=(self._process.stderr, )) t.daemon = True t.start() def close(self): if self._socket: while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) self._socket.close() def _backendLog(self, line): try: line_str = line.decode("utf-8") except UnicodeDecodeError: line_str = line.decode( "latin1" ) #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. Logger.log("d", "[Backend] " + line_str.strip()) self._backend_log.append(line) ## Get the logging messages of the backend connection. # \returns def getLog(self): if self._backend_log_max_lines and type( self._backend_log_max_lines) == int: while len(self._backend_log) >= self._backend_log_max_lines: del (self._backend_log[0]) return self._backend_log ## \brief Convert byte array containing 3 floats per vertex def convertBytesToVerticeList(self, data): result = [] if not (len(data) % 12): if data is not None: for index in range(0, int(len(data) / 12)): # For each 12 bits (3 floats) result.append( struct.unpack("fff", data[index * 12:index * 12 + 12])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## \brief Convert byte array containing 6 floats per vertex def convertBytesToVerticeWithNormalsList(self, data): result = [] if not (len(data) % 24): if data is not None: for index in range(0, int(len(data) / 24)): # For each 24 bits (6 floats) result.append( struct.unpack("ffffff", data[index * 24:index * 24 + 24])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## Get the command used to start the backend executable def getEngineCommand(self): return [ UM.Application.Application.getInstance().getPreferences().getValue( "backend/location"), "--port", str(self._socket.getPort()) ] ## Start the (external) backend process. def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]: kwargs = {} #type: Dict[str, Any] if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 # BELOW_NORMAL_PRIORITY_CLASS try: return subprocess.Popen(command_list, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) except PermissionError: Logger.log( "e", "Couldn't start back-end: No permission to execute process.") except FileNotFoundError: Logger.logException("e", "Unable to find backend executable: %s", command_list[0]) return None def _storeOutputToLogThread(self, handle): while True: line = handle.readline() if line == b"": self.backendQuit.emit() break self._backendLog(line) def _storeStderrToLogThread(self, handle): while True: line = handle.readline() if line == b"": break self._backendLog(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): self._logSocketState(state) if state == Arcus.SocketState.Listening: if not UM.Application.Application.getInstance( ).getUseExternalBackend(): self.startEngine() elif state == Arcus.SocketState.Connected: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Debug function created to provide more info for CURA-2127 def _logSocketState(self, state): if state == Arcus.SocketState.Listening: Logger.log("d", "Socket state changed to Listening") elif state == Arcus.SocketState.Connecting: Logger.log("d", "Socket state changed to Connecting") elif state == Arcus.SocketState.Connected: Logger.log("d", "Socket state changed to Connected") elif state == Arcus.SocketState.Error: Logger.log("d", "Socket state changed to Error") elif state == Arcus.SocketState.Closing: Logger.log("d", "Socket state changed to Closing") elif state == Arcus.SocketState.Closed: Logger.log("d", "Socket state changed to Closed") ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if message.getTypeName() not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", message.getTypeName()) return self._message_handlers[message.getTypeName()](message) ## Private socket error handler def _onSocketError(self, error): if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: self._port += 1 Logger.log( "d", "Socket was unable to bind to port, increasing port number to %s", self._port) elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError: Logger.log("i", "Backend crashed or closed.") elif error.getErrorCode() == Arcus.ErrorCode.Debug: Logger.log("d", "Socket debug: %s", str(error)) return else: Logger.log("w", "Unhandled socket error %s", str(error)) self._createSocket() ## Creates a socket and attaches listeners. def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first." ) # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode( sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
class Backend(PluginObject, SignalEmitter): def __init__(self): super().__init__() # Call super to make multiple inheritence work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._createSocket() self._process = None self._backend_log = [] processingProgress = Signal() backendConnected = Signal() ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opend & ready to accept connections def startEngine(self): try: self._backend_log = [] self._process = self._runEngineProcess(self.getEngineCommand()) Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0])) self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), 'utf-8')) t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout,)) t.daemon = True t.start() t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stderr,)) t.daemon = True t.start() except FileNotFoundError as e: Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0])) def close(self): if self._socket: self._socket.close() ## Get the logging messages of the backend connection. # \returns def getLog(self): return self._backend_log ## \brief Convert byte array containing 3 floats per vertex def convertBytesToVerticeList(self, data): result = [] if not (len(data) % 12): if data is not None: for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats) result.append(struct.unpack("fff",data[index*12:index*12+12])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## \brief Convert byte array containing 6 floats per vertex def convertBytesToVerticeWithNormalsList(self,data): result = [] if not (len(data) % 24): if data is not None: for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats) result.append(struct.unpack("ffffff",data[index*24:index*24+24])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## Get the command used to start the backend executable def getEngineCommand(self): return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket_thread.getPort())] ## Start the (external) backend process. def _runEngineProcess(self, command_list): kwargs = {} if subprocess.mswindows: su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS return subprocess.Popen(command_list, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) def _storeOutputToLogThread(self, handle): while True: line = handle.readline() if line == b"": break self._backend_log.append(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): if state == SignalSocket.ListeningState: if not Application.getInstance().getCommandLineOption("external-backend", False): self.startEngine() elif state == SignalSocket.ConnectedState: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if type(message) not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", type(message)) return self._message_handlers[type(message)](message) ## Private socket error handler def _onSocketError(self, error): if error.errno == 98 or error.errno == 48:# Socked in use error self._port += 1 self._createSocket() elif error.errno == 104 or error.errno == 32 or error.errno == 54 or error.errno == 41: # 104 is connection reset by peer. 32 is broken pipe. 54 is also connection reset by peer. # 41 is specific for MacOSX and happens when closing a socket. # All these imply the connection to the backend was broken and we need to restart it. Logger.log("i", "Backend crashed or closed. Restarting...") self._createSocket() elif platform.system() == "Windows": if error.winerror == 10048:# Socked in use error self._port += 1 self._createSocket() elif error.winerror == 10054: Logger.log("i", "Backend crashed or closed. Restarting...") self._createSocket() else: Logger.log("e", str(error)) ## Creates a socket and attaches listeners. def _createSocket(self): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) self._socket.listen("127.0.0.1", self._port) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port)
class Backend(PluginObject): def __init__(self): super().__init__() # Call super to make multiple inheritance work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._process = None # type: Optional[subprocess.Popen] self._backend_log = [] self._backend_log_max_lines = None self._backend_state = BackendState.NotStarted UM.Application.Application.getInstance().callLater(self._createSocket) processingProgress = Signal() backendStateChange = Signal() backendConnected = Signal() backendQuit = Signal() def setState(self, new_state): if new_state != self._backend_state: self._backend_state = new_state self.backendStateChange.emit(self._backend_state) ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opened & ready to accept connections def startEngine(self): command = self.getEngineCommand() if not command: self._createSocket() return if not self._backend_log_max_lines: self._backend_log = [] # Double check that the old process is indeed killed. if self._process is not None: self._process.terminate() Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) self._process = self._runEngineProcess(command) if self._process is None: # Failed to start engine. return Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0]) self._backendLog(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,)) t.daemon = True t.start() t = threading.Thread(target = self._storeStderrToLogThread, args = (self._process.stderr,)) t.daemon = True t.start() def close(self): if self._socket: while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) self._socket.close() def _backendLog(self, line): try: line_str = line.decode("utf-8") except UnicodeDecodeError: line_str = line.decode("latin1") #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. Logger.log("d", "[Backend] " + line_str.strip()) self._backend_log.append(line) ## Get the logging messages of the backend connection. # \returns def getLog(self): if self._backend_log_max_lines and type(self._backend_log_max_lines) == int: while len(self._backend_log) >= self._backend_log_max_lines: del(self._backend_log[0]) return self._backend_log ## Get the command used to start the backend executable def getEngineCommand(self): return [UM.Application.Application.getInstance().getPreferences().getValue("backend/location"), "--port", str(self._socket.getPort())] ## Start the (external) backend process. def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]: kwargs = {} #type: Dict[str, Any] if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 # BELOW_NORMAL_PRIORITY_CLASS try: return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs) except PermissionError: Logger.log("e", "Couldn't start back-end: No permission to execute process.") except FileNotFoundError: Logger.logException("e", "Unable to find backend executable: %s", command_list[0]) except BlockingIOError: Logger.log("e", "Couldn't start back-end: Resource is temporarily unavailable") return None def _storeOutputToLogThread(self, handle): while True: try: line = handle.readline() except OSError: Logger.logException("w", "Exception handling stdout log from backend.") continue if line == b"": self.backendQuit.emit() break self._backendLog(line) def _storeStderrToLogThread(self, handle): while True: try: line = handle.readline() except OSError: Logger.logException("w", "Exception handling stderr log from backend.") continue if line == b"": break self._backendLog(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): self._logSocketState(state) if state == Arcus.SocketState.Listening: if not UM.Application.Application.getInstance().getUseExternalBackend(): self.startEngine() elif state == Arcus.SocketState.Connected: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Debug function created to provide more info for CURA-2127 def _logSocketState(self, state): if state == Arcus.SocketState.Listening: Logger.log("d", "Socket state changed to Listening") elif state == Arcus.SocketState.Connecting: Logger.log("d", "Socket state changed to Connecting") elif state == Arcus.SocketState.Connected: Logger.log("d", "Socket state changed to Connected") elif state == Arcus.SocketState.Error: Logger.log("d", "Socket state changed to Error") elif state == Arcus.SocketState.Closing: Logger.log("d", "Socket state changed to Closing") elif state == Arcus.SocketState.Closed: Logger.log("d", "Socket state changed to Closed") ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if message.getTypeName() not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", message.getTypeName()) return self._message_handlers[message.getTypeName()](message) ## Private socket error handler def _onSocketError(self, error): if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: self._port += 1 Logger.log("d", "Socket was unable to bind to port, increasing port number to %s", self._port) elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError: Logger.log("i", "Backend crashed or closed.") elif error.getErrorCode() == Arcus.ErrorCode.Debug: Logger.log("d", "Socket debug: %s", str(error)) return else: Logger.log("w", "Unhandled socket error %s", str(error)) self._createSocket() ## Creates a socket and attaches listeners. def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode(sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
class Backend(PluginObject, SignalEmitter): def __init__(self): super().__init__() # Call super to make multiple inheritence work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._process = None self._backend_log = [] Application.getInstance().callLater(self._createSocket) processingProgress = Signal() backendStateChange = Signal() backendConnected = Signal() backendQuit = Signal() ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opend & ready to accept connections def startEngine(self): try: command = self.getEngineCommand() if not command: self._createSocket() return self._backend_log = [] self._process = self._runEngineProcess(command) Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0])) self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,)) t.daemon = True t.start() t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stderr,)) t.daemon = True t.start() except FileNotFoundError as e: Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0])) def close(self): if self._socket: self._socket.close() ## Get the logging messages of the backend connection. # \returns def getLog(self): return self._backend_log ## \brief Convert byte array containing 3 floats per vertex def convertBytesToVerticeList(self, data): result = [] if not (len(data) % 12): if data is not None: for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats) result.append(struct.unpack("fff", data[index * 12: index * 12 + 12])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## \brief Convert byte array containing 6 floats per vertex def convertBytesToVerticeWithNormalsList(self,data): result = [] if not (len(data) % 24): if data is not None: for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats) result.append(struct.unpack("ffffff",data[index * 24: index * 24 + 24])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## Get the command used to start the backend executable def getEngineCommand(self): return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())] ## Start the (external) backend process. def _runEngineProcess(self, command_list): kwargs = {} if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs) def _storeOutputToLogThread(self, handle): while True: line = handle.readline() if line == b"": self.backendQuit.emit() break self._backend_log.append(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): if state == Arcus.SocketState.Listening: if not Application.getInstance().getCommandLineOption("external-backend", False): self.startEngine() elif state == Arcus.SocketState.Connected: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if message.getTypeName() not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", type(message)) return self._message_handlers[message.getTypeName()](message) ## Private socket error handler def _onSocketError(self, error): if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: self._port += 1 elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError: Logger.log("i", "Backend crashed or closed. Restarting...") elif error.getErrorCode() == Arcus.ErrorCode.Debug: Logger.log("d", str(error)) return else: Logger.log("w", str(error)) sleep(0.1) #Hack: Withouth a sleep this can deadlock the application spamming error messages. self._createSocket() ## Creates a socket and attaches listeners. def _createSocket(self, protocol_file): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # If the error occured due to parsing, both connections believe that connection is okay. So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
class Backend(PluginObject, SignalEmitter): def __init__(self): super().__init__() # Call super to make multiple inheritence work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._createSocket() self._process = None self._backend_log = [] processingProgress = Signal() backendConnected = Signal() ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opend & ready to accept connections def startEngine(self): try: command = self.getEngineCommand() if not command: self._createSocket() return self._backend_log = [] self._process = self._runEngineProcess(command) Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0])) self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), 'utf-8')) t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout,)) t.daemon = True t.start() t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stderr,)) t.daemon = True t.start() except FileNotFoundError as e: Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0])) def close(self): if self._socket: self._socket.close() ## Get the logging messages of the backend connection. # \returns def getLog(self): return self._backend_log ## \brief Convert byte array containing 3 floats per vertex def convertBytesToVerticeList(self, data): result = [] if not (len(data) % 12): if data is not None: for index in range(0,int(len(data)/12)): #For each 12 bits (3 floats) result.append(struct.unpack("fff",data[index*12:index*12+12])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## \brief Convert byte array containing 6 floats per vertex def convertBytesToVerticeWithNormalsList(self,data): result = [] if not (len(data) % 24): if data is not None: for index in range(0,int(len(data)/24)): #For each 24 bits (6 floats) result.append(struct.unpack("ffffff",data[index*24:index*24+24])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## Get the command used to start the backend executable def getEngineCommand(self): return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())] ## Start the (external) backend process. def _runEngineProcess(self, command_list): kwargs = {} if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS return subprocess.Popen(command_list, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) def _storeOutputToLogThread(self, handle): while True: line = handle.readline() if line == b"": break self._backend_log.append(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): if state == SignalSocket.ListeningState: if not Application.getInstance().getCommandLineOption("external-backend", False): self.startEngine() elif state == SignalSocket.ConnectedState: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if type(message) not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", type(message)) return self._message_handlers[type(message)](message) ## Private socket error handler def _onSocketError(self, error): try: if error.errno == 98 or error.errno == 48:# Socked in use error self._port += 1 elif error.errno == 104 or error.errno == 32 or error.errno == 54 or error.errno == 41: # 104 is connection reset by peer. 32 is broken pipe. 54 is also connection reset by peer. # 41 is specific for MacOSX and happens when closing a socket. # All these imply the connection to the backend was broken and we need to restart it. Logger.log("i", "Backend crashed or closed. Restarting...") elif platform.system() == "Windows": if error.winerror == 10048:# Socked in use error self._port += 1 elif error.winerror == 10054: Logger.log("i", "Backend crashed or closed. Restarting...") else: Logger.log("e", str(error)) except Exception as e: Logger.log("e", "Failed to parse socket error") self._createSocket() ## Creates a socket and attaches listeners. def _createSocket(self): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) self._socket.listen("127.0.0.1", self._port) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port)
class Backend(PluginObject): """Base class for any backend communication (separate piece of software). It makes use of the Socket class from libArcus for the actual communication bits. The message_handlers dict should be filled with string (full name of proto message), function pairs. """ def __init__(self): super().__init__() # Call super to make multiple inheritance work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._process = None # type: Optional[subprocess.Popen] self._backend_log = [] self._backend_log_max_lines = None self._backend_state = BackendState.NotStarted UM.Application.Application.getInstance().callLater(self._createSocket) processingProgress = Signal() backendStateChange = Signal() backendConnected = Signal() backendQuit = Signal() def setState(self, new_state): if new_state != self._backend_state: self._backend_state = new_state self.backendStateChange.emit(self._backend_state) def startEngine(self): """:brief Start the backend / engine. Runs the engine, this is only called when the socket is fully opened & ready to accept connections """ command = self.getEngineCommand() if not command: self._createSocket() return if not self._backend_log_max_lines: self._backend_log = [] # Double check that the old process is indeed killed. if self._process is not None: try: self._process.terminate() except PermissionError: Logger.log("e", "Unable to kill running engine. Access is denied.") return Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) self._process = self._runEngineProcess(command) if self._process is None: # Failed to start engine. return Logger.log("i", "Started engine process: %s", self.getEngineCommand()[0]) self._backendLog( bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) t = threading.Thread(target=self._storeOutputToLogThread, args=(self._process.stdout, ), name="EngineOutputThread") t.daemon = True t.start() t = threading.Thread(target=self._storeStderrToLogThread, args=(self._process.stderr, ), name="EngineErrorThread") t.daemon = True t.start() def close(self): if self._socket: while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) self._socket.close() def _backendLog(self, line): try: line_str = line.decode("utf-8") except UnicodeDecodeError: line_str = line.decode( "latin1" ) #Latin-1 as a fallback since it can never give decoding errors. All characters are 1 byte. Logger.log("d", "[Backend] " + line_str.strip()) self._backend_log.append(line) def getLog(self): """Get the logging messages of the backend connection.""" if self._backend_log_max_lines and type( self._backend_log_max_lines) == int: while len(self._backend_log) >= self._backend_log_max_lines: del (self._backend_log[0]) return self._backend_log def getEngineCommand(self): """Get the command used to start the backend executable """ return [ UM.Application.Application.getInstance().getPreferences().getValue( "backend/location"), "--port", str(self._socket.getPort()) ] def _runEngineProcess(self, command_list) -> Optional[subprocess.Popen]: """Start the (external) backend process.""" kwargs = {} #type: Dict[str, Any] if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 # BELOW_NORMAL_PRIORITY_CLASS try: # STDIN needs to be None because we provide no input, but communicate via a local socket instead. The NUL device sometimes doesn't exist on some computers. # STDOUT and STDERR need to be pipes because we'd like to log the output on those channels into the application log. return subprocess.Popen(command_list, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) except PermissionError: Logger.log( "e", "Couldn't start back-end: No permission to execute process.") except FileNotFoundError: Logger.logException("e", "Unable to find backend executable: %s", command_list[0]) except BlockingIOError: Logger.log( "e", "Couldn't start back-end: Resource is temporarily unavailable") except OSError as e: Logger.log( "e", "Couldn't start back-end: Operating system is blocking it (antivirus?): {err}" .format(err=str(e))) return None def _storeOutputToLogThread(self, handle): while True: try: line = handle.readline() except OSError: Logger.logException( "w", "Exception handling stdout log from backend.") continue if line == b"": self.backendQuit.emit() break self._backendLog(line) def _storeStderrToLogThread(self, handle): while True: try: line = handle.readline() except OSError: Logger.logException( "w", "Exception handling stderr log from backend.") continue if line == b"": break self._backendLog(line) def _onSocketStateChanged(self, state): """Private socket state changed handler.""" self._logSocketState(state) if state == Arcus.SocketState.Listening: if not UM.Application.Application.getInstance( ).getUseExternalBackend(): self.startEngine() elif state == Arcus.SocketState.Connected: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() def _logSocketState(self, state): """Debug function created to provide more info for CURA-2127""" if state == Arcus.SocketState.Listening: Logger.log("d", "Socket state changed to Listening") elif state == Arcus.SocketState.Connecting: Logger.log("d", "Socket state changed to Connecting") elif state == Arcus.SocketState.Connected: Logger.log("d", "Socket state changed to Connected") elif state == Arcus.SocketState.Error: Logger.log("d", "Socket state changed to Error") elif state == Arcus.SocketState.Closing: Logger.log("d", "Socket state changed to Closing") elif state == Arcus.SocketState.Closed: Logger.log("d", "Socket state changed to Closed") def _onMessageReceived(self): """Private message handler""" message = self._socket.takeNextMessage() if message.getTypeName() not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", message.getTypeName()) return self._message_handlers[message.getTypeName()](message) def _onSocketError(self, error): """Private socket error handler""" if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: self._port += 1 Logger.log( "d", "Socket was unable to bind to port, increasing port number to %s", self._port) elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError: Logger.log("i", "Backend crashed or closed.") elif error.getErrorCode() == Arcus.ErrorCode.Debug: Logger.log("d", "Socket debug: %s", str(error)) return else: Logger.log("w", "Unhandled socket error %s", str(error)) self._createSocket() def _createSocket(self, protocol_file): """Creates a socket and attaches listeners.""" if self._socket: Logger.log("d", "Previous socket existed. Closing that first." ) # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode( sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
class Backend(PluginObject): def __init__(self): super().__init__() # Call super to make multiple inheritance work. self._supported_commands = {} self._message_handlers = {} self._socket = None self._port = 49674 self._process = None self._backend_log = [] Application.getInstance().callLater(self._createSocket) processingProgress = Signal() backendStateChange = Signal() backendConnected = Signal() backendQuit = Signal() ## \brief Start the backend / engine. # Runs the engine, this is only called when the socket is fully opened & ready to accept connections def startEngine(self): try: command = self.getEngineCommand() if not command: self._createSocket() return self._backend_log = [] self._process = self._runEngineProcess(command) Logger.log("i", "Started engine process: %s" % (self.getEngineCommand()[0])) self._backend_log.append(bytes("Calling engine with: %s\n" % self.getEngineCommand(), "utf-8")) t = threading.Thread(target = self._storeOutputToLogThread, args = (self._process.stdout,)) t.daemon = True t.start() t = threading.Thread(target = self._storeStderrToLogThread, args = (self._process.stderr,)) t.daemon = True t.start() except FileNotFoundError as e: Logger.log("e", "Unable to find backend executable: %s" % (self.getEngineCommand()[0])) def close(self): if self._socket: self._socket.close() ## Get the logging messages of the backend connection. # \returns def getLog(self): return self._backend_log ## \brief Convert byte array containing 3 floats per vertex def convertBytesToVerticeList(self, data): result = [] if not (len(data) % 12): if data is not None: for index in range(0, int(len(data) / 12)): # For each 12 bits (3 floats) result.append(struct.unpack("fff", data[index * 12: index * 12 + 12])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## \brief Convert byte array containing 6 floats per vertex def convertBytesToVerticeWithNormalsList(self,data): result = [] if not (len(data) % 24): if data is not None: for index in range(0,int(len(data)/24)): # For each 24 bits (6 floats) result.append(struct.unpack("ffffff", data[index * 24: index * 24 + 24])) return result else: Logger.log("e", "Data length was incorrect for requested type") return None ## Get the command used to start the backend executable def getEngineCommand(self): return [Preferences.getInstance().getValue("backend/location"), "--port", str(self._socket.getPort())] ## Start the (external) backend process. def _runEngineProcess(self, command_list): kwargs = {} if sys.platform == "win32": su = subprocess.STARTUPINFO() su.dwFlags |= subprocess.STARTF_USESHOWWINDOW su.wShowWindow = subprocess.SW_HIDE kwargs["startupinfo"] = su kwargs["creationflags"] = 0x00004000 # BELOW_NORMAL_PRIORITY_CLASS return subprocess.Popen(command_list, stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs) def _storeOutputToLogThread(self, handle): while True: line = handle.readline() if line == b"": self.backendQuit.emit() break self._backend_log.append(line) def _storeStderrToLogThread(self, handle): while True: line = handle.readline() if line == b"": break self._backend_log.append(line) ## Private socket state changed handler. def _onSocketStateChanged(self, state): if state == Arcus.SocketState.Listening: if not Application.getInstance().getCommandLineOption("external-backend", False): self.startEngine() elif state == Arcus.SocketState.Connected: Logger.log("d", "Backend connected on port %s", self._port) self.backendConnected.emit() ## Private message handler def _onMessageReceived(self): message = self._socket.takeNextMessage() if message.getTypeName() not in self._message_handlers: Logger.log("e", "No handler defined for message of type %s", message.getTypeName()) return self._message_handlers[message.getTypeName()](message) ## Private socket error handler def _onSocketError(self, error): if error.getErrorCode() == Arcus.ErrorCode.BindFailedError: self._port += 1 elif error.getErrorCode() == Arcus.ErrorCode.ConnectionResetError: Logger.log("i", "Backend crashed or closed. Restarting...") elif error.getErrorCode() == Arcus.ErrorCode.Debug: Logger.log("d", str(error)) return else: Logger.log("w", str(error)) sleep(0.1) # Hack: Without a sleep this can deadlock the application spamming error messages. self._createSocket() ## Creates a socket and attaches listeners. def _createSocket(self, protocol_file): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # If the error occured due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)