Skip to content

[BUG]: forksafe _before_fork joins network-bound background threads, freezing the gevent hub on a subprocess fork #18773

Description

@StasEvseev

Tracer Version(s)

4.10.2

Python Version(s)

3.14.2

Pip Version(s)

24.x

Bug Report

Under gunicorn gevent workers + ddtrace, the entire gevent hub can freeze when
a greenlet forks a subprocess. In production this shows up as intermittent,
invisible worker stalls: the parked greenlet is eventually killed on worker
recycle, surfacing only as a greenlet.GreenletExit.

The trigger on our hot path is platform.uname().processor, which lazily shells
out uname -p. stripe-python takes this exact path when it builds the
X-Stripe-Client-User-Agent header on every request, so every outbound Stripe
call is a potential fork.

With ddtrace==4.6.3 it is not reproducible in production.

Reproduction Code

No response

Error Logs

... TRUNCATED STACKTRACE

File "/usr/src/app/cardapp/adapter/stripe/__init__.py", line 780, in get_payment_method
    return stripe.PaymentMethod.retrieve(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        payment_method_id,
        ^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        stripe_version=self.api_version,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/venv/lib/python3.14/site-packages/stripe/_payment_method.py", line 1993, in retrieve
    instance.refresh()
    ~~~~~~~~~~~~~~~~^^
  File "/venv/lib/python3.14/site-packages/stripe/_api_resource.py", line 38, in refresh
    return self._request_and_refresh("get", self.instance_url())
           ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.14/site-packages/stripe/_api_resource.py", line 128, in _request_and_refresh
    obj = StripeObject._request(
        self,
    ...<4 lines>...
        usage=usage,
    )
  File "/venv/lib/python3.14/site-packages/stripe/_stripe_object.py", line 424, in _request
    return self._requestor.request(
           ~~~~~~~~~~~~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<4 lines>...
        usage=usage,
        ^^^^^^^^^^^^
    )
    ^
  File "/venv/lib/python3.14/site-packages/stripe/_api_requestor.py", line 201, in request
    rbody, rcode, rheaders = requestor.request_raw(
                             ~~~~~~~~~~~~~~~~~~~~~^
        method.lower(),
        ^^^^^^^^^^^^^^^
    ...<6 lines>...
        usage=usage,
        ^^^^^^^^^^^^
    )
    ^
  File "/venv/lib/python3.14/site-packages/stripe/_api_requestor.py", line 730, in request_raw
    ) = self._args_for_request_with_retries(
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        method,
        ^^^^^^^
    ...<5 lines>...
        usage=usage,
        ^^^^^^^^^^^^
    )
    ^
  File "/venv/lib/python3.14/site-packages/stripe/_api_requestor.py", line 653, in _args_for_request_with_retries
    headers = self.request_headers(
        # this cast is safe because the blocks below validate that `method` is one of the allowed values
    ...<2 lines>...
        request_options,
    )
  File "/venv/lib/python3.14/site-packages/stripe/_api_requestor.py", line 521, in request_headers
    val = func()
  File "/usr/local/lib/python3.14/platform.py", line 1354, in platform
    system, node, release, version, machine, processor = uname()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/platform.py", line 959, in __iter__
    (self.processor,)
     ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/functools.py", line 1126, in __get__
    val = self.func(instance)
  File "/usr/local/lib/python3.14/platform.py", line 954, in processor
    return _unknown_as_blank(_Processor.get())
                             ~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.14/platform.py", line 886, in get
    return func() or ''
           ~~~~^^
  File "/usr/local/lib/python3.14/platform.py", line 922, in from_subprocess
    return subprocess.check_output(
           ~~~~~~~~~~~~~~~~~~~~~~~^
        ['uname', '-p'],
        ^^^^^^^^^^^^^^^^
    ...<2 lines>...
        encoding="utf8",
        ^^^^^^^^^^^^^^^^
    ).strip()
    ^
  File "/venv/lib/python3.14/site-packages/gevent/subprocess.py", line 418, in check_output
    with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
         ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.14/site-packages/gevent/subprocess.py", line 789, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        pass_fds, cwd, env, universal_newlines,
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
                        gid, gids, uid, umask,
                        ^^^^^^^^^^^^^^^^^^^^^^
                        start_new_session, process_group)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.14/site-packages/gevent/subprocess.py", line 1790, in _execute_child
    data = errpipe_read.read()
  File "/venv/lib/python3.14/site-packages/gevent/_fileobjectposix.py", line 167, in readall
    data = self.__read(DEFAULT_BUFFER_SIZE)
  File "/venv/lib/python3.14/site-packages/gevent/_fileobjectposix.py", line 161, in __read
    wait_on_watcher(self._read_watcher, None, None, self.hub)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "src/gevent/_hub_primitives.py", line 326, in gevent._gevent_c_hub_primitives.wait_on_watcher
  File "src/gevent/_hub_primitives.py", line 350, in gevent._gevent_c_hub_primitives.wait_on_watcher
  File "src/gevent/_hub_primitives.py", line 304, in gevent._gevent_c_hub_primitives._primitive_wait
  File "src/gevent/_hub_primitives.py", line 55, in gevent._gevent_c_hub_primitives.WaitOperationsGreenlet.wait
  File "src/gevent/_waiter.py", line 154, in gevent._gevent_c_waiter.Waiter.get
  File "src/gevent/_greenlet_primitives.py", line 65, in gevent._gevent_c_greenlet_primitives.SwitchOutGreenletWithLoop.switch
  File "src/gevent/_gevent_c_greenlet_primitives.pxd", line 35, in gevent._gevent_c_greenlet_primitives._greenlet_switch
greenlet.GreenletExit

Libraries in Use

ddtrace==4.10.2
gevent==26.5.0
gunicorn (gevent worker class)
stripe (triggers platform.uname().processor on every request)

Operating System

Linux x86_64

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions