/* * Copyright (c) Facebook, Inc. and its affiliates. * * This software may be used and distributed according to the terms of the * GNU General Public License version 2. */ #pragma once #include #include #include #include #include namespace facebook { namespace eden { /** * A helper class for injecting artificial faults into the normal program flow. * * This allows external test code to inject delay or failures into specific * locations in the program. */ class FaultInjector { public: explicit FaultInjector(bool enabled); ~FaultInjector(); /** * Check for an injected fault with the specified key. * * If fault injection is disabled or if no fault matching this key has been * injected this returns a SemiFuture that is immediately ready. * * If a fault matching this key has been injected this may return a future * that blocks until some later point, and/or that may fail with an * artificially injected error. * * The main reason for having keyClass and keyValue as 2 separate string * parameters is that this often allows us to avoid having to perform string * allocation when checking for faults. For many faults the keyClass will be * a fixed string literal, while the keyValue will be some runtime-specified * string. If we accepted only a single key parameter these two strings would * need to be joined at runtime when checking for faults. */ FOLLY_NODISCARD folly::SemiFuture checkAsync( folly::StringPiece keyClass, folly::StringPiece keyValue) { if (UNLIKELY(enabled_)) { return checkAsyncImpl(keyClass, keyValue); } return folly::makeSemiFuture(); } /** * Check for an injected fault with the specified key. * * This API always returns or throws an exception immediately, and therefore * does not allow faults that block or delay execution. However, because it * does not use SemiFuture it is lower-overhead for situations where you * simply want the ability to inject an exception in performance-sensitive * code. */ void check(folly::StringPiece keyClass, folly::StringPiece keyValue) { if (UNLIKELY(enabled_)) { return checkImpl(keyClass, keyValue); } } /** * Inject a fault that triggers an exception to be thrown. * * Faults are evaluated in the order in which they are inserted. If multiple * injected faults match a given check, the fault that was injected first * takes precedence. * * The count parameter specifies how many check() calls this fault should * match before expiring. If this is 0 the fault will never expire on its * own, and can only be removed by a subsequent call to removeFault(). */ void injectError( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, folly::exception_wrapper error, size_t count = 0); /** * Inject a fault that causes the check call to block until explicitly * unblocked with a later call to unblock() or unblockWithError() */ void injectBlock( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, size_t count = 0); /** * Inject a fault that causes the check call to block for a specific amount of * time before automatically continuing. */ void injectDelay( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, std::chrono::milliseconds duration, size_t count = 0); void injectDelayedError( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, std::chrono::milliseconds duration, folly::exception_wrapper error, size_t count = 0); /** * Inject a dummy fault that does not trigger any error. * * One use for this would be inserting a higher-priority no-op before some * other fault. E.g., using a no-op to cause success even if a lower-priority * fault would trigger an error. Another potential use would be a no-op * fault that expires after hit a certain number of times, allowing the first * N calls to succeed before falling through to a lower priority fault * afterwards. */ void injectNoop( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, size_t count = 0); /** * Remove a previously configured fault definition. * * The keyValueRegex string must exactly match the regular expression string * given to one of the inject*() methods when the fault was defined. * If multiple faults have been defined with the given key class and value * information only the first one will be removed. (The one defined * earliest.) * * Returns true if a fault was removed, or false if no fault was defined with * the specified key information. */ bool removeFault( folly::StringPiece keyClass, folly::StringPiece keyValueRegex); /** * Unblock pending check()/checkAsync() calls waiting on a block fault. * * The keyValueRegex string does not need to match the initial matched fault. * For example, you can define a block fault for ".*", and then later unblock * just a subset of the check calls pending on this fault. */ size_t unblock(folly::StringPiece keyClass, folly::StringPiece keyValueRegex); size_t unblockWithError( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, folly::exception_wrapper error); size_t unblockAll(); size_t unblockAllWithError(folly::exception_wrapper error); private: struct Block {}; struct Delay { explicit Delay(std::chrono::milliseconds d) : duration(d) {} Delay(std::chrono::milliseconds d, folly::exception_wrapper e) : duration(d), error{std::move(e)} {} std::chrono::milliseconds duration; std::optional error; }; using FaultBehavior = boost::variant< folly::Unit, // no fault Block, // block until explicitly unblocked at a later point Delay, // delay for a specified amount of time folly::exception_wrapper // throw an exception >; struct Fault { Fault(folly::StringPiece regex, FaultBehavior&& behavior, size_t count); // A regular expression for the key values that this fault matches boost::regex keyValueRegex; // The number of remaining times this fault may be triggered. // If this is 0 then this fault can be triggered indefinitely. size_t countRemaining{0}; FaultBehavior behavior; }; struct BlockedCheck { BlockedCheck(folly::StringPiece kv, folly::Promise&& p) : keyValue(kv.str()), promise(std::move(p)) {} std::string keyValue; folly::Promise promise; }; struct State { // A map from key class -> Faults folly::StringKeyedUnorderedMap> faults; // A map from key class -> BlockedChecks folly::StringKeyedUnorderedMap> blockedChecks; }; FOLLY_NODISCARD folly::SemiFuture checkAsyncImpl( folly::StringPiece keyClass, folly::StringPiece keyValue); void checkImpl(folly::StringPiece keyClass, folly::StringPiece keyValue); void injectFault( folly::StringPiece keyClass, folly::StringPiece keyValueRegex, FaultBehavior&& fault, size_t count); FaultBehavior findFault( folly::StringPiece keyClass, folly::StringPiece keyValue); FOLLY_NODISCARD folly::SemiFuture addBlockedFault( folly::StringPiece keyClass, folly::StringPiece keyValue); FOLLY_NODISCARD std::vector extractBlockedChecks( folly::StringPiece keyClass, folly::StringPiece keyValueRegex); size_t unblockAllImpl(std::optional error); /** * Fault injection is normally disabled during normal production use. * This simple constant flag allows us to quickly check if fault injection is * enabled in the first place, and fall through */ bool const enabled_{false}; folly::Synchronized state_; }; } // namespace eden } // namespace facebook