Utilities/BackgroundService.py

changeset 3579
eccd12461319
parent 3538
33a75660df08
child 3581
03b351be4436
equal deleted inserted replaced
3576:83b535c23264 3579:eccd12461319
15 import os 15 import os
16 import struct 16 import struct
17 import sys 17 import sys
18 from zlib import adler32 18 from zlib import adler32
19 19
20 from PyQt4.QtCore import QProcess, pyqtSignal 20 from PyQt4.QtCore import QProcess, pyqtSignal, QTimer
21 from PyQt4.QtGui import QApplication 21 from PyQt4.QtGui import QApplication
22 from PyQt4.QtNetwork import QTcpServer, QHostAddress 22 from PyQt4.QtNetwork import QTcpServer, QHostAddress
23 23
24 from E5Gui import E5MessageBox
25 from E5Gui.E5Application import e5App
24 import Preferences 26 import Preferences
25 import Utilities 27 import Utilities
26 28
27 from eric5config import getConfig 29 from eric5config import getConfig
28 30
35 37
36 def __init__(self): 38 def __init__(self):
37 """ 39 """
38 Constructor of the BackgroundService class. 40 Constructor of the BackgroundService class.
39 """ 41 """
40 self.processes = [] 42 self.processes = {}
41 self.connections = {} 43 self.connections = {}
42 self.isWorking = None 44 self.isWorking = None
45 self.runningJob = [None, None, None, None]
43 self.__queue = [] 46 self.__queue = []
44 self.services = {} 47 self.services = {}
45 48
46 super(BackgroundService, self).__init__() 49 super(BackgroundService, self).__init__()
47 50
60 for pyName in ['Python', 'Python3']: 63 for pyName in ['Python', 'Python3']:
61 interpreter = Preferences.getDebugger( 64 interpreter = Preferences.getDebugger(
62 pyName + "Interpreter") 65 pyName + "Interpreter")
63 process = self.__startExternalClient(interpreter, port) 66 process = self.__startExternalClient(interpreter, port)
64 if process: 67 if process:
65 self.processes.append(process) 68 if pyName == 'Python':
69 pyName = 'Python2'
70 self.processes[pyName] = process, interpreter
66 71
67 def __startExternalClient(self, interpreter, port): 72 def __startExternalClient(self, interpreter, port):
68 """ 73 """
69 Private method to start the background client as external process. 74 Private method to start the background client as external process.
70 75
91 client. 96 client.
92 """ 97 """
93 if self.__queue and self.isWorking is None: 98 if self.__queue and self.isWorking is None:
94 fx, lang, fn, data = self.__queue.pop(0) 99 fx, lang, fn, data = self.__queue.pop(0)
95 self.isWorking = lang 100 self.isWorking = lang
101 self.runningJob = fx, lang, fn, data
96 self.__send(fx, lang, fn, data) 102 self.__send(fx, lang, fn, data)
97 103
98 def __send(self, fx, lang, fn, data): 104 def __send(self, fx, lang, fn, data):
99 """ 105 """
100 Private method to send a job request to one of the clients. 106 Private method to send a job request to one of the clients.
105 @param data function argument(s) (any basic datatype) 111 @param data function argument(s) (any basic datatype)
106 """ 112 """
107 connection = self.connections.get(lang) 113 connection = self.connections.get(lang)
108 if connection is None: 114 if connection is None:
109 if fx != 'INIT': 115 if fx != 'INIT':
110 self.serviceNotAvailable.emit( 116 # Avoid growing recursion deep which could itself result in an
111 fx, lang, fn, self.tr( 117 # exception
112 '{0} not configured.').format(lang)) 118 QTimer.singleShot(
119 0,
120 lambda: self.serviceNotAvailable.emit(
121 fx, lang, fn, self.tr(
122 '{0} not configured.').format(lang)))
113 # Reset flag and continue processing queue 123 # Reset flag and continue processing queue
114 self.isWorking = None 124 self.isWorking = None
115 self.__processQueue() 125 self.__processQueue()
116 else: 126 else:
117 packedData = json.dumps([fx, fn, data]) 127 packedData = json.dumps([fx, fn, data])
144 fx, fn, data = json.loads(packedData) 154 fx, fn, data = json.loads(packedData)
145 155
146 if fx == 'INIT': 156 if fx == 'INIT':
147 pass 157 pass
148 elif fx == 'EXCEPTION': 158 elif fx == 'EXCEPTION':
159 # Remove connection because it'll close anyway
160 self.connections.pop(lang, None)
149 # Call sys.excepthook(type, value, traceback) to emulate the 161 # Call sys.excepthook(type, value, traceback) to emulate the
150 # exception which was caught on the client 162 # exception which was caught on the client
151 sys.excepthook(*data) 163 sys.excepthook(*data)
152 QApplication.processEvents() 164 res = E5MessageBox.question(
165 None,
166 self.tr("Restart background client?"),
167 self.tr(
168 "<p>The background client for <b>{0}</b> has stopped"
169 " due to an exception. It's used by various plug-ins like"
170 " the different checkers.</p>"
171 "<p>Select<br>"
172 "<b>'Yes'</b> to restart the client, but abort the last"
173 " job<br>"
174 "<b>'Retry'</b> to restart the client and the last job<br>"
175 "<b>'No'</b> to leave the client off.</p>"
176 "<p>Note: The client can be restarted by opening and"
177 " accepting the preferences dialog or reloading/ changing"
178 " the project.</p>").format(lang),
179 E5MessageBox.Yes | E5MessageBox.No | E5MessageBox.Retry,
180 E5MessageBox.Yes)
181
182 if res == E5MessageBox.Retry:
183 self.enqueueRequest(*self.runningJob)
184 else:
185 fx, lng, fn, data = self.runningJob
186 self.services[(fx, lng)][3](fx, lng, fn, self.tr(
187 'An error in Erics background client stopped the service.')
188 )
189 if res != E5MessageBox.No:
190 self.isWorking = None
191 self.restartService(lang, True)
192 return
153 elif data == 'Unknown service.': 193 elif data == 'Unknown service.':
154 callback = self.services.get((fx, lang)) 194 callback = self.services.get((fx, lang))
155 if callback: 195 if callback:
156 callback[3](fx, lang, fn, data) 196 callback[3](fx, lang, fn, data)
157 else: 197 else:
159 if callback: 199 if callback:
160 callback[2](fn, *data) 200 callback[2](fn, *data)
161 201
162 self.isWorking = None 202 self.isWorking = None
163 self.__processQueue() 203 self.__processQueue()
204
205 def preferencesOrProjectChanged(self):
206 """
207 Public slot to restart the built in languages.
208 """
209 for pyName in ['Python', 'Python3']:
210 interpreter = Preferences.getDebugger(
211 pyName + "Interpreter")
212
213 if pyName == 'Python':
214 pyName = 'Python2'
215
216 # Tweak the processes list to reflect the changed interpreter
217 proc, inter = self.processes.pop(pyName, [None, None])
218 self.processes[pyName] = proc, interpreter
219
220 self.restartService(pyName)
221
222 def restartService(self, language, forceKill=False):
223 """
224 Public method to restart a given lanuage.
225
226 @param language to restart (str)
227 @keyparam forceKill flag to kill a running task (bool)
228 """
229 try:
230 proc, interpreter = self.processes.pop(language)
231 except KeyError:
232 return
233
234 # Don't kill a process if it's still working
235 if not forceKill:
236 while self.isWorking is not None:
237 QApplication.processEvents()
238
239 conn = self.connections.pop(language, None)
240 if conn:
241 conn.blockSignals(True)
242 conn.close()
243 if proc:
244 proc.close()
245
246 port = self.serverPort()
247 process = self.__startExternalClient(interpreter, port)
248 if process:
249 self.processes[language] = process, interpreter
164 250
165 def enqueueRequest(self, fx, lang, fn, data): 251 def enqueueRequest(self, fx, lang, fn, data):
166 """ 252 """
167 Implement a queued processing of incomming events. 253 Implement a queued processing of incomming events.
168 254
240 326
241 for (fx, lng), args in self.services.items(): 327 for (fx, lng), args in self.services.items():
242 if lng == lang: 328 if lng == lang:
243 # Register service with modulepath and module 329 # Register service with modulepath and module
244 self.enqueueRequest('INIT', lng, fx, args[:2]) 330 self.enqueueRequest('INIT', lng, fx, args[:2])
331
332 # Syntax check the open editors again
333 try:
334 vm = e5App().getObject("ViewManager")
335 except KeyError:
336 return
337 for editor in vm.getOpenEditors():
338 if editor.getLanguage() == lang:
339 QTimer.singleShot(0, editor.checkSyntax)
245 340
246 def on_disconnectSocket(self, lang): 341 def on_disconnectSocket(self, lang):
247 """ 342 """
248 Slot when connection to a client is lost. 343 Slot is called when connection to a client is lost.
249 344
250 @param lang client language which connection is lost (str) 345 @param lang client language which connection is lost (str)
251 """ 346 """
252 self.connections.pop(lang) 347 conn = self.connections.pop(lang, None)
253 # Maybe the task is killed while ideling 348 if conn:
254 if self.isWorking == lang: 349 conn.close()
350 fx, lng, fn, data = self.runningJob
351 if fx != 'INIT' and lng == lang:
352 self.services[(fx, lng)][3](fx, lng, fn, self.tr(
353 'Erics background client disconnected because of an'
354 ' unknown reason.')
355 )
255 self.isWorking = None 356 self.isWorking = None
256 # Remove pending jobs and send warning to the waiting caller 357
257 # Make a copy of the list because it's modified in the loop 358 res = E5MessageBox.yesNo(
258 for args in self.__queue[:]: 359 None,
259 fx, lng, fn, data = args 360 self.tr('Background client disconnected.'),
260 if lng == lang: 361 self.tr(
261 # Call onErrorCallback with error message 362 'The background client for <b>{0}</b> disconnect because'
262 self.__queue.remove(args) 363 ' of an unknown reason.<br>Should it be restarted?'
263 self.services[(fx, lng)][3](fx, fn, lng, self.tr( 364 ).format(lang),
264 'Error in Erics background service stopped service.')) 365 yesDefault=True)
265 366 if res:
367 self.restartService(lang)
368
266 def shutdown(self): 369 def shutdown(self):
267 """ 370 """
268 Cleanup the connections and processes when Eric is shuting down. 371 Cleanup the connections and processes when eric is shuting down.
269 """ 372 """
270 # Make copy of dictionary values because the list is changed by 373 for connection in self.connections.values():
271 # on_disconnectSocket 374 # Prevent calling of on_disconnectSocket
272 for connection in list(self.connections.values()): 375 connection.blockSignals(True)
273 if connection: 376 connection.close()
274 connection.close() 377
275 378 for process, interpreter in self.processes.values():
276 for process in self.processes:
277 process.close() 379 process.close()
278 process = None 380 process = None

eric ide

mercurial