@@ -143,6 +143,9 @@ def __init__(
143143 self .encoding = encoding
144144 self .reader_fds = {}
145145 base_dir = os .path .join (BASE_DIR , f"aexpect_{ self .a_id } " )
146+ self ._close_lockfile = os .path .join (
147+ BASE_DIR , f"aexpect_{ self .a_id } .lock"
148+ )
146149
147150 # Define filenames for communication with server
148151 utils_path .init_dir (base_dir )
@@ -263,8 +266,8 @@ def _get_aexpect_helper(self, helper_cmd, pass_fds, echo, command):
263266 # Wait for the server to complete its initialization
264267 full_output = ""
265268 pattern = f"Server { self .a_id } ready"
266- end_time = time .time () + 60
267- while time .time () < end_time :
269+ end_time = time .monotonic () + 60
270+ while time .monotonic () < end_time :
268271 output = sub .stdout .readline ().decode (self .encoding , "ignore" )
269272 if pattern in output :
270273 break
@@ -431,21 +434,48 @@ def close(self, sig=signal.SIGKILL):
431434
432435 :param sig: The signal to send the process when attempting to kill it.
433436 """
434- if not self .closed :
435- self .kill (sig = sig )
436- # Wait for the server to exit
437- wait_for_lock (self .lock_server_running_filename )
438- # Call all cleanup routines
439- for hook in self .close_hooks :
440- hook (self )
441- # Close reader file descriptors
442- self ._close_reader_fds ()
443- self .reader_fds = {}
444- # Remove all used files
445- if "AEXPECT_DEBUG" not in os .environ :
446- shutil .rmtree (os .path .join (BASE_DIR , f"aexpect_{ self .a_id } " ))
447- self ._close_aexpect_helper ()
448- self .closed = True
437+ if self .closed :
438+ return
439+ lock = None
440+ try :
441+ try :
442+ lock = get_lock_fd (self ._close_lockfile , timeout = 60 )
443+ except FileNotFoundError :
444+ if not self .closed :
445+ raise
446+ if not self .closed :
447+ self .kill (sig = sig )
448+ # Wait for the server to exit
449+ if not wait_for_lock (
450+ self .lock_server_running_filename , timeout = 60
451+ ):
452+ LOG .warning (
453+ "Failed to get lock, the aexpect_helper "
454+ "process might be left behind. Proceeding "
455+ "anyway..."
456+ )
457+ # Call all cleanup routines
458+ for hook in self .close_hooks :
459+ hook (self )
460+ # Close reader file descriptors
461+ self ._close_reader_fds ()
462+ self .reader_fds = {}
463+ # Remove all used files
464+ if "AEXPECT_DEBUG" not in os .environ :
465+ shutil .rmtree (
466+ os .path .join (BASE_DIR , f"aexpect_{ self .a_id } " ),
467+ ignore_errors = True ,
468+ )
469+ self ._close_aexpect_helper ()
470+ self .closed = True
471+ finally :
472+ if lock is not None :
473+ try :
474+ unlock_fd (lock )
475+ os .unlink (self ._close_lockfile )
476+ except FileNotFoundError :
477+ # File already removed by other thread
478+ pass
449479
450480 def set_linesep (self , linesep ):
451481 """
@@ -866,7 +896,7 @@ def _read_nonblocking(self, internal_timeout=None, timeout=None):
866896 internal_timeout *= 1000
867897 end_time = None
868898 if timeout :
869- end_time = time .time () + timeout
899+ end_time = time .monotonic () + timeout
870900 expect_pipe = self ._get_fd ("expect" )
871901 poller = select .poll ()
872902 poller .register (expect_pipe , select .POLLIN )
@@ -885,7 +915,7 @@ def _read_nonblocking(self, internal_timeout=None, timeout=None):
885915 data += raw_data .decode (self .encoding , "ignore" )
886916 else :
887917 return read , data
888- if end_time and time .time () > end_time :
918+ if end_time and time .monotonic () > end_time :
889919 return read , data
890920
891921 def read_nonblocking (self , internal_timeout = None , timeout = None ):
@@ -979,10 +1009,10 @@ def read_until_output_matches(
9791009 poller = select .poll ()
9801010 poller .register (expect_pipe , select .POLLIN )
9811011 output = ""
982- end_time = time .time () + timeout
1012+ end_time = time .monotonic () + timeout
9831013 while True :
9841014 try :
985- max_ms = int ((end_time - time .time ()) * 1000 )
1015+ max_ms = int ((end_time - time .monotonic ()) * 1000 )
9861016 poll_timeout_ms = max (0 , max_ms )
9871017 poll_status = poller .poll (poll_timeout_ms )
9881018 except select .error :
@@ -991,7 +1021,7 @@ def read_until_output_matches(
9911021 raise ExpectTimeoutError (patterns , output )
9921022 # Read data from child
9931023 read , data = self ._read_nonblocking (
994- internal_timeout , end_time - time .time ()
1024+ internal_timeout , end_time - time .monotonic ()
9951025 )
9961026 if not read :
9971027 break
@@ -1261,10 +1291,10 @@ def is_responsive(self, timeout=5.0):
12611291 # Send a newline
12621292 self .sendline ()
12631293 # Wait up to timeout seconds for some output from the child
1264- end_time = time .time () + timeout
1265- while time .time () < end_time :
1294+ end_time = time .monotonic () + timeout
1295+ while time .monotonic () < end_time :
12661296 time .sleep (0.5 )
1267- if self .read_nonblocking (0 , end_time - time .time ()).strip ():
1297+ if self .read_nonblocking (0 , end_time - time .monotonic ()).strip ():
12681298 return True
12691299 # No output -- report unresponsive
12701300 return False
@@ -1384,8 +1414,8 @@ def cmd_output_safe(self, cmd, timeout=60, strip_console_codes=False):
13841414 self .sendline (cmd )
13851415 out = ""
13861416 success = False
1387- start_time = time .time ()
1388- while (time .time () - start_time ) < timeout :
1417+ start_time = time .monotonic ()
1418+ while (time .monotonic () - start_time ) < timeout :
13891419 try :
13901420 out += self .read_up_to_prompt (0.5 )
13911421 success = True
@@ -1721,8 +1751,8 @@ def run_tail(
17211751 encoding = encoding ,
17221752 )
17231753
1724- end_time = time .time () + timeout
1725- while time .time () < end_time and bg_process .is_alive ():
1754+ end_time = time .monotonic () + timeout
1755+ while time .monotonic () < end_time and bg_process .is_alive ():
17261756 time .sleep (0.1 )
17271757
17281758 return bg_process
@@ -1774,8 +1804,8 @@ def run_bg(
17741804 encoding = encoding ,
17751805 )
17761806
1777- end_time = time .time () + timeout
1778- while time .time () < end_time and bg_process .is_alive ():
1807+ end_time = time .monotonic () + timeout
1808+ while time .monotonic () < end_time and bg_process .is_alive ():
17791809 time .sleep (0.1 )
17801810
17811811 return bg_process
0 commit comments