From fdf0474bfe1d5fa6f327705288682070c9fcc92b Mon Sep 17 00:00:00 2001 From: Andrii Grynenko Date: Wed, 18 May 2016 17:11:24 -0700 Subject: [PATCH] Export documentation from dex to folly Reviewed By: yfeldblum Differential Revision: D3317666 fbshipit-source-id: f5558b117c8da74789ee444cdaa00231e75dc31e --- folly/experimental/fibers/README.md | 552 ++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 folly/experimental/fibers/README.md diff --git a/folly/experimental/fibers/README.md b/folly/experimental/fibers/README.md new file mode 100644 index 00000000..a691d56b --- /dev/null +++ b/folly/experimental/fibers/README.md @@ -0,0 +1,552 @@ + +

folly::fibers

folly::fibers is an async C++ framework, which uses fibers for parallelism.

Overview #

+ +

Fibers (or coroutines) are lightweight application threads. Multiple fibers can be running on top of a single system thread. Unlike system threads, all the context switching between fibers is happening explicitly. Because of this every such context switch is very fast (~200 million of fiber context switches can be made per second on a single CPU core).

+ +

folly::fibers implements a task manager (FiberManager), which executes scheduled tasks on fibers. It also provides some fiber-compatible synchronization primitives.

+ +

Basic example #

+ +
...
+folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+folly::fibers::Baton baton;
+
+fiberManager.addTask([&]() {
+  std::cout << "Task 1: start" << std::endl;
+  baton.wait();
+  std::cout << "Task 1: after baton.wait()" << std::endl; 
+});
+
+fiberManager.addTask([&]() {
+  std::cout << "Task 2: start" << std::endl;
+  baton.post();
+  std::cout << "Task 2: after baton.post()" << std::endl; 
+});
+
+evb.loop();
+...
+ +

This would print:

+ +
Task 1: start
+Task 2: start
+Task 2: after baton.post()
+Task 1: after baton.wait()
+ +

It's very important to note that both tasks in this example were executed on the same system thread. Task 1 was suspended by baton.wait() call. Task 2 then started and called baton.post(), resuming Task 1.

+ +

Features #

+ +
    +
  • Fibers creation and scheduling is performed by FiberManager
  • +
  • Integration with any event-management system (e.g. EventBase)
  • +
  • Low-level synchronization primitives (Baton) as well as higher-level primitives built on top of them (await, collectN, mutexes, ... )
  • +
  • Synchronization primitives have timeout support
  • +
  • Built-in mechanisms for fiber stack-overflow detection
  • +
  • Optional fiber-local data (i.e. equivalent of thread locals)
  • +
+ +

Non-features #

+ +
    +
  • Individual fibers scheduling can't be directly controlled by application
  • +
  • FiberManager is not thread-safe (we recommend to keep one FiberManager per thread). Application is responsible for managing it's own threads and distributing load between them
  • +
  • We don't support automatic stack size adjustments. Each fiber has a stack of fixed size.
  • +
+ +

Why would I not want to use fibers ? #

+ +

The only real downside to using fibers is the need to keep a pre-allocated stack for every fiber being run. That either makes you application use a lot of memory (if you have many concurrent tasks and each of them uses large stacks) or creates a risk of stack overflow bugs (if you try to reduce the stack size).

+ +

We believe these problems can be addressed (and we provide some tooling for that), as fibers library is used in many critical applications at Facebook (mcrouter, TAO, Service Router). However, it's important to be aware of the risks and be ready to deal with stack issues if you decide to use fibers library in your application.

+ +

What are the alternatives ? #

+ +
    +
  • Futures library works great for asynchronous high-level application code. Yet code written using fibers library is generally much simpler and much more efficient (you are not paying the penalty of heap allocation).
  • +
  • You can always keep writing your asynchronous code using traditional callback approach. Such code quickly becomes hard to manage and is error-prone. Even though callback approach seems to allow you writing the most efficient code, inefficiency still comes from heap allocations (std::functions used for callbacks, context objects to be passed between callbacks etc.)
  • +

Quick guide

Let's take a look at this basic example:

