issue #406: don't leak FDs on failed child start.

This commit is contained in:
David Wilson 2018-11-03 15:49:52 +00:00
parent 6ff1e001da
commit 14b389cb46
2 changed files with 17 additions and 2 deletions

View File

@ -210,6 +210,9 @@ Core Library
:class:`mitogen.core.Broker` did not call :meth:`mitogen.core.Poller.close` :class:`mitogen.core.Broker` did not call :meth:`mitogen.core.Poller.close`
during shutdown, leaking the underlying poller FD in masters and parents. during shutdown, leaking the underlying poller FD in masters and parents.
* `#406 <https://github.com/dw/mitogen/issues/406>`_: connections could leak
FDs when a child process failed to start.
* `#411 <https://github.com/dw/mitogen/issues/411>`_: the SSH method typed * `#411 <https://github.com/dw/mitogen/issues/411>`_: the SSH method typed
"``y``" rather than the requisite "``yes``" when `check_host_keys="accept"` "``y``" rather than the requisite "``yes``" when `check_host_keys="accept"`
was configured. This would lead to connection timeouts due to the hung was configured. This would lead to connection timeouts due to the hung

View File

@ -211,7 +211,7 @@ def create_socketpair():
return parentfp, childfp return parentfp, childfp
def detach_popen(*args, **kwargs): def detach_popen(close_on_error=None, **kwargs):
""" """
Use :class:`subprocess.Popen` to construct a child process, then hack the Use :class:`subprocess.Popen` to construct a child process, then hack the
Popen so that it forgets the child it created, allowing it to survive a Popen so that it forgets the child it created, allowing it to survive a
@ -223,6 +223,8 @@ def detach_popen(*args, **kwargs):
delivered to this process, causing later 'legitimate' calls to fail with delivered to this process, causing later 'legitimate' calls to fail with
ECHILD. ECHILD.
:param list close_on_error:
Array of integer file descriptors to close on exception.
:returns: :returns:
Process ID of the new child. Process ID of the new child.
""" """
@ -230,7 +232,13 @@ def detach_popen(*args, **kwargs):
# handling, without tying the surrounding code into managing a Popen # handling, without tying the surrounding code into managing a Popen
# object, which isn't possible for at least :mod:`mitogen.fork`. This # object, which isn't possible for at least :mod:`mitogen.fork`. This
# should be replaced by a swappable helper class in a future version. # should be replaced by a swappable helper class in a future version.
proc = subprocess.Popen(*args, **kwargs) try:
proc = subprocess.Popen(**kwargs)
except Exception:
for fd in close_on_error or ():
os.close(fd)
raise
proc._child_created = False proc._child_created = False
return proc.pid return proc.pid
@ -277,6 +285,7 @@ def create_child(args, merge_stdio=False, stderr_pipe=False, preexec_fn=None):
stdout=childfp, stdout=childfp,
close_fds=True, close_fds=True,
preexec_fn=preexec_fn, preexec_fn=preexec_fn,
close_on_error=[parentfp.fileno(), childfp.fileno()],
**extra **extra
) )
if stderr_pipe: if stderr_pipe:
@ -344,6 +353,7 @@ def tty_create_child(args):
stdout=slave_fd, stdout=slave_fd,
stderr=slave_fd, stderr=slave_fd,
preexec_fn=_acquire_controlling_tty, preexec_fn=_acquire_controlling_tty,
close_on_error=[master_fd, slave_fd],
close_fds=True, close_fds=True,
) )
@ -372,6 +382,7 @@ def hybrid_tty_create_child(args):
mitogen.core.set_block(childfp) mitogen.core.set_block(childfp)
disable_echo(master_fd) disable_echo(master_fd)
disable_echo(slave_fd) disable_echo(slave_fd)
pid = detach_popen( pid = detach_popen(
args=args, args=args,
stdin=childfp, stdin=childfp,
@ -379,6 +390,7 @@ def hybrid_tty_create_child(args):
stderr=slave_fd, stderr=slave_fd,
preexec_fn=_acquire_controlling_tty, preexec_fn=_acquire_controlling_tty,
close_fds=True, close_fds=True,
close_on_error=[master_fd, slave_fd, parentfp.fileno(), childfp.fileno()],
) )
os.close(slave_fd) os.close(slave_fd)