Utilities/BackgroundService.py

changeset 3579
eccd12461319
parent 3538
33a75660df08
child 3581
03b351be4436
--- 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

eric ide

mercurial