codekingpro/portable-devtools
114k
1"""2This module provides an interface to spawn a detached task to be3run with httpie.internal.daemon_runner on a separate process. It is4based on DVC's daemon system.5https://github.com/iterative/dvc/blob/main/dvc/daemon.py6"""7 8import inspect9import os10import platform11import sys12import httpie.__main__13from contextlib import suppress14from subprocess import Popen, DEVNULL15from typing import Dict, List16from httpie.compat import is_frozen, is_windows17 18 19ProcessContext = Dict[str, str]20 21 22def _start_process(cmd: List[str], **kwargs) -> Popen:23 prefix = [sys.executable]24 # If it is frozen, sys.executable points to the binary (http).25 # Otherwise it points to the python interpreter.26 if not is_frozen:27 main_entrypoint = httpie.__main__.__file__28 prefix += [main_entrypoint]29 return Popen(prefix + cmd, close_fds=True, shell=False, stdout=DEVNULL, stderr=DEVNULL, **kwargs)30 31 32def _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None:33 from subprocess import (34 CREATE_NEW_PROCESS_GROUP,35 CREATE_NO_WINDOW,36 STARTF_USESHOWWINDOW,37 STARTUPINFO,38 )39 40 # https://stackoverflow.com/a/700642441 # https://bugs.python.org/issue4161942 creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW43 44 startupinfo = STARTUPINFO()45 startupinfo.dwFlags |= STARTF_USESHOWWINDOW46 47 _start_process(48 cmd,49 env=process_context,50 creationflags=creationflags,51 startupinfo=startupinfo,52 )53 54 55def _spawn_posix(args: List[str], process_context: ProcessContext) -> None:56 """57 Perform a double fork procedure* to detach from the parent58 process so that we don't block the user even if their original59 command's execution is done but the release fetcher is not.60 61 [1]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_0362 """63 64 from httpie.core import main65 66 try:67 pid = os.fork()68 if pid > 0:69 return70 except OSError:71 os._exit(1)72 73 os.setsid()74 75 try:76 pid = os.fork()77 if pid > 0:78 os._exit(0)79 except OSError:80 os._exit(1)81 82 # Close all standard inputs/outputs83 sys.stdin.close()84 sys.stdout.close()85 sys.stderr.close()86 87 if platform.system() == 'Darwin':88 # Double-fork is not reliable on MacOS, so we'll use a subprocess89 # to ensure the task is isolated properly.90 process = _start_process(args, env=process_context)91 # Unlike windows, since we already completed the fork procedure92 # we can simply join the process and wait for it.93 process.communicate()94 else:95 os.environ.update(process_context)96 with suppress(BaseException):97 main(['http'] + args)98 99 os._exit(0)100 101 102def _spawn(args: List[str], process_context: ProcessContext) -> None:103 """104 Spawn a new process to run the given command.105 """106 if is_windows:107 _spawn_windows(args, process_context)108 else:109 _spawn_posix(args, process_context)110 111 112def spawn_daemon(task: str) -> None:113 args = [task, '--daemon']114 process_context = os.environ.copy()115 if not is_frozen:116 file_path = os.path.abspath(inspect.stack()[0][1])117 process_context['PYTHONPATH'] = os.path.dirname(118 os.path.dirname(os.path.dirname(file_path))119 )120 121 _spawn(args, process_context)122 