Skip to content

Commit

Permalink
Optimize construction of most {Future,Promise}Exceptions
Browse files Browse the repository at this point in the history
Summary:
Some exception such as `FutureCancellation` and `BrokenPromise` are constructed at high volume because they are used for non-exceptional paths. The base class `logic_error` always copies its argument into an internal `std::string`, and most of the error messages do not fit in inline storage.

Since the error messages are all static, we can specialize the `what()` to avoid the copy.

Reviewed By: ispeters

Differential Revision: D49638973

fbshipit-source-id: 3aa91f1655d10e5eda28c70f24745eefd92bde0b
  • Loading branch information
ot authored and facebook-github-bot committed Sep 29, 2023
1 parent 1838f1f commit 75ba8fe
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 25 deletions.
25 changes: 15 additions & 10 deletions folly/futures/Future.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@

namespace folly {

class FOLLY_EXPORT FutureException : public std::logic_error {
class FOLLY_EXPORT FutureException
: public static_what_exception<std::logic_error> {
public:
using std::logic_error::logic_error;
using static_what_exception<std::logic_error>::static_what_exception;
};

class FOLLY_EXPORT FutureInvalid : public FutureException {
public:
FutureInvalid() : FutureException("Future invalid") {}
FutureInvalid() : FutureException(static_lifetime{}, "Future invalid") {}
};

/// At most one continuation may be attached to any given Future.
Expand All @@ -62,38 +63,42 @@ class FOLLY_EXPORT FutureInvalid : public FutureException {
/// thrown instead.
class FOLLY_EXPORT FutureAlreadyContinued : public FutureException {
public:
FutureAlreadyContinued() : FutureException("Future already continued") {}
FutureAlreadyContinued()
: FutureException(static_lifetime{}, "Future already continued") {}
};

class FOLLY_EXPORT FutureNotReady : public FutureException {
public:
FutureNotReady() : FutureException("Future not ready") {}
FutureNotReady() : FutureException(static_lifetime{}, "Future not ready") {}
};

class FOLLY_EXPORT FutureCancellation : public FutureException {
public:
FutureCancellation() : FutureException("Future was cancelled") {}
FutureCancellation()
: FutureException(static_lifetime{}, "Future was cancelled") {}
};

class FOLLY_EXPORT FutureTimeout : public FutureException {
public:
FutureTimeout() : FutureException("Timed out") {}
FutureTimeout() : FutureException(static_lifetime{}, "Timed out") {}
};

class FOLLY_EXPORT FuturePredicateDoesNotObtain : public FutureException {
public:
FuturePredicateDoesNotObtain()
: FutureException("Predicate does not obtain") {}
: FutureException(static_lifetime{}, "Predicate does not obtain") {}
};

class FOLLY_EXPORT FutureNoTimekeeper : public FutureException {
public:
FutureNoTimekeeper() : FutureException("No timekeeper available") {}
FutureNoTimekeeper()
: FutureException(static_lifetime{}, "No timekeeper available") {}
};

class FOLLY_EXPORT FutureNoExecutor : public FutureException {
public:
FutureNoExecutor() : FutureException("No executor provided to via") {}
FutureNoExecutor()
: FutureException(static_lifetime{}, "No executor provided to via") {}
};

template <class T>
Expand Down
3 changes: 2 additions & 1 deletion folly/futures/FutureSplitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace folly {
class FOLLY_EXPORT FutureSplitterInvalid : public FutureException {
public:
FutureSplitterInvalid()
: FutureException("No Future in this FutureSplitter") {}
: FutureException(static_lifetime{}, "No Future in this FutureSplitter") {
}
};

/*
Expand Down
6 changes: 0 additions & 6 deletions folly/futures/Promise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@

#include <folly/futures/Promise.h>

#include <fmt/core.h>

namespace folly {

BrokenPromise::BrokenPromise(PrettyNameCtorTag, char const* const type)
: PromiseException(fmt::format("Broken promise for type name `{}`", type)) {
}

#if FOLLY_USE_EXTERN_FUTURE_UNIT
template class Promise<Unit>;
#endif
Expand Down
27 changes: 19 additions & 8 deletions folly/futures/Promise.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,46 @@

namespace folly {

class FOLLY_EXPORT PromiseException : public std::logic_error {
class FOLLY_EXPORT PromiseException
: public static_what_exception<std::logic_error> {
public:
using std::logic_error::logic_error;
using static_what_exception<std::logic_error>::static_what_exception;
};

class FOLLY_EXPORT PromiseInvalid : public PromiseException {
public:
PromiseInvalid() : PromiseException("Promise invalid") {}
PromiseInvalid() : PromiseException(static_lifetime{}, "Promise invalid") {}
};

class FOLLY_EXPORT PromiseAlreadySatisfied : public PromiseException {
public:
PromiseAlreadySatisfied() : PromiseException("Promise already satisfied") {}
PromiseAlreadySatisfied()
: PromiseException(static_lifetime{}, "Promise already satisfied") {}
};

class FOLLY_EXPORT FutureAlreadyRetrieved : public PromiseException {
public:
FutureAlreadyRetrieved() : PromiseException("Future already retrieved") {}
FutureAlreadyRetrieved()
: PromiseException(static_lifetime{}, "Future already retrieved") {}
};

class FOLLY_EXPORT BrokenPromise : public PromiseException {
private:
struct PrettyNameCtorTag {};
BrokenPromise(PrettyNameCtorTag, char const* type);
template <class T>
FOLLY_EXPORT static const char* error_message() {
static const std::string* msg = [] {
auto p = new std::string("Broken promise for type name `");
*p += pretty_name<T>();
*p += "`";
return p;
}();
return msg->c_str();
}

public:
template <typename T>
explicit BrokenPromise(tag_t<T>)
: BrokenPromise(PrettyNameCtorTag{}, pretty_name<T>()) {}
: PromiseException(static_lifetime{}, error_message<T>()) {}
};

// forward declaration
Expand Down
29 changes: 29 additions & 0 deletions folly/lang/Exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <atomic>
#include <exception>
#include <string>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -450,4 +451,32 @@ class exception_shared_string {
char const* what() const noexcept;
};

/**
* A wrapper around a given exception type T that allows to store a
* static-lifetime string as what() return value, avoiding having to copy it
* into dedicated allocated storage on construction as most standard exceptions
* do even if the message is static.
*
* The constructor from the base class can still be used, in which case what()
* is delegated to the base class as well.
*/
template <class T>
class static_what_exception : public T {
protected:
struct static_lifetime {};

public:
using T::T;

static_what_exception(static_lifetime, const char* msg)
: T(std::string{}), msg_(msg) {}

const char* what() const noexcept override {
return msg_ != nullptr ? msg_ : T::what();
}

private:
const char* msg_ = nullptr;
};

} // namespace folly

0 comments on commit 75ba8fe

Please sign in to comment.