issue #397, #454: pick out only shutil.rmtree() calls from atexit.

issue510
David Wilson 6 years ago
parent 2959b7911e
commit cd01957995

@ -46,6 +46,7 @@ import json
import logging import logging
import os import os
import shlex import shlex
import shutil
import sys import sys
import tempfile import tempfile
import types import types
@ -354,6 +355,55 @@ class Runner(object):
self.revert() self.revert()
class AtExitWrapper(object):
"""
Newer Ansibles use :func:`atexit.register` to trigger tmpdir cleanup when
AnsibleModule.tmpdir is responsible for creating its own temporary
directory, however with Mitogen processes are preserved across tasks,
meaning cleanup must happen earlier.
Patch :func:`atexit.register`, catching :func:`shutil.rmtree` calls so they
can be executed on task completion, rather than on process shutdown.
"""
# Wrapped in a dict to avoid instance method decoration.
original = {
'register': atexit.register
}
def __init__(self):
assert atexit.register == self.original['register'], \
"AtExitWrapper installed twice."
atexit.register = self._atexit__register
self.deferred = []
def revert(self):
"""
Restore the original :func:`atexit.register`.
"""
assert atexit.register == self._atexit__register, \
"AtExitWrapper not installed."
atexit.register = self.original['register']
def run_callbacks(self):
while self.deferred:
func, targs, kwargs = self.deferred.pop()
try:
func(*targs, **kwargs)
except Exception:
LOG.exception('While running atexit callbacks')
def _atexit__register(self, func, *targs, **kwargs):
"""
Intercept :func:`atexit.register` calls, diverting any to
:func:`shutil.rmtree` into a private list.
"""
if func == shutil.rmtree:
self.deferred.append((func, targs, kwargs))
return
self.original_register(func, *targs, **kwargs)
class ModuleUtilsImporter(object): class ModuleUtilsImporter(object):
""" """
:param list module_utils: :param list module_utils:
@ -701,6 +751,7 @@ class NewStyleRunner(ScriptRunner):
) )
self._setup_imports() self._setup_imports()
self._setup_excepthook() self._setup_excepthook()
self.atexit_wrapper = AtExitWrapper()
if libc__res_init: if libc__res_init:
libc__res_init() libc__res_init()
@ -708,6 +759,7 @@ class NewStyleRunner(ScriptRunner):
sys.excepthook = self.original_excepthook sys.excepthook = self.original_excepthook
def revert(self): def revert(self):
self.atexit_wrapper.revert()
self._argv.revert() self._argv.revert()
self._stdio.revert() self._stdio.revert()
self._revert_excepthook() self._revert_excepthook()
@ -768,14 +820,6 @@ class NewStyleRunner(ScriptRunner):
self._handle_magic_exception(mod, e) self._handle_magic_exception(mod, e)
raise raise
def _run_atexit_funcs(self):
"""
Newer Ansibles use atexit.register() to trigger tmpdir cleanup, when
AnsibleModule.tmpdir is responsible for creating its own temporary
directory.
"""
atexit._run_exitfuncs()
def _run(self): def _run(self):
mod = types.ModuleType(self.main_module_name) mod = types.ModuleType(self.main_module_name)
mod.__package__ = None mod.__package__ = None
@ -793,10 +837,10 @@ class NewStyleRunner(ScriptRunner):
try: try:
try: try:
self._run_code(code, mod) self._run_code(code, mod)
finally: except SystemExit as e:
self._run_atexit_funcs() exc = e
except SystemExit as e: finally:
exc = e self.atexit_wrapper.run_callbacks()
return { return {
'rc': exc.args[0] if exc else 2, 'rc': exc.args[0] if exc else 2,

@ -210,6 +210,12 @@ Fixes
have a reduced open files limit. A more intrusive fix has been added to have a reduced open files limit. A more intrusive fix has been added to
directly address the problem without modifying the subprocess environment. directly address the problem without modifying the subprocess environment.
* `#397 <https://github.com/dw/mitogen/issues/397>`_,
`#454 <https://github.com/dw/mitogen/issues/454>`_: the previous approach to
handling modern Ansible temporary file cleanup was too aggressive, and could
trigger early finalization of Cython-based extension modules, leading to
segmentation faults.
Core Library Core Library
~~~~~~~~~~~~ ~~~~~~~~~~~~

Loading…
Cancel
Save