Commit b5fcc4f9 authored by Lee Howes's avatar Lee Howes Committed by Facebook Github Bot

Add deferError to SemiFuture.

Summary: deferError adds an error handling callback to SemiFuture, enqueued on the internal deferred executor, and thus will run on the chained executor or inline with .get() as for defer.

Reviewed By: yfeldblum

Differential Revision: D7243193

fbshipit-source-id: 9fe7dab9fbc8b236d717b058c81a303d87fa9253
parent 93d49bcf
......@@ -748,6 +748,80 @@ SemiFuture<T>::deferValue(F&& func) && {
});
}
template <class T>
template <class F>
typename std::enable_if<
!futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::callableWith<F, exception_wrapper&>::value &&
!futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
SemiFuture<T>::deferError(F&& func) {
using Exn =
std::remove_reference_t<typename futures::detail::Extract<F>::FirstArg>;
return std::move(*this).defer(
[func = std::forward<F>(func)](Try<T>&& t) mutable {
if (auto e = t.template tryGetExceptionObject<Exn>()) {
return makeSemiFuture<T>(makeTryWith([&]() { return func(*e); }));
} else {
return makeSemiFuture<T>(std::move(t));
}
});
}
template <class T>
template <class F>
typename std::enable_if<
!futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::callableWith<F, exception_wrapper&>::value &&
futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
SemiFuture<T>::deferError(F&& func) {
using Exn =
std::remove_reference_t<typename futures::detail::Extract<F>::FirstArg>;
return std::move(*this).defer(
[func = std::forward<F>(func)](Try<T>&& t) mutable {
if (auto e = t.template tryGetExceptionObject<Exn>()) {
return func(*e);
} else {
return makeSemiFuture<T>(std::move(t));
}
});
}
template <class T>
template <class F>
typename std::enable_if<
futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
SemiFuture<T>::deferError(F&& func) {
return std::move(*this).defer(
[func = std::forward<F>(func)](Try<T> t) mutable {
if (t.hasException()) {
return makeSemiFuture<T>(func(std::move(t.exception())));
} else {
return makeSemiFuture<T>(std::move(t));
}
});
}
template <class T>
template <class F>
typename std::enable_if<
futures::detail::callableWith<F, exception_wrapper>::value &&
futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
SemiFuture<T>::deferError(F&& func) {
return std::move(*this).defer(
[func = std::forward<F>(func)](Try<T> t) mutable {
if (t.hasException()) {
return func(std::move(t.exception()));
} else {
return makeSemiFuture<T>(std::move(t));
}
});
}
template <class T>
Future<T> Future<T>::makeEmpty() {
return Future<T>(futures::detail::EmptyConstruct{});
......
......@@ -336,7 +336,52 @@ class SemiFuture : private futures::detail::FutureBase<T> {
value_type>
deferValue(F&& func) &&;
// TODO: OnError
/// Set an error callback for this SemiFuture. The callback should take a
/// single argument of the type that you want to catch, and should return a
/// value of the same type as this SemiFuture, or a SemiFuture of that type
/// (see overload below). For instance,
///
/// makeSemiFuture()
/// .defer([] {
/// throw std::runtime_error("oh no!");
/// return 42;
/// })
/// .deferError([] (std::runtime_error& e) {
/// LOG(INFO) << "std::runtime_error: " << e.what();
/// return -1; // or makeSemiFuture<int>(-1)
/// });
template <class F>
typename std::enable_if<
!futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::callableWith<F, exception_wrapper&>::value &&
!futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
deferError(F&& func);
/// Overload of deferError where the error callback returns a Future<T>
template <class F>
typename std::enable_if<
!futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::callableWith<F, exception_wrapper&>::value &&
futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
deferError(F&& func);
/// Overload of deferError that takes exception_wrapper and returns T
template <class F>
typename std::enable_if<
futures::detail::callableWith<F, exception_wrapper>::value &&
!futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
deferError(F&& func);
/// Overload of deferError that takes exception_wrapper and returns Future<T>
template <class F>
typename std::enable_if<
futures::detail::callableWith<F, exception_wrapper>::value &&
futures::detail::Extract<F>::ReturnsFuture::value,
SemiFuture<T>>::type
deferError(F&& func);
/// Return a future that completes inline, as if the future had no executor.
/// Intended for porting legacy code without behavioural change, and for rare
......
......@@ -540,3 +540,277 @@ TEST(SemiFuture, DeferWithinContinuation) {
ASSERT_EQ(innerResult, 7);
ASSERT_EQ(result, 7);
}
TEST(SemiFuture, onError) {
bool theFlag = false;
auto flag = [&] { theFlag = true; };
#define EXPECT_FLAG() \
do { \
EXPECT_TRUE(theFlag); \
theFlag = false; \
} while (0);
#define EXPECT_NO_FLAG() \
do { \
EXPECT_FALSE(theFlag); \
theFlag = false; \
} while (0);
// By reference
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t& /* e */) { flag(); });
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t& /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// By value
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t /* e */) { flag(); });
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// Polymorphic
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](std::exception& /* e */) { flag(); });
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](std::exception& /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// Non-exceptions
{
auto f =
makeSemiFuture().defer([] { throw - 1; }).deferError([&](int /* e */) {
flag();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
{
auto f =
makeSemiFuture().defer([] { throw - 1; }).deferError([&](int /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// Mutable lambda
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t& /* e */) mutable { flag(); });
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](eggs_t& /* e */) mutable {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// Function pointer
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError(onErrorHelperEggs)
.deferError(onErrorHelperGeneric);
EXPECT_EQ(10, std::move(f).get());
}
{
auto f = makeSemiFuture()
.defer([]() -> int { throw std::runtime_error("test"); })
.deferError(onErrorHelperEggs)
.deferError(onErrorHelperGeneric);
EXPECT_EQ(20, std::move(f).get());
}
{
auto f = makeSemiFuture()
.defer([]() -> int { throw std::runtime_error("test"); })
.deferError(onErrorHelperEggs);
EXPECT_THROW(std::move(f).get(), std::runtime_error);
}
// No throw
{
auto f = makeSemiFuture()
.defer([] { return 42; })
.deferError([&](eggs_t& /* e */) {
flag();
return -1;
});
EXPECT_NO_FLAG();
EXPECT_EQ(42, std::move(f).get());
EXPECT_NO_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { return 42; })
.deferError([&](eggs_t& /* e */) {
flag();
return makeSemiFuture<int>(-1);
});
EXPECT_EQ(42, std::move(f).get());
EXPECT_NO_FLAG();
}
// Catch different exception
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](std::runtime_error& /* e */) { flag(); });
EXPECT_THROW(std::move(f).get(), eggs_t);
EXPECT_NO_FLAG();
}
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](std::runtime_error& /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_THROW(std::move(f).get(), eggs_t);
EXPECT_NO_FLAG();
}
// Returned value propagates
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](eggs_t& /* e */) { return 42; });
EXPECT_EQ(42, std::move(f).get());
}
// Returned future propagates
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError(
[&](eggs_t& /* e */) { return makeSemiFuture<int>(42); });
EXPECT_EQ(42, std::move(f).get());
}
// Throw in callback
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](eggs_t& e) -> int { throw e; });
EXPECT_THROW(std::move(f).get(), eggs_t);
}
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](eggs_t& e) -> SemiFuture<int> { throw e; });
EXPECT_THROW(std::move(f).get(), eggs_t);
}
// exception_wrapper, return Future<T>
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](exception_wrapper /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
// exception_wrapper, return Future<T> but throw
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](exception_wrapper /* e */) -> SemiFuture<int> {
flag();
throw eggs;
});
EXPECT_THROW(std::move(f).get(), eggs_t);
EXPECT_FLAG();
}
// exception_wrapper, return T
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](exception_wrapper /* e */) {
flag();
return -1;
});
EXPECT_EQ(-1, std::move(f).get());
EXPECT_FLAG();
}
// exception_wrapper, return T but throw
{
auto f = makeSemiFuture()
.defer([]() -> int { throw eggs; })
.deferError([&](exception_wrapper /* e */) -> int {
flag();
throw eggs;
});
EXPECT_THROW(std::move(f).get(), eggs_t);
EXPECT_FLAG();
}
// const exception_wrapper&
{
auto f = makeSemiFuture()
.defer([] { throw eggs; })
.deferError([&](const exception_wrapper& /* e */) {
flag();
return makeSemiFuture();
});
EXPECT_NO_THROW(std::move(f).get());
EXPECT_FLAG();
}
}
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