1010import subprocess
1111import threading
1212import urllib .parse
13+ import psutil
1314
1415from .config_file import LanguageToolConfig
1516from .download_lt import download_lt , LTP_DOWNLOAD_VERSION
2021 parse_url , get_locale_language ,
2122 get_language_tool_directory , get_server_cmd ,
2223 FAILSAFE_LANGUAGE , startupinfo ,
23- LanguageToolError , ServerError , PathError
24+ LanguageToolError , ServerError , PathError ,
25+ kill_process_force
2426)
2527
2628
@@ -67,6 +69,7 @@ def __init__(
6769 self ._url = urllib .parse .urljoin (self ._url , 'v2/' )
6870 self ._update_remote_server_config (self ._url )
6971 elif not self ._server_is_alive ():
72+ self ._stop_consume_event = threading .Event ()
7073 self ._start_server_on_free_port ()
7174 if language is None :
7275 try :
@@ -334,7 +337,7 @@ def _start_local_server(self):
334337
335338 if self ._server :
336339 self ._consumer_thread = threading .Thread (
337- target = lambda : _consume (self ._server .stdout ))
340+ target = lambda : self . _consume (self ._server .stdout ))
338341 self ._consumer_thread .daemon = True
339342 self ._consumer_thread .start ()
340343 else :
@@ -345,34 +348,70 @@ def _start_local_server(self):
345348 raise ServerError (
346349 'Server running; don\' t start a server here.'
347350 )
351+
352+ def _consume (self , stdout ):
353+ """Consume/ignore the rest of the server output.
354+ Without this, the server will end up hanging due to the buffer
355+ filling up.
356+ """
357+ while not self ._stop_consume_event .is_set () and stdout .readline ():
358+ pass
359+
348360
349361 def _server_is_alive (self ):
350362 return self ._server and self ._server .poll () is None
351363
352364 def _terminate_server (self ):
353- LanguageToolError_message = ''
354- try :
355- self ._server .terminate ()
356- except OSError :
357- pass
358- try :
359- LanguageToolError_message = self ._server .communicate ()[1 ].strip ()
360- except (IOError , ValueError ):
361- pass
362- try :
363- self ._server .stdout .close ()
364- except IOError :
365- pass
366- try :
367- self ._server .stdin .close ()
368- except IOError :
369- pass
370- try :
371- self ._server .stderr .close ()
372- except IOError :
373- pass
374- self ._server = None
375- return LanguageToolError_message
365+ """
366+ Terminate the LanguageTool server process and its associated resources.
367+ This method ensures the server process and any associated threads or child processes
368+ are properly terminated and cleaned up.
369+ """
370+ # Signal the consumer thread to stop consuming stdout
371+ self ._stop_consume_event .set ()
372+ if self ._consumer_thread :
373+ # Wait for the consumer thread to finish
374+ self ._consumer_thread .join ()
375+
376+ error_message = ''
377+ if self ._server :
378+ try :
379+ try :
380+ # Get the main server process using psutil
381+ proc = psutil .Process (self ._server .pid )
382+ except psutil .NoSuchProcess :
383+ # If the process doesn't exist, set proc to None
384+ proc = None
385+
386+ # Attempt to terminate the process gracefully
387+ self ._server .terminate ()
388+ # Wait for the process to terminate and capture any stderr output
389+ _ , stderr = self ._server .communicate (timeout = 5 )
390+
391+ except subprocess .TimeoutExpired :
392+ # If the process does not terminate within the timeout, force kill it
393+ kill_process_force (proc = proc )
394+ # Capture remaining stderr output after force termination
395+ _ , stderr = self ._server .communicate ()
396+
397+ finally :
398+ # Close all associated file descriptors (stdin, stdout, stderr)
399+ if self ._server .stdin :
400+ self ._server .stdin .close ()
401+ if self ._server .stdout :
402+ self ._server .stdout .close ()
403+ if self ._server .stderr :
404+ self ._server .stderr .close ()
405+
406+ # Release the server process object
407+ self ._server = None
408+
409+ # Capture any error messages from stderr, if available
410+ if stderr :
411+ error_message = stderr .strip ()
412+
413+ # Return the error message, if any, for further logging or debugging
414+ return error_message
376415
377416
378417class LanguageToolPublicAPI (LanguageTool ):
@@ -386,14 +425,5 @@ def __init__(self, *args, **kwargs):
386425@atexit .register
387426def terminate_server ():
388427 """Terminate the server."""
389- for proc in RUNNING_SERVER_PROCESSES :
390- proc .terminate ()
391-
392-
393- def _consume (stdout ):
394- """Consume/ignore the rest of the server output.
395- Without this, the server will end up hanging due to the buffer
396- filling up.
397- """
398- while stdout .readline ():
399- pass
428+ for pid in [p .pid for p in RUNNING_SERVER_PROCESSES ]:
429+ kill_process_force (pid = pid )
0 commit comments