blob: d9f8759f70d188be6aefa7123e961b1eb0ac8a6d [file] [log] [blame]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Status change events module.
This is used to standardize communication of events between processes
through a pipe, generally through stdout.
run_event_command() starts a process that sends such events to stdout
and handles them through a callback.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import os
from signal import SIGHUP
from signal import SIGINT
from signal import SIGTERM
import enum
import subprocess32
from lucifer import sigtrap
logger = logging.getLogger(__name__)
class Event(enum.Enum):
"""Status change event enum
Members of this enum represent all possible status change events
that can be emitted by an event command and that need to be handled
by the caller.
The value of enum members must be a string, which is printed by
itself on a line to signal the event.
This should be backward compatible with all versions of
job_shepherd, which lives in the infra/lucifer repository.
"""
STARTING = 'starting'
COMPLETED = 'completed'
def run_event_command(event_handler, args, logfile):
"""Run a command that emits events.
Events printed by the command will be handled by event_handler.
While the process for the command is running, trapped signals will
be passed on to it so it can abort gracefully.
@param event_handler: callable that takes an Event instance.
@param args: passed to subprocess.Popen.
@param logfile: file to store stderr. Must be associated
with a file descriptor.
"""
logger.debug('Starting event command with %r', args)
with open(os.devnull, 'w+') as devnull, \
sigtrap.handle_signal(SIGHUP, lambda s, f: None), \
subprocess32.Popen(args,
stdin=devnull,
stdout=subprocess32.PIPE,
stderr=logfile) as proc, \
sigtrap.handle_signals([SIGINT, SIGTERM],
lambda s, f: os.kill(proc.pid, s)):
_handle_subprocess_events(event_handler, proc)
logger.debug('Subprocess exited with %d', proc.returncode)
return proc.returncode
def _handle_subprocess_events(event_handler, proc):
"""Handle a subprocess that emits events.
Events printed by the subprocess will be handled by event_handler.
@param event_handler: callable that takes an Event instance.
@param proc: Popen instance.
"""
while True:
logger.debug('Reading subprocess stdout')
line = proc.stdout.readline()
if line:
_handle_output_line(event_handler, line)
else:
break
def _handle_output_line(event_handler, line):
"""Handle a line of output from an event subprocess.
@param event_handler: callable that takes a StatusChangeEvent.
@param line: line of output.
"""
try:
event = Event(line.rstrip())
except ValueError:
logger.warning('Invalid output %r received', line)
else:
event_handler(event)