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

Add async stack-trace support to DetachedInlineTask

Summary:
The DetachedInlineTask coroutine type now has its own AsyncStackFrame.

While this is largely an internal implementation detail used only
for the TaskWithExecutor's `.start()` family of methods, this now
means that detached tasks launched using TaskWithExecutor's
`.start()` methods will now record the return-address of the call to
`.start()` in the stack-trace and will also have the async-stack begin
with the `detached_task` frame at the top-level.

Reviewed By: andriigrynenko

Differential Revision: D24440817

fbshipit-source-id: 861f1ca46ec4a28b5b7e388bb8b243e4d597ee0d
parent 155f5afa
...@@ -276,76 +276,108 @@ class FOLLY_NODISCARD TaskWithExecutor { ...@@ -276,76 +276,108 @@ class FOLLY_NODISCARD TaskWithExecutor {
// Start execution of this task eagerly and return a folly::SemiFuture<T> // Start execution of this task eagerly and return a folly::SemiFuture<T>
// that will complete with the result. // that will complete with the result.
auto start() && { FOLLY_NOINLINE SemiFuture<lift_unit_t<StorageType>> start() && {
Promise<lift_unit_t<StorageType>> p; Promise<lift_unit_t<StorageType>> p;
auto sf = p.getSemiFuture(); auto sf = p.getSemiFuture();
std::move(*this).start( std::move(*this).startImpl(
[promise = std::move(p)](Try<StorageType>&& result) mutable { [promise = std::move(p)](Try<StorageType>&& result) mutable {
promise.setTry(std::move(result)); promise.setTry(std::move(result));
}); },
folly::CancellationToken{},
FOLLY_ASYNC_STACK_RETURN_ADDRESS());
return sf; return sf;
} }
// Start execution of this task eagerly and call the callback when complete. // Start execution of this task eagerly and call the callback when complete.
template <typename F> template <typename F>
void start(F&& tryCallback, folly::CancellationToken cancelToken = {}) && { FOLLY_NOINLINE void start(
coro_.promise().setCancelToken(std::move(cancelToken)); F&& tryCallback,
folly::CancellationToken cancelToken = {}) && {
[](TaskWithExecutor task, std::move(*this).startImpl(
std::decay_t<F> cb) -> detail::InlineTaskDetached { static_cast<F&&>(tryCallback),
try { std::move(cancelToken),
cb(co_await folly::coro::co_awaitTry(std::move(task))); FOLLY_ASYNC_STACK_RETURN_ADDRESS());
} catch (const std::exception& e) {
cb(Try<StorageType>(exception_wrapper(std::current_exception(), e)));
} catch (...) {
cb(Try<StorageType>(exception_wrapper(std::current_exception())));
}
}(std::move(*this), std::forward<F>(tryCallback));
} }
// Start execution of this task eagerly, inline on the current thread. // Start execution of this task eagerly, inline on the current thread.
// Assumes that the current thread is already on the associated execution // Assumes that the current thread is already on the associated execution
// context. // context.
template <typename F> template <typename F>
void startInlineUnsafe( FOLLY_NOINLINE void startInlineUnsafe(
F&& tryCallback, F&& tryCallback,
folly::CancellationToken cancelToken = {}) && { folly::CancellationToken cancelToken = {}) && {
coro_.promise().setCancelToken(std::move(cancelToken)); std::move(*this).startInlineImpl(
static_cast<F&&>(tryCallback),
RequestContextScopeGuard contextScope{RequestContext::saveContext()}; std::move(cancelToken),
FOLLY_ASYNC_STACK_RETURN_ADDRESS());
[](TaskWithExecutor task,
std::decay_t<F> cb) -> detail::InlineTaskDetached {
try {
cb(co_await InlineTryAwaitable{std::exchange(task.coro_, {})});
} catch (const std::exception& e) {
cb(Try<StorageType>(exception_wrapper(std::current_exception(), e)));
} catch (...) {
cb(Try<StorageType>(exception_wrapper(std::current_exception())));
}
}(std::move(*this), std::forward<F>(tryCallback));
} }
// Start execution of this task eagerly inline on the current thread, // Start execution of this task eagerly inline on the current thread,
// assuming the current thread is already on the associated executor, // assuming the current thread is already on the associated executor,
// and return a folly::SemiFuture<T> that will complete with the result. // and return a folly::SemiFuture<T> that will complete with the result.
auto startInlineUnsafe() && { FOLLY_NOINLINE SemiFuture<lift_unit_t<StorageType>> startInlineUnsafe() && {
Promise<lift_unit_t<StorageType>> p; Promise<lift_unit_t<StorageType>> p;
auto sf = p.getSemiFuture(); auto sf = p.getSemiFuture();
std::move(*this).startInlineUnsafe( std::move(*this).startInlineImpl(
[promise = std::move(p)](Try<StorageType>&& result) mutable { [promise = std::move(p)](Try<StorageType>&& result) mutable {
promise.setTry(std::move(result)); promise.setTry(std::move(result));
}); },
folly::CancellationToken{},
FOLLY_ASYNC_STACK_RETURN_ADDRESS());
return sf; return sf;
} }
private: private:
template <typename F>
void startImpl(
F&& tryCallback,
folly::CancellationToken cancelToken,
void* returnAddress) && {
coro_.promise().setCancelToken(std::move(cancelToken));
startImpl(std::move(*this), static_cast<F&&>(tryCallback))
.start(returnAddress);
}
template <typename F>
void startInlineImpl(
F&& tryCallback,
folly::CancellationToken cancelToken,
void* returnAddress) && {
coro_.promise().setCancelToken(std::move(cancelToken));
RequestContextScopeGuard contextScope{RequestContext::saveContext()};
startInlineImpl(std::move(*this), static_cast<F&&>(tryCallback))
.start(returnAddress);
}
template <typename F>
detail::InlineTaskDetached startImpl(TaskWithExecutor task, F cb) {
try {
cb(co_await folly::coro::co_awaitTry(std::move(task)));
} catch (const std::exception& e) {
cb(Try<StorageType>(exception_wrapper(std::current_exception(), e)));
} catch (...) {
cb(Try<StorageType>(exception_wrapper(std::current_exception())));
}
}
template <typename F>
detail::InlineTaskDetached startInlineImpl(TaskWithExecutor task, F cb) {
try {
cb(co_await InlineTryAwaitable{std::exchange(task.coro_, {})});
} catch (const std::exception& e) {
cb(Try<StorageType>(exception_wrapper(std::current_exception(), e)));
} catch (...) {
cb(Try<StorageType>(exception_wrapper(std::current_exception())));
}
}
public:
class Awaiter { class Awaiter {
public: public:
explicit Awaiter(handle_t coro) noexcept : coro_(coro) {} explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
...@@ -424,7 +456,7 @@ class FOLLY_NODISCARD TaskWithExecutor { ...@@ -424,7 +456,7 @@ class FOLLY_NODISCARD TaskWithExecutor {
bool await_ready() { return false; } bool await_ready() { return false; }
template <typename Promise> template <typename Promise>
FOLLY_NOINLINE void await_suspend( FOLLY_NOINLINE std::experimental::coroutine_handle<> await_suspend(
std::experimental::coroutine_handle<Promise> continuation) { std::experimental::coroutine_handle<Promise> continuation) {
auto& promise = coro_.promise(); auto& promise = coro_.promise();
DCHECK(!promise.continuation_); DCHECK(!promise.continuation_);
...@@ -436,11 +468,13 @@ class FOLLY_NODISCARD TaskWithExecutor { ...@@ -436,11 +468,13 @@ class FOLLY_NODISCARD TaskWithExecutor {
calleeFrame.setReturnAddress(); calleeFrame.setReturnAddress();
// This awaitable is only ever awaited from a DetachedInlineTask // This awaitable is only ever awaited from a DetachedInlineTask
// which is not an async-stack-aware coroutine. // which is an async-stack-aware coroutine.
// Can't use symmetric-transfer here as we need to register a //
// new AsyncStackRoot (we can't assume there is one already) // Assume it has a .getAsyncFrame() and that this frame is currently
// and then ensure it is deregistered when the coroutine suspends. // active.
folly::resumeCoroutineWithNewAsyncStackRoot(coro_); auto& callerFrame = continuation.promise().getAsyncFrame();
folly::pushAsyncStackFrameCallerCallee(callerFrame, calleeFrame);
return coro_;
} }
folly::Try<StorageType> await_resume() { folly::Try<StorageType> await_resume() {
...@@ -450,6 +484,12 @@ class FOLLY_NODISCARD TaskWithExecutor { ...@@ -450,6 +484,12 @@ class FOLLY_NODISCARD TaskWithExecutor {
} }
private: private:
friend InlineTryAwaitable tag_invoke(
cpo_t<co_withAsyncStack>,
InlineTryAwaitable&& awaitable) noexcept {
return std::move(awaitable);
}
handle_t coro_; handle_t coro_;
}; };
...@@ -623,6 +663,8 @@ class FOLLY_NODISCARD Task { ...@@ -623,6 +663,8 @@ class FOLLY_NODISCARD Task {
} }
private: private:
// This overload needed as Awaiter is returned from co_viaIfAsync() which is
// then passed into co_withAsyncStack().
friend Awaiter tag_invoke( friend Awaiter tag_invoke(
cpo_t<co_withAsyncStack>, cpo_t<co_withAsyncStack>,
Awaiter&& awaiter) noexcept { Awaiter&& awaiter) noexcept {
......
...@@ -18,7 +18,10 @@ ...@@ -18,7 +18,10 @@
#include <folly/ScopeGuard.h> #include <folly/ScopeGuard.h>
#include <folly/Try.h> #include <folly/Try.h>
#include <folly/experimental/coro/WithAsyncStack.h>
#include <folly/experimental/coro/detail/Malloc.h> #include <folly/experimental/coro/detail/Malloc.h>
#include <folly/lang/Assume.h>
#include <folly/tracing/AsyncStack.h>
#include <cassert> #include <cassert>
#include <experimental/coroutine> #include <experimental/coroutine>
...@@ -241,6 +244,16 @@ inline InlineTask<void> InlineTaskPromise<void>::get_return_object() noexcept { ...@@ -241,6 +244,16 @@ inline InlineTask<void> InlineTaskPromise<void>::get_return_object() noexcept {
/// folly::coro::detail namespace to discourage general usage. /// folly::coro::detail namespace to discourage general usage.
struct InlineTaskDetached { struct InlineTaskDetached {
class promise_type { class promise_type {
struct FinalAwaiter {
bool await_ready() noexcept { return false; }
void await_suspend(
std::experimental::coroutine_handle<promise_type> h) noexcept {
folly::deactivateAsyncStackFrame(h.promise().getAsyncFrame());
h.destroy();
}
[[noreturn]] void await_resume() noexcept { folly::assume_unreachable(); }
};
public: public:
static void* operator new(std::size_t size) { static void* operator new(std::size_t size) {
return ::folly_coro_async_malloc(size); return ::folly_coro_async_malloc(size);
...@@ -250,16 +263,60 @@ struct InlineTaskDetached { ...@@ -250,16 +263,60 @@ struct InlineTaskDetached {
::folly_coro_async_free(ptr, size); ::folly_coro_async_free(ptr, size);
} }
InlineTaskDetached get_return_object() noexcept { return {}; } promise_type() noexcept {
asyncFrame_.setParentFrame(folly::getDetachedRootAsyncStackFrame());
}
std::experimental::suspend_never initial_suspend() noexcept { return {}; } InlineTaskDetached get_return_object() noexcept {
return InlineTaskDetached{
std::experimental::coroutine_handle<promise_type>::from_promise(
*this)};
}
std::experimental::suspend_never final_suspend() noexcept { return {}; } std::experimental::suspend_always initial_suspend() noexcept { return {}; }
FinalAwaiter final_suspend() noexcept { return {}; }
void return_void() noexcept {} void return_void() noexcept {}
[[noreturn]] void unhandled_exception() noexcept { std::terminate(); } [[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
template <typename Awaitable>
decltype(auto) await_transform(Awaitable&& awaitable) {
return folly::coro::co_withAsyncStack(
static_cast<Awaitable&&>(awaitable));
}
folly::AsyncStackFrame& getAsyncFrame() noexcept { return asyncFrame_; }
private:
folly::AsyncStackFrame asyncFrame_;
}; };
InlineTaskDetached(InlineTaskDetached&& other) noexcept
: coro_(std::exchange(other.coro_, {})) {}
~InlineTaskDetached() {
if (coro_) {
coro_.destroy();
}
}
FOLLY_NOINLINE void start() noexcept {
start(FOLLY_ASYNC_STACK_RETURN_ADDRESS());
}
void start(void* returnAddress) noexcept {
coro_.promise().getAsyncFrame().setReturnAddress(returnAddress);
folly::resumeCoroutineWithNewAsyncStackRoot(std::exchange(coro_, {}));
}
private:
explicit InlineTaskDetached(
std::experimental::coroutine_handle<promise_type> h) noexcept
: coro_(h) {}
std::experimental::coroutine_handle<promise_type> coro_;
}; };
} // namespace detail } // namespace detail
......
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