diff --git a/src/debugpy/adapter/__main__.py b/src/debugpy/adapter/__main__.py index a6705c069..204622a85 100644 --- a/src/debugpy/adapter/__main__.py +++ b/src/debugpy/adapter/__main__.py @@ -12,6 +12,7 @@ # WARNING: debugpy and submodules must not be imported on top level in this module, # and should be imported locally inside main() instead. +_CAN_DAEMONIZE = os.name == "posix" and hasattr(os, "fork") def main(): args = _parse_argv(sys.argv) @@ -31,12 +32,13 @@ def main(): if os.name == "posix": # On POSIX, we need to leave the process group and its session, and then # daemonize properly by double-forking (first fork already happened when - # this process was spawned). + # this process was spawned). Some POSIX runtimes, such as GraalPy, do not + # implement fork(), so in that case we settle for a single detached child. # NOTE: if process is already the session leader, then # setsid would fail with `operation not permitted` if os.getsid(os.getpid()) != os.getpid(): os.setsid() - if os.fork() != 0: + if _CAN_DAEMONIZE and os.fork() != 0: sys.exit(0) for stdio in sys.stdin, sys.stdout, sys.stderr: diff --git a/src/debugpy/launcher/debuggee.py b/src/debugpy/launcher/debuggee.py index ec0c9eff1..464cac1af 100644 --- a/src/debugpy/launcher/debuggee.py +++ b/src/debugpy/launcher/debuggee.py @@ -58,9 +58,12 @@ def spawn(process_name, cmdline, env, redirect_output): else: kwargs = {} - if sys.platform != "win32" and sys.implementation.name != 'graalpy': - # GraalPy does not support running code between fork and exec + # GraalPy does not support running code between fork and exec, but supports the + # process_group argument for Popen + if sys.platform != "win32" and sys.implementation.name == "graalpy": + kwargs.update(process_group=0) + if sys.platform != "win32" and sys.implementation.name != 'graalpy': def preexec_fn(): try: # Start the debuggee in a new process group, so that the launcher can diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index 81356783b..28795fa37 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -17,6 +17,7 @@ from debugpy.common.util import hide_debugpy_internals _tls = threading.local() +_CAN_DAEMONIZE = os.name == "posix" and hasattr(os, "fork") # TODO: "gevent", if possible. _config = { @@ -218,9 +219,11 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): creationflags=creationflags, env=python_env, ) - if os.name == "posix": + if _CAN_DAEMONIZE: # It's going to fork again to daemonize, so we need to wait on it to - # clean it up properly. + # clean it up properly. If we did not fork, we cannot take this path + # because it cannot perform that extra fork; waiting there would just + # preserve the broken assumption that a daemonized grandchild exists. _adapter_process.wait() else: # Suppress misleading warning about child process still being alive when