Commit c03ff623 authored by Lewis Baker's avatar Lewis Baker Committed by Facebook GitHub Bot

Add data-structures for representing async stack-traces

Summary:
Defines new data-structures folly::AsyncStackFrame and folly::AsyncStackRoot
that can be used to build intrusive data-structures that represent
asynchronous call-chains.

These data-structures can be walked at either runtime from within the process
or by external tooling, such as debuggers or profilers, allowing them to capture
stack-traces that correctly attribute the calls to asynchonous callbacks to the
asynchronous caller that initiaded the operation rather than to the immediate
caller of the callback (typically an executor's event-loop).

These data-structures are initially intended to be used by folly::coro coroutines
to allow chains of these coroutines to be walked, but should also general enough
to be applied to other types of asynchronous operations, such as futures or even
asynchronous operations from other languages (eg. Rust).

Reviewed By: andriigrynenko

Differential Revision: D21118885

fbshipit-source-id: 0d6130b40fd04ef330800eae4feb93bb79f48105
parent 59aad4e9
/*
* Copyright (c) Facebook, Inc. and its 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
namespace folly {
inline void checkAsyncStackFrameIsActive(
FOLLY_MAYBE_UNUSED const folly::AsyncStackFrame& frame) noexcept {
assert(frame.stackRoot != nullptr);
assert(tryGetCurrentAsyncStackRoot() == frame.stackRoot);
assert(frame.stackRoot->topFrame.load(std::memory_order_relaxed) == &frame);
}
inline void activateAsyncStackFrame(
folly::AsyncStackRoot& root,
folly::AsyncStackFrame& frame) noexcept {
assert(tryGetCurrentAsyncStackRoot() == &root);
root.setTopFrame(frame);
}
inline void deactivateAsyncStackFrame(folly::AsyncStackFrame& frame) noexcept {
checkAsyncStackFrameIsActive(frame);
frame.stackRoot->topFrame.store(nullptr, std::memory_order_relaxed);
frame.stackRoot = nullptr;
}
inline void pushAsyncStackFrameCallerCallee(
folly::AsyncStackFrame& callerFrame,
folly::AsyncStackFrame& calleeFrame) noexcept {
checkAsyncStackFrameIsActive(callerFrame);
calleeFrame.stackRoot = callerFrame.stackRoot;
calleeFrame.parentFrame = &callerFrame;
calleeFrame.stackRoot->topFrame.store(
&calleeFrame, std::memory_order_release);
// Clearing out non-top-frame's stackRoot is not strictly necessary
// but it may help with debugging.
callerFrame.stackRoot = nullptr;
}
inline void popAsyncStackFrameCallee(
folly::AsyncStackFrame& calleeFrame) noexcept {
checkAsyncStackFrameIsActive(calleeFrame);
assert(calleeFrame.parentFrame != nullptr);
auto& callerFrame = *calleeFrame.parentFrame;
auto& stackRoot = *calleeFrame.stackRoot;
callerFrame.stackRoot = &stackRoot;
stackRoot.topFrame.store(&callerFrame, std::memory_order_release);
// Clearing out non-top-frame's stackRoot is not strictly necessary
// but it may help with debugging.
calleeFrame.stackRoot = nullptr;
}
#if FOLLY_HAS_COROUTINES
template <typename Promise>
void resumeCoroutineWithNewAsyncStackRoot(
std::experimental::coroutine_handle<Promise> h) noexcept {
resumeCoroutineWithNewAsyncStackRoot(h, h.promise().getAsyncFrame());
}
#endif
inline AsyncStackFrame* AsyncStackFrame::getParentFrame() noexcept {
return parentFrame;
}
inline const AsyncStackFrame* AsyncStackFrame::getParentFrame() const noexcept {
return parentFrame;
}
inline void AsyncStackFrame::setParentFrame(AsyncStackFrame& frame) noexcept {
parentFrame = &frame;
}
inline AsyncStackRoot* AsyncStackFrame::getStackRoot() noexcept {
return stackRoot;
}
inline void AsyncStackFrame::setReturnAddress(void* p) noexcept {
instructionPointer = p;
}
inline void* AsyncStackFrame::getReturnAddress() const noexcept {
return instructionPointer;
}
inline void AsyncStackRoot::setTopFrame(AsyncStackFrame& frame) noexcept {
assert(this->topFrame.load(std::memory_order_relaxed) == nullptr);
assert(frame.stackRoot == nullptr);
frame.stackRoot = this;
this->topFrame.store(&frame, std::memory_order_release);
}
inline AsyncStackFrame* AsyncStackRoot::getTopFrame() noexcept {
return topFrame.load(std::memory_order_relaxed);
}
inline void AsyncStackRoot::setStackFrameContext(
void* framePtr,
void* ip) noexcept {
stackFramePtr = framePtr;
returnAddress = ip;
}
inline void* AsyncStackRoot::getStackFramePointer() const noexcept {
return stackFramePtr;
}
inline void* AsyncStackRoot::getReturnAddress() const noexcept {
return returnAddress;
}
inline const AsyncStackRoot* AsyncStackRoot::getNextRoot() const noexcept {
return nextRoot;
}
inline void AsyncStackRoot::setNextRoot(AsyncStackRoot* next) noexcept {
nextRoot = next;
}
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its 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.
*/
#include <folly/tracing/AsyncStack.h>
#include <folly/Likely.h>
#include <glog/logging.h>
#include <atomic>
#include <cassert>
#include <mutex>
#if defined(__linux__)
#define FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD 1
#else
#define FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD 0
#endif
#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
#include <folly/portability/PThread.h>
// Use a global TLS key variable to make it easier for profilers/debuggers
// to lookup the current thread's AsyncStackRoot by walking the pthread
// TLS structures.
extern "C" {
// Current pthread implementation has valid keys in range 0 .. 1023.
// Initialise to some value that will be interpreted as an invalid key.
pthread_key_t folly_async_stack_root_tls_key = 0xFFFF'FFFFu;
}
#endif // FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
namespace folly {
namespace {
#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
static pthread_once_t initialiseTlsKeyFlag = PTHREAD_ONCE_INIT;
static void ensureAsyncRootTlsKeyIsInitialised() noexcept {
(void)pthread_once(&initialiseTlsKeyFlag, []() noexcept {
int result = pthread_key_create(&folly_async_stack_root_tls_key, nullptr);
if (UNLIKELY(result != 0)) {
LOG(FATAL)
<< "Failed to initialise folly_async_stack_root_tls_key: (error:"
<< result << ")";
std::terminate();
}
VLOG(2) << "Initialising folly_async_stack_root_tls_key at address "
<< (void*)(&folly_async_stack_root_tls_key)
<< " with pthread_key_t " << folly_async_stack_root_tls_key;
});
}
#endif
struct AsyncStackRootHolder {
#if FOLLY_ASYNC_STACK_ROOT_USE_PTHREAD
AsyncStackRootHolder() noexcept {
ensureAsyncRootTlsKeyIsInitialised();
const int result =
pthread_setspecific(folly_async_stack_root_tls_key, this);
if (FOLLY_UNLIKELY(result != 0)) {
LOG(FATAL) << "Failed to set current thread's AsyncStackRoot";
std::terminate();
}
}
#endif
AsyncStackRoot* get() const noexcept {
return value.load(std::memory_order_relaxed);
}
void set(AsyncStackRoot* root) noexcept {
value.store(root, std::memory_order_release);
}
void set_relaxed(AsyncStackRoot* root) noexcept {
value.store(root, std::memory_order_relaxed);
}
std::atomic<AsyncStackRoot*> value{nullptr};
};
static thread_local AsyncStackRootHolder currentThreadAsyncStackRoot;
} // namespace
AsyncStackRoot* tryGetCurrentAsyncStackRoot() noexcept {
return currentThreadAsyncStackRoot.get();
}
AsyncStackRoot* exchangeCurrentAsyncStackRoot(
AsyncStackRoot* newRoot) noexcept {
auto* oldStackRoot = currentThreadAsyncStackRoot.get();
currentThreadAsyncStackRoot.set(newRoot);
return oldStackRoot;
}
namespace detail {
ScopedAsyncStackRoot::ScopedAsyncStackRoot(
void* framePointer,
void* returnAddress) noexcept {
root_.setStackFrameContext(framePointer, returnAddress);
root_.nextRoot = currentThreadAsyncStackRoot.get();
currentThreadAsyncStackRoot.set(&root_);
}
ScopedAsyncStackRoot::~ScopedAsyncStackRoot() {
assert(currentThreadAsyncStackRoot.get() == &root_);
assert(root_.topFrame.load(std::memory_order_relaxed) == nullptr);
currentThreadAsyncStackRoot.set_relaxed(root_.nextRoot);
}
} // namespace detail
} // namespace folly
namespace folly {
[[noreturn]] static void detached_task() {
LOG(FATAL) << "The detached_task() dummy function should never be called";
}
AsyncStackRoot& getCurrentAsyncStackRoot() noexcept {
auto* root = tryGetCurrentAsyncStackRoot();
assert(root != nullptr);
return *root;
}
static AsyncStackFrame makeDetachedRootFrame() noexcept {
AsyncStackFrame frame;
frame.setReturnAddress(reinterpret_cast<char*>(&detached_task) + 2);
return frame;
}
static AsyncStackFrame detachedRootFrame = makeDetachedRootFrame();
AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept {
return detachedRootFrame;
}
#if FOLLY_HAS_COROUTINES
FOLLY_NOINLINE void resumeCoroutineWithNewAsyncStackRoot(
std::experimental::coroutine_handle<> h,
folly::AsyncStackFrame& frame) noexcept {
detail::ScopedAsyncStackRoot root;
root.activateFrame(frame);
h.resume();
}
#endif // FOLLY_HAS_COROUTINES
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its 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 <folly/CPortability.h>
#include <folly/CppAttributes.h>
#include <folly/Portability.h>
#include <atomic>
#include <cassert>
#if FOLLY_HAS_COROUTINES
#include <experimental/coroutine>
#endif
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() __builtin_return_address(0)
#else
#define FOLLY_ASYNC_STACK_RETURN_ADDRESS() static_cast<void*>(nullptr)
#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() __builtin_frame_address(0)
#else
#define FOLLY_ASYNC_STACK_FRAME_POINTER() static_cast<void*>(nullptr)
#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().
//
// Assumes that there is actually a parent frame. If there is no parent
// frame then use deactivateAsyncStackFrame() instead.
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;
#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(
std::experimental::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(
std::experimental::coroutine_handle<Promise> h) noexcept;
#endif // FOLLY_HAS_COROUTINES
// 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 {
public:
AsyncStackFrame() = default;
// The parent frame is the frame of the async operation that is logically
// the caller of this frame.
AsyncStackFrame* getParentFrame() noexcept;
const AsyncStackFrame* getParentFrame() const noexcept;
void setParentFrame(AsyncStackFrame& frame) noexcept;
// Get access to the current stack-root.
//
// This is only valid for either the root or leaf AsyncStackFrame
// in a chain of frames.
//
// In the case of an active leaf-frame it is used as a cache to
// avoid accessing the thread-local when pushing/popping frames.
// In the case of the root frame (which has a null parent frame)
// it points to an AsyncStackRoot that contains information about
// the normal-stack caller.
AsyncStackRoot* getStackRoot() noexcept;
// The return address is generallty the address of the code in the
// caller that will be executed when the operation owning the current
// frame completes.
void setReturnAddress(void* p = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
void* getReturnAddress() const noexcept;
private:
friend AsyncStackRoot;
friend AsyncStackFrame& getDetachedRootAsyncStackFrame() noexcept;
friend void activateAsyncStackFrame(
folly::AsyncStackRoot&,
folly::AsyncStackFrame&) noexcept;
friend void deactivateAsyncStackFrame(folly::AsyncStackFrame&) noexcept;
friend void pushAsyncStackFrameCallerCallee(
folly::AsyncStackFrame&,
folly::AsyncStackFrame&) noexcept;
friend void checkAsyncStackFrameIsActive(
const folly::AsyncStackFrame&) noexcept;
friend void popAsyncStackFrameCallee(folly::AsyncStackFrame&) noexcept;
// Pointer to the async caller's stack-frame info.
//
// This forms a linked-list of frames that make up a stack.
// The list is terminated by a null pointer which indicates
// the top of the async stack - either because the operation
// is detached or because the next frame is a thread that is
// blocked waiting for the async stack to complete.
AsyncStackFrame* parentFrame = nullptr;
// Instruction pointer of the caller of this frame.
// This will typically be either the address of the continuation
// of this asynchronous operation, or the address of the code
// that launched this asynchronous operation. May be null
// if the address is not known.
//
// Typically initialised with the result of a call to
// FOLLY_ASYNC_STACK_RETURN_ADDRESS().
void* instructionPointer = nullptr;
// Pointer to the stack-root for the current thread.
// Cache this in each async-stack frame so we don't have to
// read from a thread-local to get the pointer.
//
// This pointer is only valid for the top-most stack frame.
// When a frame is pushed or popped it should be copied to
// the next frame, etc.
//
// The exception is for the bottom-most frame (ie. where
// parentFrame == null). In this case, if stackRoot is non-null
// then it points to a root that is currently blocked on some
// thread waiting for the async work to complete. In this case
// you can find the information about the stack-frame for that
// thread in the AsyncStackRoot and can use it to continue
// walking the stack-frames.
AsyncStackRoot* stackRoot = nullptr;
};
// 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 {
public:
// Sets the top-frame to be 'frame' and also updates the cached
// 'frame.stackRoot' to be 'this'.
//
// The current stack root must not currently have any active
// frame.
void setTopFrame(AsyncStackFrame& frame) noexcept;
AsyncStackFrame* getTopFrame() noexcept;
// Initialises this stack root with information about the context
// in which the stack-root was declared. This records information
// about where the async-stack-trace should be spliced into the
// normal stack-trace.
void setStackFrameContext(
void* framePtr = FOLLY_ASYNC_STACK_FRAME_POINTER(),
void* ip = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
void* getStackFramePointer() const noexcept;
void* getReturnAddress() const noexcept;
const AsyncStackRoot* getNextRoot() const noexcept;
void setNextRoot(AsyncStackRoot* next) noexcept;
private:
friend class detail::ScopedAsyncStackRoot;
friend void activateAsyncStackFrame(
folly::AsyncStackRoot&,
folly::AsyncStackFrame&) noexcept;
friend void deactivateAsyncStackFrame(folly::AsyncStackFrame&) noexcept;
friend void pushAsyncStackFrameCallerCallee(
folly::AsyncStackFrame&,
folly::AsyncStackFrame&) noexcept;
friend void checkAsyncStackFrameIsActive(
const folly::AsyncStackFrame&) noexcept;
friend void popAsyncStackFrameCallee(folly::AsyncStackFrame&) noexcept;
// Pointer to the currently-active AsyncStackFrame for this event
// loop or callback invocation. May be null if this event loop is
// not currently executing any async operations.
//
// This is atomic primarily to enforce visibility of writes to the
// AsyncStackFrame that occur before the topFrame in other processes,
// such as profilers/debuggers that may be running concurrently
// with the current thread.
std::atomic<AsyncStackFrame*> topFrame{nullptr};
// Pointer to the next event loop context lower on the current
// thread's stack.
// This is nullptr if this is not a nested call to an event loop.
AsyncStackRoot* nextRoot = nullptr;
// Pointer to the stack-frame and return-address of the function
// call that registered this AsyncStackRoot on the current thread.
// This is generally the stack-frame responsible for executing async
// callbacks (typically an event-loop).
// Anything prior to this frame on the stack in the current thread
// is potentially unrelated to the call-chain of the current async-stack.
//
// Typically initialised with FOLLY_ASYNC_STACK_FRAME_POINTER() or
// setStackFrameContext().
void* stackFramePtr = nullptr;
// Typically initialise with FOLLY_ASYNC_STACK_RETURN_ADDRESS() or
// setStackFrameContext().
void* returnAddress = nullptr;
};
namespace detail {
class ScopedAsyncStackRoot {
public:
explicit ScopedAsyncStackRoot(
void* framePointer = FOLLY_ASYNC_STACK_FRAME_POINTER(),
void* returnAddress = FOLLY_ASYNC_STACK_RETURN_ADDRESS()) noexcept;
~ScopedAsyncStackRoot();
void activateFrame(AsyncStackFrame& frame) noexcept {
folly::activateAsyncStackFrame(root_, frame);
}
private:
AsyncStackRoot root_;
};
} // namespace detail
} // namespace folly
#include <folly/tracing/AsyncStack-inl.h>
/*
* Copyright (c) Facebook, Inc. and its 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.
*/
#include <folly/tracing/AsyncStack.h>
#include <folly/portability/GTest.h>
#include <glog/logging.h>
TEST(AsyncStack, ScopedAsyncStackRoot) {
void* const stackFramePtr = FOLLY_ASYNC_STACK_FRAME_POINTER();
void* const returnAddress = FOLLY_ASYNC_STACK_RETURN_ADDRESS();
CHECK(folly::tryGetCurrentAsyncStackRoot() == nullptr);
{
folly::detail::ScopedAsyncStackRoot scopedRoot{stackFramePtr,
returnAddress};
auto* root = folly::tryGetCurrentAsyncStackRoot();
CHECK_NOTNULL(root);
folly::AsyncStackFrame frame;
scopedRoot.activateFrame(frame);
CHECK_EQ(root, frame.getStackRoot());
CHECK_EQ(stackFramePtr, root->getStackFramePointer());
CHECK_EQ(returnAddress, root->getReturnAddress());
CHECK_EQ(&frame, root->getTopFrame());
folly::deactivateAsyncStackFrame(frame);
CHECK(frame.getStackRoot() == nullptr);
CHECK(root->getTopFrame() == nullptr);
}
CHECK(folly::tryGetCurrentAsyncStackRoot() == nullptr);
}
TEST(AsyncStack, PushPop) {
folly::detail::ScopedAsyncStackRoot scopedRoot{nullptr};
auto& root = folly::getCurrentAsyncStackRoot();
folly::AsyncStackFrame frame1;
folly::AsyncStackFrame frame2;
folly::AsyncStackFrame frame3;
scopedRoot.activateFrame(frame1);
CHECK_EQ(&frame1, root.getTopFrame());
CHECK_EQ(&root, frame1.getStackRoot());
folly::pushAsyncStackFrameCallerCallee(frame1, frame2);
CHECK_EQ(&frame2, root.getTopFrame());
CHECK_EQ(&frame1, frame2.getParentFrame());
CHECK_EQ(&root, frame2.getStackRoot());
CHECK(frame1.getStackRoot() == nullptr);
folly::pushAsyncStackFrameCallerCallee(frame2, frame3);
CHECK_EQ(&frame3, root.getTopFrame());
CHECK_EQ(&frame2, frame3.getParentFrame());
CHECK_EQ(&frame1, frame2.getParentFrame());
CHECK(frame1.getParentFrame() == nullptr);
CHECK(frame2.getStackRoot() == nullptr);
folly::deactivateAsyncStackFrame(frame3);
CHECK(root.getTopFrame() == nullptr);
CHECK(frame3.getStackRoot() == nullptr);
folly::activateAsyncStackFrame(root, frame3);
CHECK_EQ(&frame3, root.getTopFrame());
CHECK_EQ(&root, frame3.getStackRoot());
folly::popAsyncStackFrameCallee(frame3);
CHECK_EQ(&frame2, root.getTopFrame());
CHECK_EQ(&root, frame2.getStackRoot());
CHECK(frame3.getStackRoot() == nullptr);
folly::popAsyncStackFrameCallee(frame2);
CHECK_EQ(&frame1, root.getTopFrame());
CHECK_EQ(&root, frame1.getStackRoot());
CHECK(frame2.getStackRoot() == nullptr);
folly::deactivateAsyncStackFrame(frame1);
CHECK(root.getTopFrame() == nullptr);
CHECK(frame1.getStackRoot() == nullptr);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment