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