Improve lmc no-virt error handling

When monitoring log output in livemedia-creator --no-virt it could get
stuck if the output from anaconda stops for some reason.

This changes execReadlines so that it will only read output when it is
available, will monitor the process state, and continue to call the
callback function.

It also adds a final timeout on proc.communicate() so that if Anaconda
becomes stuck and won't exit livemedia-creator will eventually exit.

When the no-virt callback terminates anaconda on an error it now sends a
TERM signal to all of the unshare process' children because just sending
it to unshare doesn't cause anaconda to exit.
This commit is contained in:
Brian C. Lane 2020-12-08 15:57:51 -08:00
parent 07600b2418
commit 6400515880
4 changed files with 47 additions and 16 deletions

View File

@ -118,6 +118,7 @@ Requires: anaconda-core
Requires: anaconda-tui Requires: anaconda-tui
Requires: anaconda-install-env-deps Requires: anaconda-install-env-deps
Requires: system-logos Requires: system-logos
Requires: python3-psutil
%description lmc-novirt %description lmc-novirt
Additional dependencies required by livemedia-creator when using it with --no-virt Additional dependencies required by livemedia-creator when using it with --no-virt

View File

@ -19,9 +19,11 @@
# #
import os import os
import select
import subprocess import subprocess
from subprocess import TimeoutExpired from subprocess import TimeoutExpired
import signal import signal
import time
import logging import logging
log = logging.getLogger("pylorax") log = logging.getLogger("pylorax")
@ -288,6 +290,7 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None, filter_st
self._proc = proc self._proc = proc
self._argv = argv self._argv = argv
self._callback = callback self._callback = callback
self._data = ""
def __iter__(self): def __iter__(self):
return self return self
@ -302,14 +305,34 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None, filter_st
pass pass
def __next__(self): def __next__(self):
# Read the next line, blocking if a line is not yet available # Return lines from stdout while also calling _callback
line = self._proc.stdout.readline().decode("utf-8") while True:
if line == '' or not self._callback(self._proc): # Check for input without blocking
# Output finished, wait for the process to end if select.select([self._proc.stdout], [], [], 0)[0]:
self._proc.communicate() size = len(self._proc.stdout.peek(1))
if size > 0:
self._data += self._proc.stdout.read(size).decode("utf-8")
# Check for successful exit if self._data.find("\n") >= 0:
if self._proc.returncode < 0: line = self._data.split("\n", 1)
self._data = line[1]
return line[0]
if self._proc.poll() is not None or not self._callback(self._proc):
# Output finished, wait 60s for the process to end
try:
self._proc.communicate(timeout=60)
except subprocess.TimeoutExpired:
# Did not exit in 60s, kill it and wait 30s more
self._proc.kill()
try:
self._proc.communicate(timeout=30)
except subprocess.TimeoutExpired:
pass
if self._proc.returncode is None:
raise OSError("process '%s' failed to be killed" % self._argv)
elif self._proc.returncode < 0:
raise OSError("process '%s' was killed by signal %s" % raise OSError("process '%s' was killed by signal %s" %
(self._argv, -self._proc.returncode)) (self._argv, -self._proc.returncode))
elif self._proc.returncode > 0: elif self._proc.returncode > 0:
@ -317,7 +340,8 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None, filter_st
(self._argv, self._proc.returncode)) (self._argv, self._proc.returncode))
raise StopIteration raise StopIteration
return line.strip() # Don't loop too fast with no input to read
time.sleep(0.5)
argv = [command] + argv argv = [command] + argv

View File

@ -291,7 +291,12 @@ def novirt_cancel_check(cancel_funcs, proc):
""" """
for f in cancel_funcs: for f in cancel_funcs:
if f(): if f():
proc.terminate() # Anaconda runs from unshare, anaconda doesn't exit correctly so try to
# send TERM to all of them directly
import psutil
for p in psutil.Process(proc.pid).children(recursive=True):
p.terminate()
psutil.Process(proc.pid).terminate()
return True return True
return False return False

View File

@ -12,6 +12,7 @@ python3-librepo
python3-magic python3-magic
python3-mako python3-mako
python3-pocketlint python3-pocketlint
python3-psutil
python3-pycdlib python3-pycdlib
python3-pylint python3-pylint
python3-pyparted python3-pyparted