/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <atomic> #include <cassert> #include <cstdint> #include <folly/CPortability.h> #include <folly/CppAttributes.h> #include <folly/Function.h> #include <folly/Portability.h> #include <folly/experimental/coro/Coroutine.h> namespace folly { // Gets the instruction pointer of the return-address of the current function. // // Generally a function that uses this macro should be declared FOLLY_NOINLINE // to prevent this returning surprising results in cases where the function // is inlined. #if FOLLY_HAS_BUILTIN(__builtin_return_address) #define FOLLY_ASYNC_STACK_RETURN_ADDRESS() … #else #define FOLLY_ASYNC_STACK_RETURN_ADDRESS … #endif // Gets pointer to the current function invocation's stack-frame. // // Generally a function that uses this macro should be declared FOLLY_NOINLINE // to prevent this returning surprising results in cases where the function // is inlined. #if FOLLY_HAS_BUILTIN(__builtin_frame_address) #define FOLLY_ASYNC_STACK_FRAME_POINTER() … #else #define FOLLY_ASYNC_STACK_FRAME_POINTER … #endif // This header defines data-structures used to represent an async stack-trace. // // These data-structures are intended for use by coroutines (and possibly other // representations of async operations) to allow the current program to record // an async-stack as a linked-list of async-stack-frames in a similar way to // how a normal thread represents the stack as a linked-list of stack frames. // // From a high-level, just looking at the AsyncStackRoot/AsyncStackFrame // data-structures, each thread maintains a linked-list of active AsyncStack // chains that looks a bit like this. // // Stack Register // | // V // Stack Frame currentStackRoot (TLS) // | | // V V // Stack Frame <- AsyncStackRoot -> AsyncStackFrame -> AsyncStackFrame -> ... // | | // V | // Stack Frame | // : | // V V // Stack Frame <- AsyncStackRoot -> AsyncStackFrame -> AsyncStackFrame -> ... // | | // V X // Stack Frame // : // V // // Whenever a thread enters an event loop or is about to execute some // asynchronus callback/continuation the current thread registers an // AsyncStackRoot and records the stack-frame of the normal thread // stack that corresponds to that call so that each AsyncStackRoot // can be later interleaved with a normal thread stack-trace at // the appropriate location. // // Each AsyncStackRoot contains a pointer to the currently active // AsyncStackFrame (if any). This AsyncStackFrame forms the head // of a linked-list of AsyncStackFrame objects that represent the // async stack-trace. Each non-head AsyncStackFrame is a suspended // asynchronous operation, which is typically suspended waiting for // the previous operation to complete. // // // The following diagram shows in more detail how each of the fields // in these data-structures relate to each other and how the // async-stack interleaves with the normal thread-stack. // // Current Thread Stack // ==================== // +------------------------------------+ <--- current top of stack // | Normal Stack Frame | // | - stack-base-pointer ---. | // | - return-address | | Thread Local Storage // | | | ==================== // +--------------------------V---------+ // | ... | +-------------------------+ // | : | | - currentStackRoot -. | // | : | | | | // +--------------------------V---------+ +----------------------|--+ // | Normal Stack Frame | | // | - stack-base-pointer ---. | | // | - return-address | .-------------------------------` // | | | | // +--------------------------V------|--+ // | Active Async Operation | | // | (Callback or Coroutine) | | Heap Allocated // | - stack-base-pointer ---. | | ============== // | - return-address | | | // | - pointer to async state | --------------> +-------------------------+ // | (e.g. coro frame or | | | | Coroutine Frame | // | future core) | | | | +---------------------+ | // | | | | | | Promise | | // +--------------------------V------|--+ | | +-----------------+ | | // | Event / Callback | | .------>| AsyncStackFrame | | | // | Loop Callsite | | | | | | - parentFrame --------. // | - stack-base-pointer ---. | | | | | | - instructionPtr| | | | // | - return-address | | | | | | | - stackRoot -. | | | | // | | | | | | | +--------------|--+ | | | // | +--------------------+ | | | | | | ... | | | | // | | AsyncStackRoot |<--------` | | | +----------------|----+ | | // | | - topFrame -----------------------` | ... | | | // | | - stackFramePtr -. |<---------------, +------------------|------+ | // | | - nextRoot --. | | | | | | | // | +--------------|---|-+ | | '----------------------` | // +-----------------|---V----V---------+ +-------------------------+ | // | ... | | | Coroutine Frame | | // | | : | | | | // | | : | | +-------------------+ | | // +-----------------|--------V---------+ | | AsyncStackFrame |<----` // | Async Operation | | | | - parentFrame --------. // | (Callback/Coro) | | | | - instructionPtr | | | // | | : | | | - stackRoot | | | // | | : | | +-------------------+ | | // +-----------------|--------V---------+ +-------------------------+ | // | Event Loop / | | : // | Callback Call | | : // | - frame-pointer | -------. | V // | - return-address| | | // | | | | Another chain of potentially // | +--------------V-----+ | | unrelated AsyncStackFrame // | | AsyncStackRoot | | | +---------------------+ // | | - topFrame ---------------- - - - - > | AsyncStackFrame | // | | - stackFramePtr -. | | | | - parentFrame -. | // | | - nextRoot -. | | | | +----------------|----+ // | +-------------|----|-+ | | : // | | | | | V // +----------------|----V----V---------+ // | ... : | // | V | // | | // +------------------------------------+ // // // This data-structure can be inspected from within the current process // if desired, but is also intended to allow tools such as debuggers or // profilers that are inspecting the memory of this process remotely. struct AsyncStackRoot; struct AsyncStackFrame; namespace detail { class ScopedAsyncStackRoot; } // Get access to the current thread's top-most AsyncStackRoot. // // Returns nullptr if there is no active AsyncStackRoot. FOLLY_NODISCARD AsyncStackRoot* tryGetCurrentAsyncStackRoot() noexcept; // Get access to the current thread's top-most AsyncStackRoot. // // Assumes that there is a current AsyncStackRoot. FOLLY_NODISCARD AsyncStackRoot& getCurrentAsyncStackRoot() noexcept; // Exchange the current thread's active AsyncStackRoot with the // specified AsyncStackRoot pointer, returning the old AsyncStackRoot // pointer. // // This is intended to be used to update the thread-local pointer // when context-switching fiber stacks. FOLLY_NODISCARD AsyncStackRoot* exchangeCurrentAsyncStackRoot( AsyncStackRoot* newRoot) noexcept; // Perform some consistency checks on the specified AsyncStackFrame, // assuming that it is the currently active AsyncStackFrame. void checkAsyncStackFrameIsActive(const folly::AsyncStackFrame& frame) noexcept; // Activate the specified AsyncStackFrame on the specified AsyncStackRoot, // setting it as the current 'topFrame'. // // The AsyncStackRoot must be the current thread's top-most AsyncStackRoot // and it must not currently have an active 'topFrame'. // // This is typically called immediately prior to executing a callback that // resumes the async operation represented by 'frame'. void activateAsyncStackFrame( folly::AsyncStackRoot& root, folly::AsyncStackFrame& frame) noexcept; // Deactivate the specified AsyncStackFrame, clearing the current 'topFrame'. // // Typically called when the current async operation completes or is suspended // and execution is about to return from the callback to the executor's event // loop. void deactivateAsyncStackFrame(folly::AsyncStackFrame& frame) noexcept; // Push the 'callee' frame onto the current thread's async stack, deactivating // the 'caller' frame and setting up the 'caller' to be the parent-frame of // the 'callee'. // // The 'caller' frame must be the current thread's active frame. // // After this call, the 'callee' frame will be the current thread's active // frame. // // This is typically used when one async operation is about to transfer // execution to a child async operation. e.g. via a coroutine symmetric // transfer. void pushAsyncStackFrameCallerCallee( folly::AsyncStackFrame& callerFrame, folly::AsyncStackFrame& calleeFrame) noexcept; // Pop the 'callee' frame off the stack, restoring the parent frame as the // current frame. // // This is typically used when the current async operation completes and // you are about to call/resume the caller. e.g. performing a symmetric // transfer to the calling coroutine in final_suspend(). // // If calleeFrame.getParentFrame() is null then this method is equivalent // to deactivateAsyncStackFrame(), leaving no active AsyncStackFrame on // the current AsyncStackRoot. void popAsyncStackFrameCallee(folly::AsyncStackFrame& calleeFrame) noexcept; // Get a pointer to a special frame that can be used as the root-frame // for a chain of AsyncStackFrame that does not chain onto a normal // call-stack. // // The caller should never modify this frame as it will be shared across // many frames and threads. The implication of this restriction is that // you should also never activate this frame. AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept; // Given an initial AsyncStackFrame, this will write `addresses` with // the return addresses of the frames in this async stack trace, up to // `maxAddresses` written. // This assumes `addresses` has `maxAddresses` allocated space available. // Returns the number of frames written. size_t getAsyncStackTraceFromInitialFrame( folly::AsyncStackFrame* initialFrame, std::uintptr_t* addresses, size_t maxAddresses); #if FOLLY_HAS_COROUTINES // Resume the specified coroutine after installing a new AsyncStackRoot // on the current thread and setting the specified AsyncStackFrame as // the current async frame. FOLLY_NOINLINE void resumeCoroutineWithNewAsyncStackRoot( coro::coroutine_handle<> h, AsyncStackFrame& frame) noexcept; // Resume the specified coroutine after installing a new AsyncStackRoot // on the current thread and setting the coroutine's associated // AsyncStackFrame, obtained by calling promise.getAsyncFrame(), as the // current async frame. template <typename Promise> void resumeCoroutineWithNewAsyncStackRoot( coro::coroutine_handle<Promise> h) noexcept; #endif // FOLLY_HAS_COROUTINES /** * Push a dummy "leaf" frame into the stack to annotate the stack as * "suspended". * * The leaf frame will be made discoverable to debugging tools * which may use the leaves to walk the stack traces of suspended stacks. */ void activateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept; bool isSuspendedLeafActive(AsyncStackFrame& leafFrame) noexcept; /** * Apply `fn` on all suspended leaf frames. * Note: Avoid performing async work within `fn` as it may cause deadlocks. * * This API does nothing in non-debug-builds. */ void sweepSuspendedLeafFrames(folly::FunctionRef<void(AsyncStackFrame*)> fn); /** * Pop the dummy "leaf" frame off the stack to annotate the stack as * having resumed. * * The leaf frame will no longer be discoverable to debugging tools */ void deactivateSuspendedLeaf(AsyncStackFrame& leafFrame) noexcept; // An async stack frame contains information about a particular // invocation of an asynchronous operation. // // For example, asynchronous operations implemented using coroutines // would have each coroutine-frame contain an instance of AsyncStackFrame // to record async-stack trace information for that coroutine invocation. struct AsyncStackFrame { … }; // A stack-root represents the context of an event loop // that is running some asynchronous work. The current async // operation that is being executed by the event loop (if any) // is pointed to by the 'topFrame'. // // If the current event loop is running nested inside some other // event loop context then the 'nextRoot' points to the AsyncStackRoot // context for the next event loop up the stack on the current thread. // // The 'stackFramePtr' holds a pointer to the normal stack-frame // that is currently executing this event loop. This allows // reconciliation of the parts between a normal stack-trace and // the start of the async-stack trace. // // The current thread's top-most context (the head of the linked // list of contexts) is obtained by calling getCurrentAsyncStackRoot(). struct AsyncStackRoot { … }; namespace detail { class ScopedAsyncStackRoot { … }; } // namespace detail } // namespace folly #include <folly/tracing/AsyncStack-inl.h>