5151PRIORITY_BACKGROUND = 4 # info may be needed sometime
5252
5353logger_name = 'CodeIntel.codeintel'
54+ logger_level = logging .INFO # INFO
5455
55- logging .getLogger (logger_name ).setLevel (logging .INFO ) # INFO
56+ logger = logging .getLogger (logger_name )
57+ logger .setLevel (logger_level )
5658
5759
5860class CodeIntel (object ):
59- def __init__ (self ):
61+ def __init__ (self , main_thread_runner ):
62+ """
63+ main_thread_runner - Must be a function receiving a single parameter,
64+ a function to be executed in the main thread.
65+ """
66+ self ._main_thread_runner = main_thread_runner
6067 self .log = logging .getLogger (logger_name + '.' + self .__class__ .__name__ )
6168 self .mgr = None
6269 self ._mgr_lock = threading .Lock ()
@@ -151,7 +158,7 @@ def activate(self, reset_db_as_necessary=False, codeintel_command=None, oop_mode
151158
152159 @property
153160 def enabled (self ):
154- return self ._enabled
161+ return self ._enabled and self . mgr and self . mgr . is_alive ()
155162
156163 def deactivate (self ):
157164 with self ._mgr_lock :
@@ -449,6 +456,7 @@ def __init__(self, service, progress_callback=None, shutdown_callback=None, code
449456 if env is not None :
450457 self .env = env
451458 self ._state_condvar = threading .Condition ()
459+ self ._discard_time = time .time ()
452460 self .requests = {} # keyed by request id; value is tuple (callback, request data, time sent) requests will time out at some point...
453461 self .unsent_requests = queue .Queue ()
454462 threading .Thread .__init__ (self , name = "CodeIntel Manager Thread" )
@@ -469,26 +477,29 @@ def start(self, reset_db_as_necessary=False):
469477
470478 def shutdown (self ):
471479 """Abort any outstanding requests and shut down gracefully"""
472- self .abort ()
473480 if self .state is CodeIntelManager .STATE_DESTROYED :
474481 return # already dead
482+ self .abort ()
483+ self .quit ()
475484 if not self .pipe :
476485 # not quite dead, but already disconnected... ungraceful shutdown
477486 self .kill ()
478487 return
479- self ._send (command = 'quit' , callback = self .do_quit )
480- self .state = CodeIntelManager .STATE_QUITTING
481488
482489 def abort (self ):
483490 """Abort all running requests"""
484491 for req in list (self .requests .keys ()):
485492 self ._abort .add (req )
486- self ._send (
493+ self .send (
487494 command = 'abort' ,
488495 id = req ,
489496 callback = lambda request , response : None ,
490497 )
491498
499+ def quit (self ):
500+ self .send (command = 'quit' , callback = self .do_quit )
501+ self .state = CodeIntelManager .STATE_QUITTING
502+
492503 def close (self ):
493504 try :
494505 self .pipe .close ()
@@ -739,10 +750,10 @@ def initialization_completed():
739750 self ._send_request_thread .start ()
740751 update ("CodeIntel ready." , state = CodeIntelManager .STATE_READY )
741752
742- self ._send (callback = get_cpln_langs , command = 'get-languages' , type = 'cpln' )
743753 self ._send (callback = get_citadel_langs , command = 'get-languages' , type = 'citadel' )
744754 self ._send (callback = get_xml_langs , command = 'get-languages' , type = 'xml' )
745755 self ._send (callback = get_stdlib_langs , command = 'get-languages' , type = 'stdlib-supported' )
756+ self ._send (callback = get_cpln_langs , command = 'get-languages' , type = 'cpln' )
746757
747758 self .set_global_environment (self .env , self .prefs )
748759
@@ -756,7 +767,7 @@ def update_callback(response):
756767 def set_global_environment (self , env , prefs ):
757768 self .env = env
758769 self .prefs = [prefs ] if isinstance (prefs , dict ) else prefs
759- self ._send (
770+ self .send (
760771 command = 'set-environment' ,
761772 env = self .env ,
762773 prefs = self .prefs ,
@@ -768,7 +779,7 @@ def get_available_catalogs(request, response):
768779 self .available_catalogs = response .get ('catalogs' , [])
769780 if update_callback :
770781 update_callback (response )
771- self ._send (callback = get_available_catalogs , command = 'get-available-catalogs' )
782+ self .send (callback = get_available_catalogs , command = 'get-available-catalogs' )
772783
773784 def send (self , callback = None , ** kwargs ):
774785 """Public API for sending a request.
@@ -783,11 +794,12 @@ def send(self, callback=None, **kwargs):
783794
784795 def _send_queued_requests (self ):
785796 """Worker to send unsent requests"""
786- while True :
787- with self ._state_condvar :
788- if self .state is CodeIntelManager .STATE_DESTROYED :
789- break # Manager already shut down
790- if self .state is not CodeIntelManager .STATE_READY :
797+
798+ self .log .info ("%s thread started..." % threading .current_thread ().name )
799+
800+ while self .state not in (CodeIntelManager .STATE_QUITTING , CodeIntelManager .STATE_DESTROYED ):
801+ if self .state is not CodeIntelManager .STATE_READY :
802+ with self ._state_condvar :
791803 self ._state_condvar .wait ()
792804 continue # wait...
793805 callback , kwargs = self .unsent_requests .get ()
@@ -796,6 +808,8 @@ def _send_queued_requests(self):
796808 break
797809 self ._send (callback , ** kwargs )
798810
811+ self .log .info ("%s thread ended!" % threading .current_thread ().name )
812+
799813 def _send (self , callback = None , ** kwargs ):
800814 """
801815 Private API for sending; ignores the current state of the manager and
@@ -804,8 +818,6 @@ def _send(self, callback=None, **kwargs):
804818 calling thread until the data has been written (though possibly not yet
805819 received on the other end).
806820 """
807- if not self .pipe or self .state is CodeIntelManager .STATE_QUITTING :
808- return # Nope, eating all commands during quit
809821 req_id = hex (self ._next_id )
810822 kwargs ['req_id' ] = req_id
811823 text = json .dumps (kwargs , separators = (',' , ':' ))
@@ -833,17 +845,16 @@ def run(self):
833845 assert threading .current_thread ().name != "MainThread" , \
834846 "CodeIntelManager.run should run on background thread!"
835847
836- self .log .debug ( "CodeIntelManager thread started..." )
848+ self .log .info ( "%s thread started..." % threading . current_thread (). name )
837849
838- while True :
850+ while self . state not in ( CodeIntelManager . STATE_QUITTING , CodeIntelManager . STATE_DESTROYED ) :
839851 ok = False
840852
841853 self .init_child ()
842854 if not self .proc :
843855 break # init child failed
844856
845857 first_buf = True
846- discard_time = 0.0
847858 try :
848859 buf = b''
849860 while self .proc and self .pipe :
@@ -875,20 +886,6 @@ def run(self):
875886 raise ValueError ("Invalid frame length character: %r" % ch )
876887 buf += ch
877888
878- now = time .time ()
879- if now - discard_time > 60 : # discard some stale results
880- for req_id , (callback , request , sent_time ) in list (self .requests .items ()):
881- if sent_time < now - 5 * 60 :
882- # sent 5 minutes ago - it's irrelevant now
883- try :
884- if callback :
885- callback (request , {})
886- except Exception as e :
887- self .log .error ("Failed timing out request" )
888- else :
889- self .log .debug ("Discarding request %r" , request )
890- del self .requests [req_id ]
891-
892889 except Exception as e :
893890 if self .state in (CodeIntelManager .STATE_QUITTING , CodeIntelManager .STATE_DESTROYED ):
894891 self .log .debug ("IOError in codeintel during shutdown; ignoring" )
@@ -902,43 +899,63 @@ def run(self):
902899 if not ok :
903900 time .sleep (3 )
904901
905- self .log .debug ( "CodeIntelManager thread ended!" )
902+ self .log .info ( "%s thread ended!" % threading . current_thread (). name )
906903
907904 def handle (self , response ):
908905 """Handle a response from the codeintel process"""
909- self .log .debug ("handling: %r" , response )
910- req_id = response .get ('req_id' )
911- callback , request , sent_time = self .requests .get (req_id , (None , None , None ))
912- request_command = request .get ('command' , '' ) if request else None
913- response_command = response .get ('command' , request_command )
914- if req_id is None or request_command != response_command :
915- # unsolicited response, look for a handler
916- try :
917- if not response_command :
918- self .log .error ("No 'command' in response %r" , response )
919- raise ValueError ("Invalid response frame %r" % response )
920- meth = getattr (self , 'do_' + response_command .replace ('-' , '_' ), None )
921- if not meth :
922- self .log .error ("Unknown command %r, response %r" , response_command , response )
923- raise ValueError ("Unknown unsolicited response \" %s\" " % response_command )
924- meth (response )
925- except Exception as e :
926- self .log .error ("Error handling unsolicited response" )
927- return
928- if not request :
929- self .log .error ("Discard response for unknown request %s (command %s): have %s" ,
930- req_id , response_command or '%r' % response , sorted (self .requests .keys ()))
931- return
932- self .log .debug ("Request %s (command %s) took %0.2f seconds" , req_id , request_command or '<unknown>' , time .time () - sent_time )
933- if 'success' in response :
934- # remove completed request
935- self .log .debug ("Removing completed request %s" , req_id )
936- del self .requests [req_id ]
937- else :
938- # unfinished response; update the sent time so it doesn't time out
939- self .requests [req_id ] = (callback , request , time .time ())
940- if callback :
941- callback (request , response )
906+ def _handle ():
907+ assert threading .current_thread ().name == "MainThread" , \
908+ "CodeIntelManager.handle() should run on main thread!"
909+
910+ now = time .time ()
911+ if now - self ._discard_time > 60 : # (not so often) discard some stale results
912+ self ._discard_time = now
913+ for req_id , (callback , request , sent_time ) in list (self .requests .items ()):
914+ if sent_time < now - 5 * 60 :
915+ # sent 5 minutes ago - it's irrelevant now
916+ try :
917+ if callback :
918+ callback (request , {})
919+ except Exception as e :
920+ self .log .error ("Failed timing out request" )
921+ else :
922+ self .log .debug ("Discarding request %r" , request )
923+ del self .requests [req_id ]
924+
925+ self .log .debug ("handling: %r" , response )
926+ req_id = response .get ('req_id' )
927+ callback , request , sent_time = self .requests .get (req_id , (None , None , None ))
928+ request_command = request .get ('command' , '' ) if request else None
929+ response_command = response .get ('command' , request_command )
930+ if req_id is None or request_command != response_command :
931+ # unsolicited response, look for a handler
932+ try :
933+ if not response_command :
934+ self .log .error ("No 'command' in response %r" , response )
935+ raise ValueError ("Invalid response frame %r" % response )
936+ meth = getattr (self , 'do_' + response_command .replace ('-' , '_' ), None )
937+ if not meth :
938+ self .log .error ("Unknown command %r, response %r" , response_command , response )
939+ raise ValueError ("Unknown unsolicited response \" %s\" " % response_command )
940+ meth (response )
941+ except Exception as e :
942+ self .log .error ("Error handling unsolicited response" )
943+ return
944+ if not request :
945+ self .log .error ("Discard response for unknown request %s (command %s): have %s" ,
946+ req_id , response_command or '%r' % response , sorted (self .requests .keys ()))
947+ return
948+ self .log .debug ("Request %s (command %s) took %0.2f seconds" , req_id , request_command or '<unknown>' , time .time () - sent_time )
949+ if 'success' in response :
950+ # remove completed request
951+ self .log .debug ("Removing completed request %s" , req_id )
952+ del self .requests [req_id ]
953+ else :
954+ # unfinished response; update the sent time so it doesn't time out
955+ self .requests [req_id ] = (callback , request , time .time ())
956+ if callback :
957+ callback (request , response )
958+ self .service ._main_thread_runner (_handle ) # Do handling in main thread
942959
943960 def do_scan_complete (self , response ):
944961 """Scan complete unsolicited response"""
0 commit comments