Skip to content

Conversation

@elijahandrews
Copy link
Contributor

@elijahandrews elijahandrews commented Jul 29, 2016

Say we have a function:

def f():
    meaning_of_life = 40 + 2
    return meaning_of_life

Right now, if we want to add tracing to the whole function, we'd do something like this:

def f():
    with tracer.trace('meaning_of_life'):
        meaning_of_life = 40 + 2
        return meaning_of_life

This is annoying because we have to indent all of code in the function, which makes diffs difficult to read and obscures git history. It also means we need an extra level of indentation which we may be able to avoid. Finally, if a function is long, it may not be obvious how much of the function the context handler applies to.

It would be cool if we could instead add a span around an entire function using a decorator:

@tracer.wrap()  # span name defaults to function name
def f():
    meaning_of_life = 40 + 2
    return meaning_of_life

This PR adds this functionality.

exception_occurred = False
try:
f()
except:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think these usally are better using assert_raises or

try:
    whatever()
except Exception:
    pass
else:
    assert 0, "oops"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's way nicer. Forgot about assert_raises and that you can throw else on a try block! 💯

@clutchski
Copy link
Contributor

clutchski commented Jul 30, 2016

I omitted this for the exact reason you mentioned: you can't set tags, metadata, etc (which can be important) and I hate decorators because they make pdb difficult, are really inflexible, etc. But I suspect that eventually customer would ask for this.

If we do this, I think @tracer.trace() is better than @Span(tracer).

I guess we could always do

@tracer.trace()
def whatever();
     # do something
     span = tracer.current_span()
     span.set_tag(1, 2)

but that is a bit indirect. thoughts @elijahandrews @LotharSee?

@elijahandrews
Copy link
Contributor Author

Thanks for the feedback. I think that we should have the decorator option, but only encourage its use when you want to instrument an entire function, and you don't want to set tags or metrics.

As soon as you want to instrument a smaller part of the function, or you want to add tags or metrics, we should encourage people to use one of the other, more flexible notations.

I think what will actually end up happening is that a developer's first pass on tracing a system will be to throw @tracer.trace() on top of a bunch of functions, so they can get a rough idea of where they're spending most of their time. After that, they'll probably want to add some child spans that give more specific information about some of the slower functions. So in the end, they'll up with code that looks like this:

@tracer.trace()
def some_quick_function():
    # No need to add extra spans since we barely spend any time in here

@tracer.trace()
def some_slow_function():
     # The bulk of the work is done in here, so we need finer tracing
    with @tracer.trace() as awesome_span:
        # do something awesome
    with @tracer.trace() as different_span:
        # do something different
        different_span.set_tag(1, 2)

if sampler is not None:
self.sampler = sampler

def trace_function(self, name=None, service=None, resource=None, span_type=None):
Copy link
Contributor

@clutchski clutchski Aug 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of trace_function what about wrap?

@tracer.wrap('foo-bar')
def abcdef(self):
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thumbsup

@elijahandrews elijahandrews changed the title [WIP] Trace with function decorator Add Trace.wrap decorator Aug 3, 2016
def wrap(self, name=None, service=None, resource=None, span_type=None):
def wrap_decorator(func):
if name is None:
span_name = '{}.{}'.format(func.__module__, func.__name__)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth discussing whether we want to include the module name as well as the function name here.

Including the module name is good because we can tell exactly which function is being traced.

Including the module name is bad because we may get really long span names, and our interface doesn't display these well at this time.

I'm learning towards including the module name.


def wrap_decorator(func):
if name is None:
span_name = '{}.{}'.format(func.__module__, func.__name__)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(comment got buried so reposting):

It's worth discussing whether we want to include the module name as well as the function name here.

Including the module name is good because we can tell exactly which function is being traced.

Including the module name is bad because we may get really long span names, and our interface doesn't display these well at this time.

I'm learning towards including the module name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really want to nudge people towards short spans. datalayer.metrics.query.spidly._parse_spidly will make me slightly insane. i guess its' ok though.

@clutchski
Copy link
Contributor

ok go ahead.

@elijahandrews elijahandrews merged commit 7a462f2 into master Aug 3, 2016
@elijahandrews elijahandrews deleted the elijah/decorator branch August 3, 2016 20:43
KowalskiThomas added a commit that referenced this pull request Jan 6, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree 
#1   0x00007f9a7accc6b5 pthread_create 
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread 
#3   0x00007f9a7a639d18 PeriodicThread_start 
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork 
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod 
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall 
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.
dd-octo-sts bot pushed a commit that referenced this pull request Jan 6, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
KowalskiThomas added a commit that referenced this pull request Jan 6, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
dd-octo-sts bot pushed a commit that referenced this pull request Jan 6, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
KowalskiThomas added a commit that referenced this pull request Jan 6, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
KowalskiThomas added a commit that referenced this pull request Jan 7, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
KowalskiThomas added a commit that referenced this pull request Jan 7, 2026
…15858)

Backport 4c69fdd from #15798 to 4.1.

## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree 
#1   0x00007f9a7accc6b5 pthread_create 
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread 
#3   0x00007f9a7a639d18 PeriodicThread_start 
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork 
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod 
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall 
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

Co-authored-by: Thomas Kowalski <thomas.kowalski@datadoghq.com>
KowalskiThomas added a commit that referenced this pull request Jan 7, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree
#1   0x00007f9a7accc6b5 pthread_create
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread
#3   0x00007f9a7a639d18 PeriodicThread_start
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

(cherry picked from commit 4c69fdd)
KowalskiThomas added a commit that referenced this pull request Jan 7, 2026
…15861)

Backport 4c69fdd from #15798 to 4.0.

## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree 
#1   0x00007f9a7accc6b5 pthread_create 
#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread 
#3   0x00007f9a7a639d18 PeriodicThread_start 
#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
#6   0x00007f9a7ad17073 __fork 
#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
#32  0x00007f9a7b065c25 PyObject_VectorcallMethod 
#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
#35  0x00007f9a7b039358 PyObject_Vectorcall 
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.

Co-authored-by: Thomas Kowalski <thomas.kowalski@datadoghq.com>
kianjones9 pushed a commit to kianjones9/dd-trace-py that referenced this pull request Jan 9, 2026
## Description

https://datadoghq.atlassian.net/browse/PROF-13112

This is an attempt to address the following crash. There seems to be a
case (that I wasn't able to reproduce in a Docker image, but maybe my
"code environment" didn't match the customer's exactly) where using
`uvloop` results in a crash caused by `PeriodicThread_start` after
`uvloop` tries to restart Threads after a fork.

```
#0   0x00007f9a7acdbefa cfree 
DataDog#1   0x00007f9a7accc6b5 pthread_create 
DataDog#2   0x00007f9a7a63aaa5 std::thread::_M_start_thread 
DataDog#3   0x00007f9a7a639d18 PeriodicThread_start 
DataDog#4   0x00007f9a2e71d565 __pyx_f_6uvloop_4loop_9UVProcess__after_fork (uvloop/loop.c:120214:3)
DataDog#5   0x00007f9a2e6369a8 __pyx_f_6uvloop_4loop___get_fork_handler (uvloop/loop.c:163075:24)
DataDog#6   0x00007f9a7ad17073 __fork 
DataDog#7   0x00007f9a2e732d62 uv__spawn_and_init_child_fork (src/unix/process.c:831:10)
DataDog#8   0x00007f9a2e732d62 uv__spawn_and_init_child (src/unix/process.c:919:9)
DataDog#9   0x00007f9a2e732d62 uv_spawn (src/unix/process.c:1013:18)
DataDog#10  0x00007f9a2e71fb87 __pyx_f_6uvloop_4loop_9UVProcess__init (uvloop/loop.c:119056:19)
DataDog#11  0x00007f9a2e711bf7 __pyx_f_6uvloop_4loop_18UVProcessTransport_new (uvloop/loop.c:126866:16)
DataDog#12  0x00007f9a2e712aa7 __pyx_gb_6uvloop_4loop_4Loop_116generator16 (uvloop/loop.c:54030:28)
DataDog#13  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
DataDog#14  0x00007f9a2e699f8a __Pyx_Coroutine_AmSend (uvloop/loop.c:196492:18)
DataDog#15  0x00007f9a2e69a052 __Pyx_Coroutine_Yield_From_Coroutine (uvloop/loop.c:197380:14)
DataDog#16  0x00007f9a2e69b0e5 __Pyx_Coroutine_Yield_From (uvloop/loop.c:197408:16)
DataDog#17  0x00007f9a2e69b0e5 __pyx_gb_6uvloop_4loop_4Loop_122generator18 (uvloop/loop.c:55002:15)
DataDog#18  0x00007f9a2e631419 __Pyx_Coroutine_SendEx (uvloop/loop.c:196315:14)
DataDog#19  0x00007f9a2e69bb86 __Pyx_Generator_Next (uvloop/loop.c:196581:18)
DataDog#20  0x00007f9a2e6398eb __Pyx_PyObject_Call (uvloop/loop.c:191431:15)
DataDog#21  0x00007f9a2e6398eb __Pyx_PyObject_FastCallDict (uvloop/loop.c:191552:16)
DataDog#22  0x00007f9a2e715a69 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66873:27)
DataDog#23  0x00007f9a2e71996b __pyx_f_6uvloop_4loop_4Loop__on_idle (uvloop/loop.c:17975:25)
DataDog#24  0x00007f9a2e713e52 __pyx_f_6uvloop_4loop_6Handle__run (uvloop/loop.c:66927:24)
DataDog#25  0x00007f9a2e715c88 __pyx_f_6uvloop_4loop_cb_idle_callback (uvloop/loop.c:87335:19)
DataDog#26  0x00007f9a2e731311 uv__run_idle (unix/loop-watcher.c:68:1)
DataDog#27  0x00007f9a2e72e647 uv_run (src/unix/core.c:439:5)
DataDog#28  0x00007f9a2e64fdb5 __pyx_f_6uvloop_4loop_4Loop__Loop__run (uvloop/loop.c:18458:23)
DataDog#29  0x00007f9a2e6b7e50 __pyx_f_6uvloop_4loop_4Loop__run (uvloop/loop.c:18876:18)
DataDog#30  0x00007f9a2e6c8cf0 __pyx_pf_6uvloop_4loop_4Loop_24run_forever (uvloop/loop.c:31528:18)
DataDog#31  0x00007f9a2e6c8cf0 __pyx_pw_6uvloop_4loop_4Loop_25run_forever (uvloop/loop.c:31331:13)
DataDog#32  0x00007f9a7b065c25 PyObject_VectorcallMethod 
DataDog#33  0x00007f9a2e6ccd60 __pyx_pf_6uvloop_4loop_4Loop_44run_until_complete (uvloop/loop.c:33768:23)
DataDog#34  0x00007f9a2e6ce591 __pyx_pw_6uvloop_4loop_4Loop_45run_until_complete (uvloop/loop.c:33318:13)
DataDog#35  0x00007f9a7b039358 PyObject_Vectorcall 
```

## Fix

The `_after_fork` boolean field marks that this thread object is in a
"post-fork zombie state." When the flag is set to true, Thread methods
(e.g. `join`) become no-ops because the threads do not exist anymore so
we should not try to do something with them. By checking that same flag,
we can tell that we are trying to start a Thread that doesn't really
exist and so we shouldn't try to do it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants