def reader(fd2pid, names, masters, slaves, process_name, basedir, is_main, syncpipe_r, syncpipe_w, q): # don't get killed when we kill the job (will exit on EOF, so no output is lost) os.setpgrp() # we are safe now, the main process can continue os.close(syncpipe_w) os.close(syncpipe_r) signal.signal(signal.SIGTERM, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN) setproctitle(process_name) out_fd = int(os.environ['BD_TERM_FD']) for fd in slaves: os.close(fd) if q: q.make_writer() fd2fd = {} if not is_main: os.chdir(basedir) fd2name = dict(zip(masters, names)) outputs = dict.fromkeys(masters, b'') if len(fd2pid) == 2: status_blacklist = set(fd2pid.values()) assert len( status_blacklist ) == 1, "fd2pid should only map to 1 value initially: %r" % ( fd2pid, ) else: status_blacklist = () assert len( fd2pid) == 1, "fd2pid should have 1 or 2 elements initially" missed = [False] output_happened = False def try_print(data=b'\n\x1b[31m*** Some output not printed ***\x1b[m\n'): try: os.write(out_fd, data) except OSError: missed[0] = True # set output nonblocking, so we can't be blocked by terminal io. # errors generated here go to stderr, which is the real stderr # in the main iowrapper (so it can block) and goes to the main # iowrapper in the method iowrappers (so it can still block, but # is unlikely to do so for long). with nonblocking(out_fd): while masters: if missed[0]: # Some output failed to print last time around. # Wait up to one second for new data and then try # to write a message about that (before the new data). ready, _, _ = select(masters, [], [], 1.0) missed[0] = False try_print() else: ready, _, _ = select(masters, [], []) for fd in ready: try: data = os.read(fd, 65536) except OSError as e: # On Linux a pty will return # OSError: [Errno 5] Input/output error # instead of b'' for EOF. Don't know why. # Let's try to be a little restrictive in what we catch. if e.errno != errno.EIO: raise data = b'' if data: if not is_main: if fd not in fd2pid: fd2pid[fd] = int(data[:16], 16) data = data[16:] if not data: continue if fd not in fd2fd: fd2fd[fd] = os.open(fd2name[fd], os.O_CREAT | os.O_WRONLY, 0o666) os.write(fd2fd[fd], data) try_print(data) output_happened = True if not is_main: outputs[fd] = (outputs[fd] + data[-MAX_OUTPUT:])[-MAX_OUTPUT:] statmsg._output(fd2pid[fd], outputs[fd].decode('utf-8', 'replace')) else: if q: # let fork_analysis know it's time to wake up # (in case the process died badly and didn't put anything in q) q.try_notify() if fd in fd2fd: os.close(fd2fd[fd]) del fd2fd[fd] masters.remove(fd) os.close(fd) if not is_main: try: pid = fd2pid.pop(fd) if pid in status_blacklist: # don't do it for prepare as synthesis has the same PID. status_blacklist.remove(pid) # but clear the output if needed. if outputs[fd]: statmsg._clear_output(pid) else: statmsg._end(pid=pid) except Exception: # Failure can happen here if the method exits # before analysis (fd2pid not fully populated). pass if missed[0]: missed[0] = False try_print() if missed[0]: # Give it a little time, then give up. sleep(0.03) missed[0] = False try_print() if not output_happened and not is_main: os.chdir('..') os.rmdir(basedir)
def reader(fd2pid, names, masters, slaves, process_name, basedir, is_main): signal.signal(signal.SIGTERM, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN) setproctitle(process_name) out_fd = int(os.environ['BD_TERM_FD']) os.chdir(basedir) for fd in slaves: os.close(fd) if is_main: fd = os.open(names, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o666) fd2fd = dict.fromkeys(masters, fd) else: fd2name = dict(zip(masters, names)) fd2fd = {} outputs = dict.fromkeys(masters, b'') if len(fd2pid) == 2: status_blacklist = set(fd2pid.values()) assert len(status_blacklist) == 1, "fd2pid should only map to 1 value initially: %r" % (fd2pid,) else: status_blacklist = () assert len(fd2pid) == 1, "fd2pid should have 1 or 2 elements initially" missed = [False] output_happened = False def try_print(data=b'\n\x1b[31m*** Some output not printed ***\x1b[m\n'): try: os.write(out_fd, data) except OSError: missed[0] = True # set output nonblocking, so we can't be blocked by terminal io. # errors generated here go to stderr, which is the real stderr # in the main iowrapper (so it can block) and goes to the main # iowrapper in the method iowrappers (so it can still block, but # is unlikely to do so for long, and will end up in the log). with nonblocking(out_fd): while masters: if missed[0]: # Some output failed to print last time around. # Wait up to one second for new data and then try # to write a message about that (before the new data). ready, _, _ = select(masters, [], [], 1.0) missed[0] = False try_print() else: ready, _, _ = select(masters, [], []) for fd in ready: data = os.read(fd, 65536) if data: if not is_main: if fd not in fd2pid: fd2pid[fd] = unpack("=Q", data[:8])[0] data = data[8:] if not data: continue if fd not in fd2fd: fd2fd[fd] = os.open(fd2name[fd], os.O_CREAT | os.O_WRONLY, 0o666) os.write(fd2fd[fd], data) try_print(data) output_happened = True if not is_main: outputs[fd] = (outputs[fd] + data[-MAX_OUTPUT:])[-MAX_OUTPUT:] status._output(fd2pid[fd], outputs[fd].decode('utf-8', 'replace')) else: if fd in fd2fd: os.close(fd2fd[fd]) del fd2fd[fd] masters.remove(fd) os.close(fd) if not is_main: try: pid = fd2pid.pop(fd) if pid in status_blacklist: # don't do it for prepare as synthesis has the same PID. status_blacklist.remove(pid) else: status._end(pid=pid) except Exception: # Failure can happen here if the method exits # before analysis (fd2pid not fully populated). pass if missed[0]: missed[0] = False try_print() if missed[0]: # Give it a little time, then give up. sleep(0.03) try_print() if not output_happened and not is_main: os.chdir('..') os.rmdir(basedir)