def _pipe_stdio(cls, maybe_shutdown_socket, stdin_isatty, stdout_isatty, stderr_isatty, handle_stdin): """Handles stdio redirection in the case of pipes and/or mixed pipes and ttys.""" stdio_writers = ((ChunkType.STDOUT, stdout_isatty), (ChunkType.STDERR, stderr_isatty)) types, ttys = zip(*(stdio_writers)) @contextmanager def maybe_handle_stdin(want): if want: with NailgunStreamStdinReader.open(maybe_shutdown_socket, stdin_isatty) as fd: yield fd else: with open("/dev/null", "rb") as fh: yield fh.fileno() # TODO https://github.com/pantsbuild/pants/issues/7653 with maybe_handle_stdin( handle_stdin) as stdin_fd, PipedNailgunStreamWriter.open_multi( maybe_shutdown_socket.socket, types, ttys) as ((stdout_pipe, stderr_pipe), writer), stdio_as(stdout_fd=stdout_pipe.write_fd, stderr_fd=stderr_pipe.write_fd, stdin_fd=stdin_fd): # N.B. This will be passed to and called by the `DaemonExiter` prior to sending an # exit chunk, to avoid any socket shutdown vs write races. stdout, stderr = sys.stdout, sys.stderr def finalizer(): try: stdout.flush() stderr.flush() finally: time.sleep( 0.001 ) # HACK: Sleep 1ms in the main thread to free the GIL. stdout_pipe.stop_writing() stderr_pipe.stop_writing() writer.join(timeout=60) if writer.isAlive(): raise NailgunStreamWriterError( "pantsd timed out while waiting for the stdout/err to finish writing to the socket." ) yield finalizer
def test_auto_shutdown_on_write_end_closed(self, mock_writer, mock_select, mock_read): pipe = Pipe.create(False) test_data = [b"A"] * 1000 + [b''] mock_read.side_effect = test_data mock_select.side_effect = [([pipe.read_fd], [], [])] * len(test_data) writer = PipedNailgunStreamWriter( pipes=[pipe], socket=self.mock_socket, chunk_type=(ChunkType.STDOUT,), chunk_eof_type=None, buf_size=len(b"A") ) with writer.running(): pipe.stop_writing() writer.join(1) self.assertFalse(writer.is_alive()) mock_writer.assert_has_calls([unittest.mock.call(unittest.mock.ANY, ChunkType.STDOUT, b'A')] * (len(test_data) - 1))