log : refactored acquire and release semantic to meet the context manager protocol

This commit is contained in:
alalazo 2016-07-14 09:34:01 +02:00
parent 00b8e0b567
commit 1ecea4c2f1
2 changed files with 80 additions and 73 deletions

View File

@ -101,19 +101,23 @@ def __exit__(self, exc_type, exception, traceback):
class log_output(object): class log_output(object):
"""Redirects output and error of enclosed block to a file. """Spawns a daemon that reads from a pipe and writes to a file
Usage: Usage:
with log_output('logfile.txt', 'w'): # Spawns the daemon
# do things ... output will be logged. with log_output('logfile.txt', 'w') as log_redirection:
# do things ... output is not redirected
with log_redirection:
# do things ... output will be logged
or: or:
with log_output('logfile.txt', echo=True): with log_output('logfile.txt', echo=True) as log_redirection:
# do things ... output is not redirected
with log_redirection:
# do things ... output will be logged # do things ... output will be logged
# and also printed to stdout. # and also printed to stdout.
Opens a stream in 'w' mode at instance creation and closes Opens a stream in 'w' mode at daemon spawning and closes it at daemon joining.
it at instance deletion
If echo is True, also prints the output to stdout. If echo is True, also prints the output to stdout.
""" """
def __init__(self, filename, echo=False, force_color=False, debug=False): def __init__(self, filename, echo=False, force_color=False, debug=False):
@ -128,32 +132,21 @@ def __init__(self, filename, echo=False, force_color=False, debug=False):
self.directAssignment = False self.directAssignment = False
self.read, self.write = os.pipe() self.read, self.write = os.pipe()
# Spawn a daemon that writes what it reads from a pipe # Sets a daemon that writes to file what it reads from a pipe
self.p = multiprocessing.Process(target=self._forward_redirected_pipe, args=(self.read,), name='logger_daemon') self.p = multiprocessing.Process(target=self._spawn_writing_daemon, args=(self.read,), name='logger_daemon')
self.p.daemon = True self.p.daemon = True
# I just need this to communicate to un-summon the daemon # Needed to un-summon the daemon
self.parent_pipe, self.child_pipe = multiprocessing.Pipe() self.parent_pipe, self.child_pipe = multiprocessing.Pipe()
def acquire(self): def __enter__(self):
self.p.start() self.p.start()
return log_output.OutputRedirection(self)
def release(self): def __exit__(self, exc_type, exc_val, exc_tb):
self.parent_pipe.send(True) self.parent_pipe.send(True)
self.p.join(60.0) # 1 minute to join the child self.p.join(60.0) # 1 minute to join the child
def __enter__(self): def _spawn_writing_daemon(self, read):
"""Redirect output from the with block to a file.
Hijacks stdout / stderr and writes to the pipe
connected to the logger daemon
"""
# remember these values for later.
self._force_color = color._force_color
self._debug = tty._debug
# Redirect this output to a pipe
self._redirect_to_pipe(self.write)
def _forward_redirected_pipe(self, read):
# Parent: read from child, skip the with block. # Parent: read from child, skip the with block.
read_file = os.fdopen(read, 'r', 0) read_file = os.fdopen(read, 'r', 0)
with open(self.filename, 'w') as log_file: with open(self.filename, 'w') as log_file:
@ -187,7 +180,26 @@ def _forward_redirected_pipe(self, read):
if self.child_pipe.poll(): if self.child_pipe.poll():
break break
def _redirect_to_pipe(self, write): def __del__(self):
"""Closes the pipes"""
os.close(self.write)
os.close(self.read)
class OutputRedirection(object):
def __init__(self, other):
self.__dict__.update(other.__dict__)
def __enter__(self):
"""Redirect output from the with block to a file.
Hijacks stdout / stderr and writes to the pipe
connected to the logger daemon
"""
# remember these values for later.
self._force_color = color._force_color
self._debug = tty._debug
# Redirect this output to a pipe
write = self.write
try: try:
# Save old stdout and stderr # Save old stdout and stderr
self._stdout = os.dup(sys.stdout.fileno()) self._stdout = os.dup(sys.stdout.fileno())
@ -226,8 +238,3 @@ def __exit__(self, exc_type, exception, traceback):
# restore output options. # restore output options.
color._force_color = self._force_color color._force_color = self._force_color
tty._debug = self._debug tty._debug = self._debug
def __del__(self):
"""Closes the pipes"""
os.close(self.write)
os.close(self.read)

View File

@ -1136,13 +1136,13 @@ def build_process():
self.log_path = log_path self.log_path = log_path
self.env_path = env_path self.env_path = env_path
dump_environment(env_path) dump_environment(env_path)
log_redirection = log_output(log_path, verbose, sys.stdout.isatty(), True) # Spawn a daemon that reads from a pipe and redirects everything to log_path
log_redirection.acquire() with log_output(log_path, verbose, sys.stdout.isatty(), True) as log_redirection:
for phase_name, phase in zip(self.phases, self._InstallPhase_phases): for phase_name, phase in zip(self.phases, self._InstallPhase_phases):
tty.msg('Executing phase : \'{0}\''.format(phase_name)) tty.msg('Executing phase : \'{0}\''.format(phase_name))
# Redirect stdout and stderr to daemon pipe
with log_redirection: with log_redirection:
getattr(self, phase)(self.spec, self.prefix) getattr(self, phase)(self.spec, self.prefix)
log_redirection.release()
self.log() self.log()
# Run post install hooks before build stage is removed. # Run post install hooks before build stage is removed.
spack.hooks.post_install(self) spack.hooks.post_install(self)