mirror of
https://github.com/clearml/wexpect-venv
synced 2025-04-03 20:50:48 +00:00
[FIX] real child process cannot be fetched from host, because it may died befor fetching it, so signal handling cannot be handled directly.
This commit is contained in:
parent
a9471a94e6
commit
d46e700655
@ -43,6 +43,7 @@ import os
|
||||
import traceback
|
||||
import pkg_resources
|
||||
import psutil
|
||||
import signal
|
||||
from io import StringIO
|
||||
|
||||
import ctypes
|
||||
@ -56,6 +57,7 @@ import socket
|
||||
|
||||
from .wexpect_util import init_logger
|
||||
from .wexpect_util import EOF_CHAR
|
||||
from .wexpect_util import SIGNAL_CHARS
|
||||
|
||||
#
|
||||
# System-wide constants
|
||||
@ -109,6 +111,7 @@ class ConsoleReaderBase:
|
||||
self.host_process = psutil.Process(host_pid)
|
||||
self.child_process = None
|
||||
self.child_pid = None
|
||||
self.enable_signal_chars = True
|
||||
|
||||
logger.info("ConsoleReader started")
|
||||
|
||||
@ -130,6 +133,8 @@ class ConsoleReaderBase:
|
||||
0, None, None, si)
|
||||
self.child_process = psutil.Process(self.child_pid)
|
||||
|
||||
logger.info(f'Child pid: {self.child_pid} Console pid: {self.console_pid}')
|
||||
|
||||
except:
|
||||
logger.info(traceback.format_exc())
|
||||
return
|
||||
@ -176,6 +181,11 @@ class ConsoleReaderBase:
|
||||
logger.debug(f'get_from_host: {s}')
|
||||
else:
|
||||
logger.spam(f'get_from_host: {s}')
|
||||
if self.enable_signal_chars:
|
||||
for sig,char in SIGNAL_CHARS.items():
|
||||
if char in s:
|
||||
self.child_process.send_signal(sig)
|
||||
s = s.decode()
|
||||
self.write(s)
|
||||
|
||||
if cursorPos.Y > maxconsoleY and not paused:
|
||||
@ -201,7 +211,7 @@ class ConsoleReaderBase:
|
||||
def isalive(self, process):
|
||||
"""True if the child is still alive, false otherwise"""
|
||||
try:
|
||||
process.wait(timeout=0)
|
||||
self.exitstatus = process.wait(timeout=0)
|
||||
return False
|
||||
except psutil.TimeoutExpired:
|
||||
return True
|
||||
@ -481,7 +491,7 @@ class ConsoleReaderSocket(ConsoleReaderBase):
|
||||
# timeout exception is setup
|
||||
if err == 'timed out':
|
||||
logger.debug('recv timed out, retry later')
|
||||
return ''
|
||||
return b''
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
@ -489,7 +499,7 @@ class ConsoleReaderSocket(ConsoleReaderBase):
|
||||
raise Exception('orderly shutdown on server end')
|
||||
else:
|
||||
# got a message do something :)
|
||||
return msg.decode()
|
||||
return msg
|
||||
|
||||
|
||||
class ConsoleReaderPipe(ConsoleReaderBase):
|
||||
@ -526,6 +536,6 @@ class ConsoleReaderPipe(ConsoleReaderBase):
|
||||
if avail > 0:
|
||||
resp = win32file.ReadFile(self.pipe, 4096)
|
||||
ret = resp[1]
|
||||
return ret.decode()
|
||||
return ret
|
||||
else:
|
||||
return ''
|
||||
return b''
|
||||
|
@ -89,6 +89,7 @@ from .wexpect_util import TIMEOUT
|
||||
from .wexpect_util import split_command_line
|
||||
from .wexpect_util import init_logger
|
||||
from .wexpect_util import EOF_CHAR
|
||||
from .wexpect_util import SIGNAL_CHARS
|
||||
|
||||
logger = logging.getLogger('wexpect')
|
||||
|
||||
@ -389,26 +390,10 @@ class SpawnBase:
|
||||
return self.console_process
|
||||
|
||||
def get_child_process(self, force=False):
|
||||
'''Fetches and returns the child process (and pid)
|
||||
|
||||
The console starts the *real* child. This function fetches this *real* child's process ID
|
||||
and process handle. If the console process is slower,(the OS does not grant enough CPU for
|
||||
that), the child, cannot be started, when we reach this function, therefore the
|
||||
`self.get_console_process().children()` line will return an empty list. So we ask console's child
|
||||
in a loop, while, we found a (the) child.
|
||||
This loop cannot be an infinite loop. If the console's process has error before/during
|
||||
starting the child. `self.get_console_process().children()` will throw error.
|
||||
'''
|
||||
if force or self.console_process is None:
|
||||
while True:
|
||||
children = self.get_console_process().children()
|
||||
try:
|
||||
self.child_process = children[0]
|
||||
except IndexError:
|
||||
time.sleep(.1)
|
||||
continue
|
||||
self.child_pid = self.child_process.pid
|
||||
return self.child_process
|
||||
self.child_process = self.get_console_process()
|
||||
self.child_pid = self.child_process.pid
|
||||
return self.child_process
|
||||
|
||||
def close(self): # File-like object.
|
||||
""" Closes the child console."""
|
||||
@ -444,7 +429,7 @@ class SpawnBase:
|
||||
"""Sig == sigint for ctrl-c otherwise the child is terminated."""
|
||||
try:
|
||||
self.child_process.send_signal(sig)
|
||||
except psutil._exceptions.NoSuchProcess as e:
|
||||
except psutil.NoSuchProcess as e:
|
||||
logger.info('Child has already died. %s', e)
|
||||
|
||||
def wait(self, child=True, console=True):
|
||||
@ -562,8 +547,7 @@ class SpawnBase:
|
||||
"""This is like send(), but it adds a line feed (os.linesep). This
|
||||
returns the number of bytes written. """
|
||||
|
||||
n = self.send(s)
|
||||
n = n + self.send(b'\r\n')
|
||||
n = self.send(s+'\r\n')
|
||||
return n
|
||||
|
||||
def sendeof(self):
|
||||
@ -580,7 +564,18 @@ class SpawnBase:
|
||||
char = chr(4)
|
||||
self.send(char)
|
||||
|
||||
def send(self):
|
||||
def send(self, s, delaybeforesend=None):
|
||||
"""Virtual definition
|
||||
"""
|
||||
if delaybeforesend is None:
|
||||
delaybeforesend = self.delaybeforesend
|
||||
|
||||
if delaybeforesend:
|
||||
time.sleep(delaybeforesend)
|
||||
|
||||
return self._send_impl(s)
|
||||
|
||||
def _send_impl(self, s):
|
||||
"""Virtual definition
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@ -773,6 +768,8 @@ class SpawnBase:
|
||||
end_time = time.time() + timeout
|
||||
if searchwindowsize == -1:
|
||||
searchwindowsize = self.searchwindowsize
|
||||
|
||||
logger.debug(f'searcher: {searcher}')
|
||||
|
||||
try:
|
||||
incoming = self.buffer
|
||||
@ -916,14 +913,12 @@ class SpawnPipe(SpawnBase):
|
||||
else:
|
||||
raise
|
||||
|
||||
def send(self, s):
|
||||
def _send_impl(self, s):
|
||||
"""This sends a string to the child process. This returns the number of
|
||||
bytes written. If a log file was set then the data is also written to
|
||||
the log. """
|
||||
if isinstance(s, str):
|
||||
s = str.encode(s)
|
||||
if self.delaybeforesend:
|
||||
time.sleep(self.delaybeforesend)
|
||||
try:
|
||||
if s:
|
||||
logger.debug(f"Writing: {s}")
|
||||
@ -942,6 +937,14 @@ class SpawnPipe(SpawnBase):
|
||||
else:
|
||||
raise
|
||||
return len(s)
|
||||
|
||||
def kill(self, sig=signal.SIGTERM):
|
||||
"""Sig == sigint for ctrl-c otherwise the child is terminated."""
|
||||
try:
|
||||
logger.info(f'Sending kill signal: {sig}')
|
||||
self.send(SIGNAL_CHARS[sig])
|
||||
except EOF as e:
|
||||
logger.info(e)
|
||||
|
||||
|
||||
class SpawnSocket(SpawnBase):
|
||||
@ -957,14 +960,12 @@ class SpawnSocket(SpawnBase):
|
||||
super().__init__(command=command, args=args, timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, cwd=cwd, env=env, codepage=codepage, echo=echo, interact=interact)
|
||||
|
||||
def send(self, s):
|
||||
def _send_impl(self, s):
|
||||
"""This sends a string to the child process. This returns the number of
|
||||
bytes written. If a log file was set then the data is also written to
|
||||
the log. """
|
||||
if isinstance(s, str):
|
||||
s = str.encode(s)
|
||||
if self.delaybeforesend:
|
||||
time.sleep(self.delaybeforesend)
|
||||
self.sock.sendall(s)
|
||||
return len(s)
|
||||
|
||||
@ -1015,7 +1016,15 @@ class SpawnSocket(SpawnBase):
|
||||
return ''
|
||||
|
||||
return s.decode()
|
||||
|
||||
|
||||
def kill(self, sig=signal.SIGTERM):
|
||||
"""Sig == sigint for ctrl-c otherwise the child is terminated."""
|
||||
try:
|
||||
logger.info(f'Sending kill signal: {sig}')
|
||||
self.send(SIGNAL_CHARS[sig])
|
||||
except EOF as e:
|
||||
logger.info(e)
|
||||
|
||||
|
||||
class searcher_re (object):
|
||||
"""This is regular expression string search helper for the
|
||||
|
@ -1125,19 +1125,15 @@ class Wtty:
|
||||
logger.info(f"Fetch child's process and pid...")
|
||||
|
||||
while True:
|
||||
logger.info(f"0")
|
||||
msg = win32gui.GetMessage(0, 0, 0)
|
||||
logger.info(f"0b")
|
||||
childPid = msg[1][2]
|
||||
# Sometimes win32gui.GetMessage returns a bogus PID, so keep calling it
|
||||
# until we can successfully connect to the child or timeout is
|
||||
# reached
|
||||
if childPid:
|
||||
try:
|
||||
logger.info(f"1")
|
||||
self.__childProcess = win32api.OpenProcess(
|
||||
win32con.PROCESS_TERMINATE | win32con.PROCESS_QUERY_INFORMATION, False, childPid)
|
||||
logger.info(f"2")
|
||||
self.__conProcess = win32api.OpenProcess(
|
||||
win32con.PROCESS_TERMINATE | win32con.PROCESS_QUERY_INFORMATION, False, self.conpid)
|
||||
except pywintypes.error as e:
|
||||
|
@ -42,10 +42,16 @@ import traceback
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import signal
|
||||
|
||||
# platform does not define VEOF so assume CTRL-D
|
||||
EOF_CHAR = b'\x04'
|
||||
|
||||
SIGNAL_CHARS = {
|
||||
signal.SIGTERM: b'\x011', # Device control 1
|
||||
signal.SIGINT: b'\x012', # Device control 2
|
||||
}
|
||||
|
||||
SPAM = 5
|
||||
logging.addLevelName(SPAM, "SPAM")
|
||||
def spam(self, message, *args, **kws):
|
||||
|
Loading…
Reference in New Issue
Block a user