Commit 4162d2f4 authored by Pranav Thulasiram Bhat's avatar Pranav Thulasiram Bhat Committed by Facebook GitHub Bot

Async annotation wrapper

Summary:
## Why is this diff needed?
This diff presents an annotation framework designed to address some of the issues we've had working with fibers in NodeAPI:
- It's difficult to identify functions doing I/O. As a result, we've had I/Os happening sequentially in for loops.
- It's difficult to tell if a function is running on fibers. This is especially hard in large code bases such as ours

## What does this diff do?
The `Async<T>` type can be used as the return type of a function. This accomplishes a couple of things:
- It indicates that this function should run on fibers and that a fiber can suspend in this function (or in one of it's children)
- It forces calling functions to `await` the result of this function. When coupled with a good lint rule, this enforces annotations right to the top of the stack.

Reviewed By: A5he

Differential Revision: D21159049

fbshipit-source-id: ee922093b140b22d8e7a7587b87aa0e783055b6c
parent f5d380b4
/*
* 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/fibers/async/Async.h>
#include <folly/fibers/FiberManagerInternal.h>
namespace folly {
namespace fibers {
namespace async {
namespace detail {
bool onFiber() {
return folly::fibers::onFiber();
}
} // namespace detail
} // namespace async
} // namespace fibers
} // 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 <glog/logging.h>
#include <utility>
namespace folly {
namespace fibers {
namespace async {
namespace detail {
/**
* Define in source to avoid including FiberManager header and keep this file
* cheap to include
*/
bool onFiber();
} // namespace detail
template <typename T>
class Async;
template <typename T>
T&& await(Async<T>&&);
/**
* Asynchronous fiber result wrapper
*
* Syntactic sugar to indicate that can be used as the return type of a
* function, indicating that a fiber can be preempted within that function.
* Wraps the eagerly executed result of the function and must be 'await'ed to
* retrieve the result.
*
* Since fibers context switches are implicit, it can be difficult to tell if a
* function does I/O. In large codebases, it can also be difficult to tell if a
* given function is running on fibers or not. The Async<> return type makes
* I/O explicit and provides a good way to identify code paths running on fiber.
*
* Async must be combined with static analysis (eg. lints) that forces a
* function that calls 'await' to also return an Async wrapped result.
*
* Runtime Consideration:
* - The wrapper is currently 0 cost (in optimized builds), and this will
* remain a guarentee
* - It provides protection (in debug builds) against running Async-annotated
* code on main context.
* - It does NOT provide protection against doing I/O in non Async-annotated
* code, both asynchronously (on fiber) or blocking (main context).
* - It does NOT provide protection from fiber's stack overflow.
*/
template <typename T>
class [[nodiscard]] Async {
public:
// General use constructor
template <typename U>
/* implicit */ Async(U && val) : val_(std::forward<U>(val)) {}
// Move constructor to allow eager-return of async without using await
template <typename U>
/* implicit */ Async(Async<U> && async) noexcept
: val_(await(std::move(async))) {}
Async(const Async&) = delete;
Async(Async && other) = default;
Async& operator=(const Async&) = delete;
Async& operator=(Async&&) = delete;
private:
T val_;
friend T&& await<T>(Async<T> &&);
};
template <>
class [[nodiscard]] Async<void> {
public:
/* implicit */ Async() {}
Async(const Async&) = delete;
Async(Async && other) = default;
Async& operator=(const Async&) = delete;
Async operator=(Async&&) = delete;
};
/**
* Function to retrieve the result from the Async wrapper
* A function calling await must return an Async wrapper itself
* for the wrapper to serve its intended purpose (the best way to enforce this
* is static analysis)
*/
template <typename T>
T&& await(Async<T>&& async) {
DCHECK(detail::onFiber());
return std::move(async.val_);
}
inline void await(Async<void>&&) {
DCHECK(detail::onFiber());
}
/**
* A utility to start annotating at top of stack (eg. the task which is added to
* fiber manager) A function must not return an Async wrapper if it uses
* `init_await` instead of `await` (again, enforce via static analysis)
*/
template <typename T>
T&& init_await(Async<T>&& async) {
return await(std::move(async));
}
inline void init_await(Async<void>&& async) {
await(std::move(async));
}
} // namespace async
} // namespace fibers
} // namespace folly
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include <folly/fibers/SimpleLoopController.h> #include <folly/fibers/SimpleLoopController.h>
#include <folly/fibers/TimedMutex.h> #include <folly/fibers/TimedMutex.h>
#include <folly/fibers/WhenN.h> #include <folly/fibers/WhenN.h>
#include <folly/fibers/async/Async.h>
#include <folly/io/async/ScopedEventBaseThread.h> #include <folly/io/async/ScopedEventBaseThread.h>
#include <folly/portability/GTest.h> #include <folly/portability/GTest.h>
...@@ -2598,3 +2599,47 @@ TEST(FiberManager, addTaskEagerKeepAlive) { ...@@ -2598,3 +2599,47 @@ TEST(FiberManager, addTaskEagerKeepAlive) {
EXPECT_TRUE(f.isReady()); EXPECT_TRUE(f.isReady());
EXPECT_EQ(42, std::move(f).get()); EXPECT_EQ(42, std::move(f).get());
} }
namespace {
std::string getString() {
return "foo";
}
async::Async<void> getAsyncNothing() {
return {};
}
async::Async<std::string> getAsyncString() {
return getString();
}
async::Async<folly::Optional<std::string>> getOptionalAsyncString() {
// use move constructor to convert Async<std::string> to
// Async<folly::Optional<std::string>>
return getAsyncString();
}
} // namespace
TEST(FiberManager, asyncAwait) {
folly::EventBase evb;
auto& fm = getFiberManager(evb);
EXPECT_NO_THROW(fm.addTaskFuture([&]() {
EXPECT_NO_THROW(async::await(getAsyncNothing()));
EXPECT_EQ(getString(), async::await(getAsyncString()));
EXPECT_EQ(
getString(), *async::await(getOptionalAsyncString()));
})
.getVia(&evb));
}
TEST(FiberManager, asyncInitAwait) {
folly::EventBase evb;
auto& fm = getFiberManager(evb);
EXPECT_NO_THROW(
fm.addTaskFuture([&]() { async::init_await(getAsyncNothing()); })
.getVia(&evb));
EXPECT_EQ(
getString(),
fm.addTaskFuture([&]() { return async::init_await(getAsyncString()); })
.getVia(&evb));
}
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