Implemented the Cancel logic for batch checks.

Sat, 18 Apr 2015 19:14:15 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 18 Apr 2015 19:14:15 +0200
changeset 4221
c9fdc07753a7
parent 4220
4df8f9fc7ea9
child 4222
1db92cbf62c9

Implemented the Cancel logic for batch checks.

Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py file | annotate | diff | comparison | revisions
Plugins/PluginCodeStyleChecker.py file | annotate | diff | comparison | revisions
Utilities/BackgroundClient.py file | annotate | diff | comparison | revisions
Utilities/BackgroundService.py file | annotate | diff | comparison | revisions
--- a/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py	Fri Apr 17 18:57:38 2015 +0200
+++ b/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py	Sat Apr 18 19:14:15 2015 +0200
@@ -112,25 +112,30 @@
     return __checkCodeStyle(filename, source, args)
 
 
-def codeStyleBatchCheck(argumentsList, send, fx):
+def codeStyleBatchCheck(argumentsList, send, fx, cancelled):
     """
     Module function to check code style for a batch of files.
     
     @param argumentsList list of arguments tuples as given for codeStyleCheck
-    @param send reference to send method
+    @param send reference to send function (function)
     @param fx registered service name (string)
+    @param cancelled reference to function checking for a cancellation
+        (function)
     """
     try:
         NumberOfProcesses = multiprocessing.cpu_count()
+        if NumberOfProcesses >= 1:
+            NumberOfProcesses -= 1
     except NotImplementedError:
-        NumberOfProcesses = 4
+        NumberOfProcesses = 1
 
     # Create queues
     taskQueue = multiprocessing.Queue()
     doneQueue = multiprocessing.Queue()
 
-    # Submit tasks
-    for task in argumentsList:
+    # Submit tasks (initially two time number of processes
+    initialTasks = 2 * NumberOfProcesses
+    for task in argumentsList[:initialTasks]:
         taskQueue.put(task)
 
     # Start worker processes
@@ -139,9 +144,15 @@
             .start()
 
     # Get and send results
+    endIndex = len(argumentsList) - initialTasks
     for i in range(len(argumentsList)):
         filename, result = doneQueue.get()
         send(fx, filename, result)
+        if cancelled():
+            # just exit the loop ignoring the results of queued tasks
+            break
+        if i < endIndex:
+            taskQueue.put(argumentsList[i + initialTasks])
 
     # Tell child processes to stop
     for i in range(NumberOfProcesses):
--- a/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Fri Apr 17 18:57:38 2015 +0200
+++ b/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Sat Apr 18 19:14:15 2015 +0200
@@ -28,7 +28,6 @@
 from . import pep8
 
 
-# TODO: implement CANCEL for batch jobs (if possible)
 class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog):
     """
     Class implementing a dialog to show the results of the code style check.
@@ -445,9 +444,6 @@
         """
         self.__lastFileItem = None
         
-        # Cancel doesn't work for batch jobs
-        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
-        
         self.checkProgressLabel.setPath(self.tr("Preparing files..."))
         progress = 0
         
@@ -587,10 +583,8 @@
         if self.noResults:
             QTreeWidgetItem(self.resultList, [self.tr('No issues found.')])
             QApplication.processEvents()
-            self.statisticsButton.setEnabled(False)
             self.showButton.setEnabled(False)
         else:
-            self.statisticsButton.setEnabled(True)
             self.showButton.setEnabled(True)
         self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
         self.resultList.header().setStretchLastSection(True)
@@ -856,7 +850,10 @@
         if button == self.buttonBox.button(QDialogButtonBox.Close):
             self.close()
         elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
-            self.__finish()
+            if self.__batch:
+                self.styleCheckService.cancelStyleBatchCheck()
+            else:
+                self.__finish()
         elif button == self.showButton:
             self.on_showButton_clicked()
         elif button == self.statisticsButton:
--- a/Plugins/PluginCodeStyleChecker.py	Fri Apr 17 18:57:38 2015 +0200
+++ b/Plugins/PluginCodeStyleChecker.py	Sat Apr 18 19:14:15 2015 +0200
@@ -203,6 +203,13 @@
                                                       data[lang])
                 self.batchesFinished = False
     
