Open Lx opened 1 year ago
If you're okay with bringing in one small dependency (detach3k
), then swapping out the subprocess.Popen
call here:
with detach.call
resolves this perfectly for me:
proc = detach.call(args)
Looking at the source for detach.call
, it just forks, spawns (using subprocess.Popen
with null stdin
/stdout
/stderr
streams) and _exit
s the fork. Popen
would probably still complain at destruction if not for the immediate _exit
—but hey, it stops the warnings from being emitted, and I have no child processes hanging around (presumably thanks to the null I/O streams).
detach.call
docs for your interest:
call(args, stdout=None, stderr=None, stdin=None, daemonize=False, preexec_fn=None, shell=False, cwd=None, env=None)
Run an external command in a separate process and detach it from the current process. Excepting
stdout
,stderr
, andstdin
all file descriptors are closed after forking. Ifdaemonize
is True then the parent process exits. All stdio is redirected toos.devnull
unless specified. Thepreexec_fn
,shell
,cwd
, andenv
parameters are the same as theirPopen
counterparts. Return the PID of the child process if not daemonized.
Hello, thanks for your research into this issue. Unfortunately, I developed a serious typing injury while coding early this year (I'm dictating this using a voice recognition program). Avoiding doing any more coding until I can recover has not proved possible, but nonetheless, I would like very much to avoid doing it unless it is absolutely necessary. Is it possible for you to investigate making the change using the external library you suggest, and then testing it on all platforms (Windows, Linux, and macOS)?
The mentioned library relies on os.fork(…)
which is not supported on Windows (see os.fork(…) documentation). The library uses a common trick, a double fork, to create an orphan process that is than handled/reaped by the init
process.
The warning can be ignored if it is produced upon terminating the main process in which case all child processes are reaped eventually and no resources leak. But currently it seems there is indeed a memory leak produced by zombie processes.
After a bit of research I think this solution would work:
import subprocess
def run(
args, shell=False, close_fds=True, stdin=None, stdout=None, stderr=None, **kwargs
):
process = subprocess.Popen(
args,
shell=shell,
close_fds=close_fds,
stdin=stdin,
stdout=stdout,
stderr=stderr,
**kwargs,
)
# We keep a reference to the process to be able to reap it.
# This avoids a memory leak (zombie processes) for processes that
# terminate before their parent.
run.processes.append(process)
run.processes = [p for p in run.processes if p.poll() is None]
return process
run.processes = []
This should avoid the memory leak but there might still be harmless warnings upon termination. Unless there are a lot of processes started this way polling them all every time should not be a performance issue.
Maybe that can be directly implemented inside _launch_file_manager
because it is a bit specific.
Although a bit hacky we could even silence the warning upon interpreter termination (adding to my previous snippet):
import atexit
def _silence_zombie_process_warning():
for process in run.processes:
# monkey patch to silence Popen warning
process.returncode = 0
atexit.register(_silence_zombie_process_warning)
By the way, I think P_DETACH
is identical to P_NOWAIT
and would give us no warning but still a zombie process. Starting or waiting in a thread does work I think.
@damonlynch I read on your website (very impressive, by the way!) that your injury might last a while. Would be interested in a pull request with this? I could test it on macOS and Windows.
Any updates on this issue?
If the call returned an object to control the process (including termination which would close the window) that would be of interest.
I have
warnings
andtracemalloc
enabled in my project, which causes_launch_file_manager
to emit this message onstderr
:From the Python v3.6+ docs for
subprocess.Popen
:No information on why—thankfully though, the relevant source code in the
Popen
destructor sheds a little more light:Indeed, in my case, an
open
child process hangs around until my main Python process exits.showinfm
should probably clean up its child processes somehow:Maybe it could fully detach from the process once it starts.
subprocess.Popen
though.os.spawnl
andP_DETACH
.Maybe it could use
subprocess.call
instead.