+ +
...
+folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+folly::fibers::Baton baton;
+
+fiberManager.addTask([&]() {
+  std::cout << "Task: start" << std::endl;
+  baton.wait();
+  std::cout << "Task: after baton.wait()" << std::endl; 
+});
+
+evb.loop();
+
+baton.post();
+std::cout << "Baton posted" << std::endl;
+
+evb.loop();
+
+...
+ +

This would print:

+ +
Task: start
+Baton posted
+Task: after baton.wait()
+ +

What makes fiber-task different from any other task run on e.g. an folly::EventBase is the ability to suspend such task, without blocking the system thread. So how do you suspend a fiber-task ?

+ +

fibers::Baton #

+ +

fibers::Baton is the core synchronization primitive which is used to suspend a fiber-task and notify when the task may be resumed. fibers::Baton supports two basic operations: wait() and post(). Calling wait() on a Baton will suspend current fiber-task until post() is called on the same Baton.

+ +

Please refer to Baton for more detailed documentation.

+ +
NOTE: fibers::Baton is the only native synchronization primitive of folly::fibers library. All other synchronization primitives provided by folly::fibers are built on top of fibers::Baton.
+ +

Integrating with other asynchronous APIs (callbacks) #

+ +

Let's say we have some existing library which provides a classic callback-style asynchronous API.

+ +
void asyncCall(Request request, folly::Function<void(Response)> cb);
+ +

If we use folly::fibers we can just make an async call from a fiber-task and wait until callback is run:

