Skip to content

Commit f6985e5

Browse files
committed
aexpect: Protect close() from other thread/process entry
The Spawn.close() contains critical section that should not be accessed multiple times, which might happen when a user uses aexpect from different threads/processes. Let's add a lock to protect it. Signed-off-by: Lukáš Doktor <[email protected]>
1 parent 5fd7051 commit f6985e5

File tree

1 file changed

+45
-21
lines changed

1 file changed

+45
-21
lines changed

aexpect/client.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
@@ -431,27 +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-
if not wait_for_lock(
438-
self.lock_server_running_filename, timeout=60
439-
):
440-
LOG.warning(
441-
"Failed to get lock, the aexpect_helper process "
442-
"might be left behind. Proceeding anyway..."
443-
)
444-
# Call all cleanup routines
445-
for hook in self.close_hooks:
446-
hook(self)
447-
# Close reader file descriptors
448-
self._close_reader_fds()
449-
self.reader_fds = {}
450-
# Remove all used files
451-
if "AEXPECT_DEBUG" not in os.environ:
452-
shutil.rmtree(os.path.join(BASE_DIR, f"aexpect_{self.a_id}"))
453-
self._close_aexpect_helper()
454-
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
455479

456480
def set_linesep(self, linesep):
457481
"""

0 commit comments

Comments
 (0)