diff --git a/ddtrace/internal/datadog/profiling/stack/echion/echion/tasks.h b/ddtrace/internal/datadog/profiling/stack/echion/echion/tasks.h index 4c71e67f5cf..25636f7ad69 100644 --- a/ddtrace/internal/datadog/profiling/stack/echion/echion/tasks.h +++ b/ddtrace/internal/datadog/profiling/stack/echion/echion/tasks.h @@ -306,9 +306,20 @@ TaskInfo::unwind(FrameStack& stack, size_t& upper_python_stack_size) std::stack coro_frames; // Unwind the coro chain + // Detect cycles in the await chain to prevent infinite loops. + // This can happen if the Profiler samples as the Task is running, + // or due to memory corruption/race conditions when reading coroutine pointers. + std::unordered_set seen_coros; for (auto py_coro = this->coro.get(); py_coro != NULL; py_coro = py_coro->await.get()) { - if (py_coro->frame != NULL) + if (seen_coros.find(py_coro) != seen_coros.end()) { + break; + } + + seen_coros.insert(py_coro); + + if (py_coro->frame != NULL) { coro_frames.push(py_coro->frame); + } } // Total number of frames added to the Stack diff --git a/ddtrace/internal/datadog/profiling/stack/echion/echion/threads.h b/ddtrace/internal/datadog/profiling/stack/echion/echion/threads.h index 47d045d5562..8f09dd3dcc5 100644 --- a/ddtrace/internal/datadog/profiling/stack/echion/echion/threads.h +++ b/ddtrace/internal/datadog/profiling/stack/echion/echion/threads.h @@ -300,11 +300,18 @@ ThreadInfo::unwind_tasks(PyThreadState* tstate) auto stack_info = std::make_unique(leaf_task.get().name, leaf_task.get().is_on_cpu); auto& stack = stack_info->stack; + std::unordered_set seen_task_origins; for (auto current_task = leaf_task;;) { auto& task = current_task.get(); + // Check for cycle in task chain + if (seen_task_origins.find(task.origin) != seen_task_origins.end()) { + break; + } + seen_task_origins.insert(task.origin); + // The task_stack_size includes both the coroutines frames and the "upper" Python synchronous frames - size_t task_stack_size = task.unwind(stack, task.is_on_cpu ? upper_python_stack_size : unused); + auto task_stack_size = task.unwind(stack, task.is_on_cpu ? upper_python_stack_size : unused); if (task.is_on_cpu) { // Get the "bottom" part of the Python synchronous Stack, that is to say the // synchronous functions and coroutines called by the Task's outermost coroutine