+    def cancelStyleBatchCheck(self):
+        """
+        Public method to cancel all batch jobs.
+        """
+        for lang in ['Python2', 'Python3']:
+            self.backgroundService.requestCancel(lang)
+    
     def __translateStyleCheck(self, fn, codeStyleCheckerStats, results):
         """
         Private slot called after perfoming a style check on one file.
--- a/Utilities/BackgroundClient.py	Fri Apr 17 18:57:38 2015 +0200
+++ b/Utilities/BackgroundClient.py	Sat Apr 18 19:14:15 2015 +0200
@@ -86,7 +86,7 @@
         Private methode to receive the given length of bytes.
         
         @param length bytes to receive (int)
-        @return received bytes or None if connection closed (str)
+        @return received bytes or None if connection closed (bytes)
         """
         data = b''
         while len(data) < length:
@@ -96,20 +96,54 @@
             data += newData
         return data
     
+    def __peek(self, length):
+        """
+        Private methode to peek the given length of bytes.
+        
+        @param length bytes to receive (int)
+        @return received bytes (bytes)
+        """
+        data = b''
+        self.connection.setblocking(False)
+        try:
+            data = self.connection.recv(length, socket.MSG_PEEK)
+        except socket.error:
+            pass
+        self.connection.setblocking(True)
+        return data
+    
+    def __cancelled(self):
+        """
+        Private method to check for a job cancellation.
+        
+        @return flag indicating a cancellation (boolean)
+        """
+        msg = self.__peek(struct.calcsize(b'!II') + 6)
+        if msg[-6:] == b"CANCEL":
+            # get rid of the message data
+            self.__peek(struct.calcsize(b'!II') + 6)
+            return True
+        else:
+            return False
+    
     def run(self):
         """
         Public method implementing the main loop of the client.
         """
         try:
             while True:
-                header = self.__receive(8)
+                header = self.__receive(struct.calcsize(b'!II'))
                 # Leave main loop if connection was closed.
                 if not header:
                     break
                 
                 length, datahash = struct.unpack(b'!II', header)
+                messageType = self.__receive(6)
                 packedData = self.__receive(length)
                 
+                if messageType != b"JOB   ":
+                    continue
+                
                 assert adler32(packedData) & 0xffffffff == datahash, \
                     'Hashes not equal'
                 if sys.version_info[0] == 3:
@@ -121,7 +155,7 @@
                 elif fx.startswith("batch_"):
                     callback = self.batchServices.get(fx)
                     if callback:
-                        callback(data, self.__send, fx)
+                        callback(data, self.__send, fx, self.__cancelled)
                         ret = "__DONE__"
                     else:
                         ret = 'Unknown batch service.'
--- a/Utilities/BackgroundService.py	Fri Apr 17 18:57:38 2015 +0200
+++ b/Utilities/BackgroundService.py	Sat Apr 18 19:14:15 2015 +0200
@@ -132,6 +132,7 @@
             header = struct.pack(
                 b'!II', len(packedData), adler32(packedData) & 0xffffffff)
             connection.write(header)
+            connection.write(b'JOB   ')    # 6 character message type
             connection.write(packedData)
 
     def __receive(self, lang):
@@ -142,7 +143,7 @@
         """
         connection = self.connections[lang]
         while connection.bytesAvailable():
-            header = connection.read(8)
+            header = connection.read(struct.calcsize(b'!II'))
             length, datahash = struct.unpack(b'!II', header)
             
             packedData = b''
@@ -290,6 +291,20 @@
                 self.__queue.append(args)
         self.__processQueue()
     
+    def requestCancel(self, lang):
+        """
+        Public method to ask a batch job to terminate.
+        
+        @param lang language to connect to (str)
+        """
+        connection = self.connections.get(lang)
+        if connection is None:
+            return
+        else:
+            header = struct.pack(b'!II', 0, 0)
+            connection.write(header)
+            connection.write(b'CANCEL')    # 6 character message type
+    
     def serviceConnect(
             self, fx, lang, modulepath, module, callback,
             onErrorCallback=None, onBatchDone=None):

eric ide

mercurial