1import argparse 2import logging 3import os 4import sys 5import subprocess 6import threading 7 8from typing import Sequence, Optional, List 9 10import orangecanvas.utils.shtools as sh 11 12 13def run_after_exit( 14 command: Sequence[str] = (), log: Optional[str] = None 15) -> None: 16 """ 17 Run the `command` after this process exits. 18 """ 19 # pass read end of a pipe to subprocess. It blocks to read from it 20 # and will not succeed until the write end is closed which will happen at 21 # this process's exit (assuming `w` is not leaked in a fork). 22 command = ["--arg=" + c for c in command] 23 if log is not None: 24 command.append("--log=" + log) 25 command = ["-m", __name__, *command] 26 with __write_fds_lock: 27 r, w = os.pipe() 28 __write_fds.append(w) 29 p = sh.python_process( 30 command, 31 stdin=r, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, 32 ) 33 # close the read end of the pipe 34 os.close(r) 35 # Popen warns in __del__ if child did not complete yet (should 36 # double fork to do this right, but since we exit immediately anyways). 37 __run_after_exit_processes.append(p) 38 39 40__run_after_exit_processes: List[subprocess.Popen] = [] 41__write_fds: List[int] = [] 42__write_fds_lock = threading.Lock() 43 44 45def __close_write_fd_after_fork(): 46 while __write_fds: 47 w = __write_fds.pop() 48 try: 49 os.close(w) 50 except OSError: 51 pass 52 __write_fds_lock.release() 53 54 55if hasattr(os, "register_at_fork"): 56 os.register_at_fork( 57 before=__write_fds_lock.acquire, 58 after_in_child=__close_write_fd_after_fork, 59 after_in_parent=__write_fds_lock.release, 60 ) 61 62 63def main(argv): 64 ap = argparse.ArgumentParser() 65 ap.add_argument("-f", default=0, type=int) 66 ap.add_argument("-a", "--arg", action='append', default=[]) 67 ap.add_argument("--log", help="Log file", type=argparse.FileType("w")) 68 ns, rest = ap.parse_known_args(argv) 69 70 if ns.log is not None: 71 logging.basicConfig(level=logging.INFO, stream=ns.log) 72 log = logging.getLogger(__name__) 73 74 if ns.f is not None: 75 readfd = int(ns.f) 76 else: 77 readfd = 0 78 79 # read form readfd (an os.pipe read end) until EOF indicating parent 80 # closed the pipe (i.e. did exit) 81 log.info("Blocking on read from fd: %d", readfd) 82 c = os.read(readfd, 1) 83 if c != b"": 84 log.error("Unexpected content %r from parent", c) 85 else: 86 log.info("Parent closed fd; %d") 87 88 if ns.arg: 89 log.info("Starting new process with cmd: %r", ns.arg) 90 p = sh.create_process( 91 ns.arg, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, 92 ) 93 main.p = p 94 return 0 95 96 97if __name__ == "__main__": 98 sys.exit(main(sys.argv)) 99