11import logging
22import os
33import socket
4+ import time
45from contextlib import closing
56from threading import Lock , current_thread
67
@@ -85,6 +86,21 @@ def __init__(self, port: int, host: str = '127.0.0.1', connect_timeout: int = 10
8586 self .host = host
8687 self .connect_timeout = connect_timeout
8788
89+ def __repr__ (self ):
90+ return (
91+ '{type_name}('
92+ 'port={self.port}'
93+ ', host={self.host}'
94+ ', connect_timeout={self.connect_timeout}'
95+ ', command={command}'
96+ ', process={self.process}'
97+ ')'
98+ ).format (
99+ type_name = type (self ).__name__ ,
100+ self = self ,
101+ command = repr (self .command )
102+ )
103+
88104 def is_alive (self ):
89105 if not self .is_running ():
90106 return False
@@ -94,15 +110,36 @@ def is_alive(self):
94110 return True
95111 return False
96112
97- def start_and_check_alive (self , ** kwargs ):
113+ def wait_for_is_alive (self , timeout : float ) -> bool :
114+ start_time = time .monotonic ()
115+ while not self .is_alive ():
116+ if not self .is_running ():
117+ return False
118+ if time .monotonic () - start_time >= timeout :
119+ return False
120+ time .sleep (0.5 )
121+ return True
122+
123+ def start_and_check_alive (self , timeout = 10 , ** kwargs ):
98124 super ().start (** kwargs )
99- if not self .is_alive ():
100- self .stop ()
101- raise ConnectionError ('failed to start listener (unable to connect)' )
125+ if self .wait_for_is_alive (timeout = timeout ):
126+ return
127+ self .stop_if_running ()
128+ if self .process .returncode == 81 :
129+ # see https://bugs.documentfoundation.org/show_bug.cgi?id=107912
130+ # "headless firstrun crashes (exit code 81)"
131+ LOGGER .info ('detected first-run error code 81, re-trying..' )
132+ self .start_and_check_alive (timeout = timeout , ** kwargs )
133+ return
134+ raise ConnectionError ('failed to start listener (unable to connect)' )
102135
103- def start_listener_if_not_running (self , ** kwargs ):
136+ def start_listener_if_not_running (self , max_uptime : float = None , ** kwargs ):
104137 if self .is_alive ():
105- return
138+ uptime = self .get_uptime ()
139+ if not max_uptime or uptime <= max_uptime :
140+ return
141+ LOGGER .info ('stopping listener, exceeded max uptime: %.3f > %.3f' , uptime , max_uptime )
142+ self .stop ()
106143 self .start_and_check_alive (** kwargs )
107144
108145
@@ -114,19 +151,33 @@ def __init__(
114151 no_launch : bool = True ,
115152 keep_listener_running : bool = True ,
116153 process_timeout : int = None ,
154+ max_uptime : float = 10 ,
117155 stop_listener_on_error : bool = True ):
118156 self .port = port
119157 self .enable_debug = enable_debug
120158 self .no_launch = no_launch
121159 self .keep_listener_running = keep_listener_running
122160 self .process_timeout = process_timeout
161+ self .max_uptime = max_uptime
123162 self .stop_listener_on_error = stop_listener_on_error
124163 self ._listener_process = ListenerProcess (port = port )
125164 self ._lock = Lock ()
126165 self ._concurrent_count = 0
127166
167+ def __repr__ (self ):
168+ return (
169+ '{type_name}('
170+ 'port={self.port}'
171+ ', keep_listener_running={self.keep_listener_running}'
172+ ', _listener_process={self._listener_process}'
173+ ')'
174+ ).format (
175+ type_name = type (self ).__name__ ,
176+ self = self
177+ )
178+
128179 def start_listener_if_not_running (self ):
129- self ._listener_process .start_if_not_running ( )
180+ self ._listener_process .start_listener_if_not_running ( max_uptime = self . max_uptime )
130181
131182 def stop_listener_if_running (self ):
132183 self ._listener_process .stop_if_running ()
@@ -136,7 +187,8 @@ def _do_convert(
136187 remove_line_no : bool = True ,
137188 remove_header_footer : bool = True ,
138189 remove_redline : bool = True ):
139- self .start_listener_if_not_running ()
190+ if self .no_launch :
191+ self .start_listener_if_not_running ()
140192
141193 temp_target_filename = change_ext (
142194 temp_source_filename , None , '-output.%s' % output_type
0 commit comments