1010import atexit
1111import errno
1212import multiprocessing
13+ import threading
1314import os
1415import re
1516import select
@@ -50,6 +51,43 @@ def ignore_signal(signum):
5051 signal .signal (signum , old_handler )
5152
5253
54+ class AltSignalHandler (object ):
55+ def __init__ (self , orig_handler , alt_handler ):
56+ self .alt_handler = alt_handler
57+ self .orig_handler = orig_handler
58+ self .current_handler = orig_handler
59+
60+ def handle (self , signum , frame ):
61+ self .current_handler (signum , frame )
62+
63+ def use_alt_handler (self ):
64+ self .current_handler = self .alt_handler
65+
66+ def use_orig_handler (self ):
67+ self .current_handler = self .orig_handler
68+
69+
70+ class SignalHandler (object ):
71+ def __init__ (self ):
72+ self .sig_to_handler = {}
73+
74+ @staticmethod
75+ def ignore_signal (signum , frame ):
76+ pass
77+
78+ def register_alt_handler (self , signum , fn ):
79+ orig_handler = signal .signal (signum , fn )
80+ self .sig_to_handler [signum ] = AltSignalHandler (orig_handler , fn )
81+
82+ def can_ignore (self , signum ):
83+ self .register_alt_handler (signum , SignalHandler .ignore_signal )
84+
85+ def activate_alt_handler (self , signum ):
86+ self .sig_to_handler [signum ].use_alt_handler ()
87+ yield
88+ self .sig_to_handler [signum ].use_orig_handler ()
89+
90+
5391def _is_background_tty (stream ):
5492 """True if the stream is a tty and calling process is in the background.
5593 """
@@ -438,39 +476,33 @@ def __enter__(self):
438476 # OS-level pipe for redirecting output to logger
439477 read_fd , write_fd = os .pipe ()
440478
441- # Use multiprocessing.COnnection to transmit FD from the parent
442- # process to the child
443- read_wrapper = multiprocessing .connection .Connection (read_fd )
444-
445479 # Multiprocessing pipe for communication back from the daemon
446480 # Currently only used to save echo value between uses
447481 self .parent_pipe , child_pipe = multiprocessing .Pipe ()
448482
449483 # Sets a daemon that writes to file what it reads from a pipe
450484 try :
451- # need to pass this b/c multiprocessing closes stdin in child.
452485 try :
453- input_wrapper = multiprocessing .connection .Connection (
454- os .dup (sys .stdin .fileno ())
455- )
486+ input = os .fdopen (os .dup (sys .stdin .fileno ()))
456487 except BaseException :
457- input_wrapper = None # just don't forward input if this fails
488+ input = None # just don't forward input if this fails
458489
459- self .process = multiprocessing .Process (
490+ read = os .fdopen (read_fd , 'r' , 1 )
491+
492+ alt_stdout = os .fdopen (os .dup (self .saved_fds ._saved_stdout ), 'w' )
493+
494+ self .thread = threading .Thread (
460495 target = _writer_daemon ,
461496 args = (
462- input_wrapper , read_wrapper , self . echo , self . log_file ,
463- child_pipe
497+ input , read , alt_stdout ,
498+ self . echo , self . log_file , child_pipe
464499 )
465500 )
466- self .process .daemon = True # must set before start()
467- self .process .start ()
501+ self .thread .start ()
468502
469503 finally :
504+ # don't close input here
470505 pass
471- #if input_wrapper:
472- # input_wrapper.close()
473- #read_wrapper.close()
474506
475507 # Flush immediately before redirecting so that anything buffered
476508 # goes to the original stream
@@ -556,7 +588,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
556588
557589 # join the daemon process. The daemon will quit automatically
558590 # when the write pipe is closed; we just wait for it here.
559- self .process .join ()
591+ self .thread .join ()
560592
561593 # restore old color and debug settings
562594 tty .color ._force_color = self ._saved_color
@@ -584,7 +616,7 @@ def force_echo(self):
584616 sys .stdout .flush ()
585617
586618
587- def _writer_daemon (stdin_wrapper , read_wrapper , echo , log_file , control_pipe ):
619+ def _writer_daemon (stdin , read , alt_stdout , echo , log_file , control_pipe ):
588620 """Daemon used by ``log_output`` to write to a log file and to ``stdout``.
589621
590622 The daemon receives output from the parent process and writes it both
@@ -632,19 +664,16 @@ def _writer_daemon(stdin_wrapper, read_wrapper, echo, log_file, control_pipe):
632664 """
633665 # Use line buffering (3rd param = 1) since Python 3 has a bug
634666 # that prevents unbuffered text I/O.
635- in_pipe = os .fdopen (read_wrapper ._handle , 'r' , 1 )
636-
637- if stdin_wrapper :
638- stdin = os .fdopen (stdin_wrapper ._handle )
639- else :
640- stdin = None
667+ in_pipe = read
641668
642669 # list of streams to select from
643670 istreams = [in_pipe , stdin ] if stdin else [in_pipe ]
644671 force_echo = False # parent can force echo for certain output
645672
646673 log_file = log_file .unwrap ()
647674
675+ alt_stdout .write ("<<< Starting thread" )
676+
648677 try :
649678 with keyboard_input (stdin ) as kb :
650679 while True :
@@ -685,8 +714,8 @@ def _writer_daemon(stdin_wrapper, read_wrapper, echo, log_file, control_pipe):
685714
686715 # Echo to stdout if requested or forced.
687716 if echo or force_echo :
688- sys . stdout .write (line )
689- sys . stdout .flush ()
717+ alt_stdout .write (line )
718+ alt_stdout .flush ()
690719
691720 # Stripped output to log file.
692721 log_file .write (_strip (line ))
@@ -699,18 +728,16 @@ def _writer_daemon(stdin_wrapper, read_wrapper, echo, log_file, control_pipe):
699728
700729 except BaseException :
701730 tty .error ("Exception occurred in writer daemon!" )
702- traceback .print_exc ()
731+ traceback .print_exc (file = alt_stdout )
703732
704733 finally :
705734 # send written data back to parent if we used a StringIO
706735 if isinstance (log_file , StringIO ):
707736 control_pipe .send (log_file .getvalue ())
708737 log_file .close ()
709- read_wrapper .close ()
710- stdin_wrapper .close ()
711738
712- # send echo value back to the parent so it can be preserved.
713- control_pipe .send (echo )
739+ # send echo value back to the parent so it can be preserved.
740+ control_pipe .send (echo )
714741
715742
716743def _retry (function ):
0 commit comments