+ +
fiberManager.addTask([]() {
+  ...
+  Response response;
+  fibers::Baton baton;
+  
+  asyncCall(request, [&](Response r) mutable {
+     response = std::move(r);
+     baton.post();
+  });
+  baton.wait();
+
+  // Now response holds response returned by the async call
+  ...
+}
+ +

Using fibers::Baton directly is generally error-prone. To make the task above simpler, folly::fibers provide fibers::await function.

+ +

With fibers::await, the code above transforms into:

+ +
fiberManager.addTask([]() {
+  ...
+  auto response = fibers::await([&](fibers::Promise<Response> promise) {
+    asyncCall(request, [promise = std::move(promise)](Response r) mutable {
+      promise.setValue(std::move(r));
+    });
+  });
+
+  // Now response holds response returned by the async call
+  ...
+}
+ +

Callback passed to fibers::await is executed immediately and then fiber-task is suspended until fibers::Promise is fulfilled. When fibers::Promise is fulfilled with a value or exception, fiber-task will be resumed and 'fibers::await' returns that value (or throws an exception, if exception was used to fulfill the Promise).

+ +
fiberManager.addTask([]() {
+  ...
+  try {
+    auto response = fibers::await([&](fibers::Promise<Response> promise) {
+      asyncCall(request, [promise = std::move(promise)](Response r) mutable {
+        promise.setException(std::runtime_error("Await will re-throw me"));
+      });
+    });
+    assert(false); // We should never get here
+  } catch (const std::exception& e) {
+    assert(e.what() == "Await will re-throw me");
+  }
+  ...
+}
+ +

If fibers::Promise is not fulfilled, fibers::await will throw a std::logic_error.

+ +
fiberManager.addTask([]() {
+  ...
+  try {
+    auto response = fibers::await([&](fibers::Promise<Response> promise) {
+      // We forget about the promise
+    });
+    assert(false); // We should never get here
+  } catch (const std::logic_error& e) {
+    ...
+  }
+  ...
+}
+ +

Please refer to await for more detailed documentation.

+ +
NOTE: most of your code written with folly::fibers, won't be using fibers::Baton or fibers::await. These primitives should only be used to integrate with other asynchronous API which are not fibers-compatible.
+ +

Integrating with other asynchronous APIs (folly::Future) #

+ +

Let's say we have some existing library which provides a Future-based asynchronous API.

+ +
folly::Future<Response> asyncCallFuture(Request request);
+ +

The good news are, folly::Future is already fibers-compatible. You can simply write:

+ +
fiberManager.addTask([]() {
+  ...
+  auto response = asyncCallFuture(request).get();
+  
+  // Now response holds response returned by the async call
+  ...
+}
+ +

Calling get() on a folly::Future object will only suspend the calling fiber-task. It won't block the system thread, letting it process other tasks.

+ +

Writing code with folly::fibers #

+ +

Building fibers-compatible API #

+ +

Following the explanations above we may wrap an existing asynchronous API in a function:

+ +
Response fiberCall(Request request) {
+  return fibers::await([&](fibers::Promise<Response> promise) {
+    asyncCall(request, [promise = std::move(promise)](Response r) mutable {
+      promise.setValue(std::move(r));
+    });
+  });
+}
+ +

We can then call it from a fiber-task:

+ +
fiberManager.addTask([]() {
+  ...
+  auto response = fiberCall(request);
+  ...
+});
+ +

But what happens if we just call fiberCall not from within a fiber-task, but directly from a system thread ? Here another important feature of fibers::Baton (and thus all other folly::fibers synchronization primitives built on top of it) comes into play. Calling wait() on a fibers::Baton within a system thread context just blocks the thread until post() is called on the same folly::Baton.

+ +

What this means is that you no longer need to write separate code for synchronous and asynchronous APIs. If you use only folly::fibers synchronization primitives for all blocking calls inside of your synchronous function, it automatically becomes asynchronous when run inside a fiber-task.

+ +

Passing by reference #

+ +

Classic asynchronous APIs (same applies to folly::Future-based APIs) generally rely on copying/moving-in input arguments and often require you to copy/move in some context variables into the callback. E.g.:

+ +
...
+Context context;
+ 
+asyncCall(request, [request, context](Response response) mutable {
+  doSomething(request, response, context);
+});
+...
+ +

Fibers-compatible APIs look more like synchronous APIs, so you can actually pass input arguments by reference and you don't have to think about passing context at all. E.g.

+ +
fiberManager.addTask([]() {
+  ...
+  Context context;
+
+  auto response = fiberCall(request);
+ 
+  doSomething(request, response, context);
+  ...
+});
+ +

Same logic applies to fibers::await. Since fibers::await call blocks until promise is fulfilled, it's safe to pass everything by reference.

+ +

Limited stack space #

+ +

So should you just run all the code inside a fiber-task ? No exactly.

+ +

Similarly to system threads, every fiber-task has some stack space assigned to it. Stack usage goes up with the number of nested function calls and objects allocated on the stack. folly::fibers implementation only supports fiber-tasks with fixed stack size. If you want to have many fiber-tasks running concurrently - you need to reduce the amount of stack assigned to each fiber-task, otherwise you may run out of memory.

+ +
IMPORTANT: If a fiber-task runs out of stack space (e.g. calls a function which does a lot of stack allocations) you program will fail.
+ +

However if you know that some function never suspends a fiber-task, you can use fibers::runInMainContext to safely call it from a fiber-task, without any risk of running out of stack space of the fiber-task.

+ +
Result useALotOfStack() {
+  char buffer[1024*1024];
+  ...
+}
+
+...
+fiberManager.addTask([]() {
+  ...
+  auto result = fibers::runInMainContext([&]() {
+    return useALotOfStack();
+  });
+  ...
+});
+...
+ +

fibers::runInMainContext will switch to the stack of the system thread (main context), run the functor passed to it and then switch back to the fiber-task stack.

+ +
IMPORTANT: Make sure you don't do any blocking calls on main context though. It will suspend the whole system thread, not just the fiber-task which was running.
+ +

Remember that it's fine to use fibers::runInMainContext in general purpose functions (those which may be called both from fiber-task and non from fiber-task). When called in non-fiber-task context fibers::runInMainContext would simply execute passed functor right away.

+ +
NOTE: Besides fibers::runInMainContext some other functions in folly::fibers are also executing some of the passed functors on the main context. E.g. functor passes to fibers::await is executed on main context, finally-functor passed to FiberManager::addTaskFinally is also executed on main context etc. Relying on this can help you avoid extra fibers::runInMainContext calls (and avoid extra context switches).
+ +

Using locks #

+ +

Consider the following example:

+ +
...
+folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+std::mutex lock;
+folly::fibers::Baton baton;
+
+fiberManager.addTask([&]() {
+  std::lock_guard<std::mutex> lg(lock);
+  baton.wait();
+});
+
+fiberManager.addTask([&]() {
+  std::lock_guard<std::mutex> lg(lock);
+});
+
+evb.loop();
+// We won't get here :(
+baton.post();
+...
+ +

First fiber-task will grab a lock and then suspend waiting on a fibers::Baton. Then second fiber-task will be run and it will try to grab a lock. Unlike system threads, fiber-task can be only suspended explicitly, so the whole system thread will be blocked waiting on the lock, and we end up with a dead-lock.

+ +

There're generally two ways we can solve this problem. Ideally we would re-design the program to never not hold any locks when fiber-task is suspended. However if we are absolutely sure we need that lock - folly::fibers library provides some fiber-task-aware lock implementations (e.g. +TimedMutex).

APIs

fibers::Baton #

+ +

All of the features of folly::fibers library are actually built on top a single synchronization primitive called Baton. fibers::Baton is a fiber-specific version of folly::Baton. It only supports two basic operations: wait() and post(). Whenever wait() is called on the Baton, the current thread or fiber-task is suspended, until post() is called on the same Baton. wait() does not suspend the thread or fiber-task if post() was already called on the Baton. Please refer to Baton for more detailed documentation.

+ +

Baton is thread-safe, so wait() and post() can be (and should be :) ) called from different threads or fiber-tasks.

+ +
NOTE: Because Baton transparently works both on threads and fiber-tasks, any synchronization primitive built using it would have the same property. This means that any library with a synchronous API, which uses only fibers::Baton for synchronization, becomes asynchronous when used in fiber context.
+ +

timed_wait() #

+ +

fibers::Baton also supports wait with timeout.

+ +
fiberManager.addTask([=]() {
+  auto baton = std::make_shared<folly::fibers::Baton>();
+  auto result = std::make_shared<Result>();
+
+  fiberManager.addTask([=]() {
+    *result = sendRequest(...);
+    baton->post();
+  });
+
+  bool success = baton.timed_wait(std::chrono::milliseconds{10});
+  if (success) {
+    // request successful
+    ...
+  } else {
+    // handle timeout
+    ...
+  }
+});
+ +
IMPORTANT: unlike wait() when using timed_wait() API it's generally not safe to pass fibers::Baton by reference. You have to make sure that task, which fulfills the Baton is either cancelled in case of timeout, or have shared ownership for the Baton.
+ +

Task creation #

+ +

addTask() / addTaskRemote() #

+ +

As you could see from previous examples, the easiest way to create a new fiber-task is to call addTask():

+ +
fiberManager.addTask([]() {
+  ...
+});
+ +

It is important to remember that addTask() is not thread-safe. I.e. it can only be safely called from the the thread, which is running the folly::FiberManager loop.

+ +

If you need to create a fiber-task from a different thread, you have to use addTaskRemote():

+ +
folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+
+std::thread t([&]() {
+  fiberManager.addTaskRemote([]() {
+    ...
+  });
+});
+
+evb.loopForever();
+ +

addTaskFinally() #

+ +

addTaskFinally() is useful when you need to run some code on the main context in the end of a fiber-task.

+ +
fiberManager.addTaskFinally(
+  [=]() {
+    ...
+    return result;
+  },
+  [=](Result&& result) {
+    callUserCallbacks(std::move(result), ...)
+  }
+);
+ +

Of course you could achieve the same by calling fibers::runInMainContext(), but addTaskFinally() reduces the number of fiber context switches:

+ +
fiberManager.addTask([=]() {
+  ...
+  folly::fibers::runInMainContext([&]() {
+    // Switched to main context
+    callUserCallbacks(std::move(result), ...)
+  }
+  // Switched back to fiber context
+
+  // On fiber context we realize there's no more work to be done.
+  // Fiber-task is complete, switching back to main context.
+});
+ +

+ +

addTaskFuture() / addTaskRemoteFuture() #

+ +

addTask() and addTaskRemote() are creating detached fiber-tasks. If you need to know when fiber-task is complete and/or have some return value for it - addTaskFuture() / addTaskRemoteFuture() can be used.

+ +
folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+
+std::thread t([&]() {
+  auto future1 = fiberManager.addTaskRemoteFuture([]() {
+    ...
+  });
+  auto future2 = fiberManager.addTaskRemoteFuture([]() {
+    ...
+  }); 
+
+  auto result1 = future1.get();
+  auto result2 = future2.get();
+  ...
+});
+
+evb.loopForever();
+ +

Other synchronization primitives #

+ +

All the listed synchronization primitives are built using fiber::Baton. Please check their source code for detailed documentation.

+ +

await

+ +

collectN

+ +

collectAny

+ +

collectN

+ +

forEach

+ +

addTasks

+ +

TimedMutex

+ +

TimedRWMutex

Fiber stacks

Similarly to system threads, every fiber-task has some stack space assigned to it. Stack usage goes up with the number of nested function calls and objects allocated on the stack. folly::fibers implementation only supports fiber-tasks with fixed stack size. If you want to have many fiber-tasks running concurrently - you need to reduce the amount of stack assigned to each fiber-task, otherwise you may run out of memory.

+ +

Selecting stack size #

+ +

Stack size used for every fiber-task is part of FiberManager configuration. But how do you pick the right stack size ?

+ +

First of all you need to figure out the maximum number of concurrent fiber-tasks your application may have. E.g. if you are writing a Thrift-service you will probably have a single fiber-task for every request in-fly (but remember that e.g. fibers::collectAll and some other synchronization primitives may create extra fiber-tasks). It's very important to get that number first, because if you will at most need 100 concurrent fiber-tasks, even 1MB stacks will result in at most 100MB used for fiber stacks. On the other hand if you need to have 100,000 concurrent fiber-tasks, even 16KB stacks will result in 1.6GB peak memory usage just for fiber stacks.

+ +

folly::fibers also supports recording stack usage (it can be enabled via recordStackEvery option of FiberManager). When enabled, the stack of each fiber-task will be filled with magic values. Later linear search can be performed to find the boundary of unused stack space.

+ +

Stack overflow detection #

+ +

By default every fiber-task stack is allocated with a special guard page next to it (this can be controlled via useGuardPages option of FiberManager). If a stack overflow happens - this guard page will be accessed, which will result in immediate segmentation fault.

+ +
IMPORTANT: disabling guard page protection may result in unnoticed stack overflows. Those will inevitably cause memory corruptions, which are usually very hard to debug.

Event Loops

folly::fibers library doesn't implement it's own event system. Instead it allows fibers::FiberManager to work with any other event system by implementing fibers::LoopController interface.

+ +

folly::EventBase integration #

+ +

The easiest way to create a fibers::FiberManager attached to a folly::EventBase is by using fibers::getFiberManager function:

+ +
folly::EventBase evb;
+auto& fiberManager = folly::fibers::getFiberManager(evb);
+
+fiberManager.addTask([]() {
+  ...
+});
+
+evb.loop();
+ +

Such fibers::FiberManager will be automatically destroyed, when folly::EventBase is destroyed.

+ +
NOTE: folly::fibers doesn't support killing fiber-tasks in-flight (for similar reasons you can't kill a thread). If fibers::FiberManager has any outstanding fiber-tasks, when folly::EventBase is being destroyed, it will keep running the event loop until all those tasks are finished.

GDB integration

folly::fibers provide some GDB extensions which can be very useful for debugging. To load them simply the following in dbg console:

+ +
source 'folly/experimental/fibers/scripts/utils.gdb'
+ +

Show all FiberManagers #

+ +

You can use print_folly_fiber_manager_map to list all fibers::FiberManagers and folly::EventBases they are attached to.

+ +
(gdb) print_folly_fiber_manager_map
+  Global FiberManager map has 2 entries.
+    (folly::EventBase*)0x7fffffffdb60 -> (folly::fibers::FiberManager*)0x7ffff5b58480
+    (folly::EventBase*)0x7fffffffd930 -> (folly::fibers::FiberManager*)0x7ffff5b58300
+ +

This will only list fibers::FiberManagers created using fibers::getFiberManager() function.

+ + + +

You can use print_folly_fiber_manager (and passing a pointer to valid fibers::FiberManager object) to print the state of given fibers::FiberManager.

+ +
(gdb) print_folly_fiber_manager &manager
+  (folly::fibers::FiberManager*)0x7fffffffdbe0
+
+  Fibers active: 3
+  Fibers allocated: 3
+  Fibers pool size: 0
+  Active fiber: (folly::fibers::Fiber*)(nil)
+  Current fiber: (folly::fibers::Fiber*)(nil)
+
+  Active fibers:
+    (folly::fibers::Fiber*)0x7ffff5b5b000   State: Awaiting
+    (folly::fibers::Fiber*)0x7ffff5b5b300   State: Awaiting
+    (folly::fibers::Fiber*)0x7ffff5b5b600   State: Awaiting
+ +

It will list all active fibers::Fiber objects, which are running fiber-tasks and their states.

+ + + +

If you have a fibers::Fiber, which is running some fiber-task, you can print its state using print_folly_fiber command.

+ +
(gdb) print_folly_fiber 0x7ffff5b5b600
+  (folly::fibers::Fiber*)0x7ffff5b5b600
+
+  State: Awaiting
+  Backtrace:
+    #0 at 0x5a22a8 in folly::fibers::FiberManager::deactivateFiber(folly::fibers::Fiber*) + 194 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out
+/gen/folly/experimental/fibers/fibers-test
+    #1 at 0x5a1606 in folly::fibers::Fiber::preempt(folly::fibers::Fiber::State)::{lambda()#1}::operator()() + 598 in section .text of /mnt/fio0/andrii/fbsou
+rce/fbcode/buck-out/gen/folly/experimental/fibers/fibers-test
+    #2 at 0x5a17f8 in folly::fibers::Fiber::preempt(folly::fibers::Fiber::State) + 176 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen/foll
+y/experimental/fibers/fibers-test
+    #3 at 0x43a76e in void folly::fibers::Baton::waitFiber<folly::fibers::FirstArgOf<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() con
+st::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}, void>::type::value_type folly::fibers::await
+<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(foll
+y::fibers::Promise<int>)#1}>(folly::fibers::Promise<int>&&)::{lambda()#1}>(folly::fibers::FiberManager&, folly::fibers::FirstArgOf<FiberManager_collectAll_Te
+st::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}
+, void>::type::value_type folly::fibers::await<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::
+{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}>(folly::fibers::Promise<int>&&)::{lambda()#1}) + 110 in section .text of /mnt/fio0/
+andrii/fbsource/fbcode/buck-out/gen/folly/experimental/fibers/fibers-test
+    #4 at 0x42fa89 in void folly::fibers::Baton::wait<folly::fibers::FirstArgOf<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{
+lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}, void>::type::value_type folly::fibers::await<Fibe
+rManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fi
+bers::Promise<int>)#1}>(folly::fibers::Promise<int>&&)::{lambda()#1}>(folly::fibers::FirstArgOf<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::opera
+tor()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}, void>::type::value_type folly::fi
+bers::await<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{
+lambda(folly::fibers::Promise<int>)#1}>(folly::fibers::Promise<int>&&)::{lambda()#1}) + 105 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen
+/folly/experimental/fibers/fibers-test
+    #5 at 0x425921 in folly::fibers::FirstArgOf<FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const:
+:{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}, void>::type::value_type folly::fibers::await<FiberManager_collectAll_Test::TestBo
+dy()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda(folly::fibers::Promise<int>)#1}>(folly::f
+ibers::Promise<int>&&) + 80 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen/folly/experimental/fibers/fibers-test
+    #6 at 0x415e9a in FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()(
+) const + 36 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen/folly/experimental/fibers/fibers-test
+    #7 at 0x42faf9 in std::_Function_handler<int (), FiberManager_collectAll_Test::TestBody()::{lambda()#1}::operator()() const::{lambda()#1}::operator()() c
+onst::{lambda()#1}>::_M_invoke(std::_Any_data const&) + 32 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen/folly/experimental/fibers/fibers
+-test
+    #8 at 0x479d5c in std::function<int ()>::operator()() const + 50 in section .text of /mnt/fio0/andrii/fbsource/fbcode/buck-out/gen/folly/experimental/fib
+ers/fibers-test
+    ...
+ +

It will print the state of the fiber-task and if fiber-task is currently awaiting - also prints its stack-trace.

+ +
NOTE: For running (i.e. not suspended) fiber-task, you can simply switch to the system thread which owns it and use regular GDB commands for debugging.
\ No newline at end of file -- 2.34.1