--- a/Utilities/BackgroundService.py Thu May 15 18:45:07 2014 +0200 +++ b/Utilities/BackgroundService.py Fri May 16 23:36:27 2014 +0200 @@ -17,10 +17,12 @@ import sys from zlib import adler32 -from PyQt4.QtCore import QProcess, pyqtSignal +from PyQt4.QtCore import QProcess, pyqtSignal, QTimer from PyQt4.QtGui import QApplication from PyQt4.QtNetwork import QTcpServer, QHostAddress +from E5Gui import E5MessageBox +from E5Gui.E5Application import e5App import Preferences import Utilities @@ -37,9 +39,10 @@ """ Constructor of the BackgroundService class. """ - self.processes = [] + self.processes = {} self.connections = {} self.isWorking = None + self.runningJob = [None, None, None, None] self.__queue = [] self.services = {} @@ -62,7 +65,9 @@ pyName + "Interpreter") process = self.__startExternalClient(interpreter, port) if process: - self.processes.append(process) + if pyName == 'Python': + pyName = 'Python2' + self.processes[pyName] = process, interpreter def __startExternalClient(self, interpreter, port): """ @@ -93,6 +98,7 @@ if self.__queue and self.isWorking is None: fx, lang, fn, data = self.__queue.pop(0) self.isWorking = lang + self.runningJob = fx, lang, fn, data self.__send(fx, lang, fn, data) def __send(self, fx, lang, fn, data): @@ -107,9 +113,13 @@ connection = self.connections.get(lang) if connection is None: if fx != 'INIT': - self.serviceNotAvailable.emit( - fx, lang, fn, self.tr( - '{0} not configured.').format(lang)) + # Avoid growing recursion deep which could itself result in an + # exception + QTimer.singleShot( + 0, + lambda: self.serviceNotAvailable.emit( + fx, lang, fn, self.tr( + '{0} not configured.').format(lang))) # Reset flag and continue processing queue self.isWorking = None self.__processQueue() @@ -146,10 +156,40 @@ if fx == 'INIT': pass elif fx == 'EXCEPTION': + # Remove connection because it'll close anyway + self.connections.pop(lang, None) # Call sys.excepthook(type, value, traceback) to emulate the # exception which was caught on the client sys.excepthook(*data) - QApplication.processEvents() + res = E5MessageBox.question( + None, + self.tr("Restart background client?"), + self.tr( + "<p>The background client for <b>{0}</b> has stopped" + " due to an exception. It's used by various plug-ins like" + " the different checkers.</p>" + "<p>Select<br>" + "<b>'Yes'</b> to restart the client, but abort the last" + " job<br>" + "<b>'Retry'</b> to restart the client and the last job<br>" + "<b>'No'</b> to leave the client off.</p>" + "<p>Note: The client can be restarted by opening and" + " accepting the preferences dialog or reloading/ changing" + " the project.</p>").format(lang), + E5MessageBox.Yes | E5MessageBox.No | E5MessageBox.Retry, + E5MessageBox.Yes) + + if res == E5MessageBox.Retry: + self.enqueueRequest(*self.runningJob) + else: + fx, lng, fn, data = self.runningJob + self.services[(fx, lng)][3](fx, lng, fn, self.tr( + 'An error in Erics background client stopped the service.') + ) + if res != E5MessageBox.No: + self.isWorking = None + self.restartService(lang, True) + return elif data == 'Unknown service.': callback = self.services.get((fx, lang)) if callback: @@ -162,6 +202,52 @@ self.isWorking = None self.__processQueue() + def preferencesOrProjectChanged(self): + """ + Public slot to restart the built in languages. + """ + for pyName in ['Python', 'Python3']: + interpreter = Preferences.getDebugger( + pyName + "Interpreter") + + if pyName == 'Python': + pyName = 'Python2' + + # Tweak the processes list to reflect the changed interpreter + proc, inter = self.processes.pop(pyName, [None, None]) + self.processes[pyName] = proc, interpreter + + self.restartService(pyName) + + def restartService(self, language, forceKill=False): + """ + Public method to restart a given lanuage. + + @param language to restart (str) + @keyparam forceKill flag to kill a running task (bool) + """ + try: + proc, interpreter = self.processes.pop(language) + except KeyError: + return + + # Don't kill a process if it's still working + if not forceKill: + while self.isWorking is not None: + QApplication.processEvents() + + conn = self.connections.pop(language, None) + if conn: + conn.blockSignals(True) + conn.close() + if proc: + proc.close() + + port = self.serverPort() + process = self.__startExternalClient(interpreter, port) + if process: + self.processes[language] = process, interpreter + def enqueueRequest(self, fx, lang, fn, data): """ Implement a queued processing of incomming events. @@ -242,37 +328,53 @@ if lng == lang: # Register service with modulepath and module self.enqueueRequest('INIT', lng, fx, args[:2]) + + # Syntax check the open editors again + try: + vm = e5App().getObject("ViewManager") + except KeyError: + return + for editor in vm.getOpenEditors(): + if editor.getLanguage() == lang: + QTimer.singleShot(0, editor.checkSyntax) def on_disconnectSocket(self, lang): """ - Slot when connection to a client is lost. + Slot is called when connection to a client is lost. @param lang client language which connection is lost (str) """ - self.connections.pop(lang) - # Maybe the task is killed while ideling - if self.isWorking == lang: + conn = self.connections.pop(lang, None) + if conn: + conn.close() + fx, lng, fn, data = self.runningJob + if fx != 'INIT' and lng == lang: + self.services[(fx, lng)][3](fx, lng, fn, self.tr( + 'Erics background client disconnected because of an' + ' unknown reason.') + ) self.isWorking = None - # Remove pending jobs and send warning to the waiting caller - # Make a copy of the list because it's modified in the loop - for args in self.__queue[:]: - fx, lng, fn, data = args - if lng == lang: - # Call onErrorCallback with error message - self.__queue.remove(args) - self.services[(fx, lng)][3](fx, fn, lng, self.tr( - 'Error in Erics background service stopped service.')) - + + res = E5MessageBox.yesNo( + None, + self.tr('Background client disconnected.'), + self.tr( + 'The background client for <b>{0}</b> disconnect because' + ' of an unknown reason.<br>Should it be restarted?' + ).format(lang), + yesDefault=True) + if res: + self.restartService(lang) + def shutdown(self): """ - Cleanup the connections and processes when Eric is shuting down. + Cleanup the connections and processes when eric is shuting down. """ - # Make copy of dictionary values because the list is changed by - # on_disconnectSocket - for connection in list(self.connections.values()): - if connection: - connection.close() + for connection in self.connections.values(): + # Prevent calling of on_disconnectSocket + connection.blockSignals(True) + connection.close() - for process in self.processes: + for process, interpreter in self.processes.values(): process.close() process = None