-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
Copy pathAsyncStack.h
500 lines (450 loc) · 21.1 KB
/
AsyncStack.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/*
* 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/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() __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().
//
// 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 {
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;
friend void activateSuspendedLeaf(folly::AsyncStackFrame&) noexcept;
friend bool isSuspendedLeafActive(folly::AsyncStackFrame&) noexcept;
friend void deactivateSuspendedLeaf(AsyncStackFrame& leafFrame) 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() const 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>