Skip to content

Commit

Permalink
chore(gevent): run after-in-child hooks after reinit
Browse files Browse the repository at this point in the history
Threads created too early in an application that uses gevent end up not
running after the gevent hub reinit is executed after fork in the child
process. This change ensures that we trigger the after-in-child fork
hooks after the call to gevent.hub.reinit to ensure that threads created
at any time will run as expected after fork.
  • Loading branch information
P403n1x87 committed Aug 10, 2022
1 parent 7c83add commit 98cb106
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 4 deletions.
7 changes: 6 additions & 1 deletion ddtrace/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from ._logger import configure_ddtrace_logger
from ddtrace.internal.module import ModuleWatchdog


ModuleWatchdog.install()

from ._logger import configure_ddtrace_logger # noqa: E402


# configure ddtrace logger before other modules log
Expand Down
20 changes: 20 additions & 0 deletions ddtrace/internal/forksafe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import typing
import weakref

from ddtrace.internal.module import ModuleWatchdog
from ddtrace.vendor import wrapt


Expand All @@ -22,6 +23,25 @@
_soft = False


def patch_gevent_hub_reinit(module):
# The gevent hub is re-initialized *after* the after-in-child fork hooks are
# called, so we patch the gevent.hub.reinit function to ensure that the
# fork hooks run again after this further re-initialisation, if it is ever
# called.
from ddtrace.internal.wrapping import wrap

def wrapped_reinit(f, args, kwargs):
try:
return f(*args, **kwargs)
finally:
ddtrace_after_in_child()

wrap(module.reinit, wrapped_reinit)


ModuleWatchdog.register_module_hook("gevent.hub", patch_gevent_hub_reinit)


def ddtrace_after_in_child():
# type: () -> None
global _registry
Expand Down
30 changes: 27 additions & 3 deletions tests/internal/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@
import tests.test_module


@pytest.fixture(autouse=True, scope="module")
def ensure_no_module_watchdog():
# DEV: The library might use the ModuleWatchdog and install it at a very
# early stage. This fixture ensures that the watchdog is not installed
# before the tests start.
was_installed = ModuleWatchdog.is_installed()
if was_installed:
ModuleWatchdog.uninstall()

yield

if was_installed:
ModuleWatchdog.install()


@pytest.fixture
def module_watchdog():
ModuleWatchdog.install()
Expand Down Expand Up @@ -65,7 +80,10 @@ def test_import_origin_hook_for_module_not_yet_imported():
path = os.getenv("MODULE_ORIGIN")
hook = mock.Mock()

ModuleWatchdog.install()
try:
ModuleWatchdog.install()
except RuntimeError:
pass

ModuleWatchdog.register_origin_hook(path, hook)

Expand Down Expand Up @@ -100,7 +118,10 @@ def test_import_module_hook_for_module_not_yet_imported():
name = "tests.test_module"
hook = mock.Mock()

ModuleWatchdog.install()
try:
ModuleWatchdog.install()
except RuntimeError:
pass

ModuleWatchdog.register_module_hook(name, hook)

Expand Down Expand Up @@ -136,7 +157,10 @@ def test_module_deleted():
path = os.getenv("MODULE_ORIGIN")
hook = mock.Mock()

ModuleWatchdog.install()
try:
ModuleWatchdog.install()
except RuntimeError:
pass

ModuleWatchdog.register_origin_hook(path, hook)
ModuleWatchdog.register_module_hook(name, hook)
Expand Down

0 comments on commit 98cb106

Please sign in to comment.