├── .vscode └── settings.json ├── README.md ├── circuit_breaker.cc ├── circuit_breaker.h ├── demo ├── CMakeLists.txt └── demo.cc └── test ├── CMakeLists.txt └── circuit_breaker_test.cc /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "tuple": "cpp" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | C++ circuit breaker 2 | ========= 3 | 4 | 5 | Cppbreaker implements the Circuit Breaker pattern in C++. 6 | 7 | 8 | ## Usage 9 | ------------ 10 | 11 | **CircuitBreaker** 12 | 13 | The struct CircuitBreaker is a state machine to prevent sending requests that are likely to fail. 14 | ``` 15 | CircuitBreaker(const Settings& st); 16 | ``` 17 | 18 | **Settings** 19 | 20 | You can configure CircuitBreaker by the struct Settings: 21 | ``` 22 | struct Settings 23 | { 24 | std::string name; // optional 25 | uint32_t max_requests = 1; // optional 26 | std::chrono::nanoseconds interval = std::chrono::nanoseconds(0); // optional 27 | std::chrono::nanoseconds timeout = std::chrono::seconds(60); // optional 28 | 29 | std::function ready_to_trip = nullptr; // optional 30 | std::function on_state_change = nullptr; // optional 31 | }; 32 | ``` 33 | - max_requests : max_requests is the maximum number of requests allowed to pass through when the CircuitBreaker is half-open. If max_requests is 0, the CircuitBreaker allows only 1 request. 34 | - interval : timeout is the period of the open state, after which the state of the CircuitBreaker becomes half-open. If timeout is 0, the timeout value of the CircuitBreaker is set to 60 seconds. 35 | - timeout : timeout is the period of the open state, after which the state of the CircuitBreaker becomes half-open. If timeout is 0, the timeout value of the CircuitBreaker is set to 60 seconds. 36 | - ready_to_trip : ready_to_trip is called with a copy of Counts whenever a request fails in the closed state. If ready_to_trip returns true, the CircuitBreaker will be placed into the open state. If ready_to_trip is nil, default ready_to_trip is used. Default ready_to_trip returns true when the number of consecutive failures is more than 5. 37 | - on_state_change : on_state_change is called whenever the state of the CircuitBreaker changes. 38 | 39 | 40 | **Execute** 41 | 42 | ``` 43 | // std::get<0>(ret) : get expected result returned by Function_ 44 | // std::get<1>(ret) : get code returned by Function_ or circuit breaker 45 | // ResultCode::ResultCodeErrTooManyRequests(-0x80000000) and ResultCode::ResultCodeErrOpenState(-0x70000000) are reserved for circuit breaker 46 | template 47 | std::tuple Execute(Function_ req) 48 | ``` 49 | 50 | `Function_` 51 | ``` 52 | std::tuple demo() 53 | { 54 | int result_code = 0; 55 | Result_ result; 56 | // do something 57 | 58 | return std::make_tuple(result, result_code); 59 | } 60 | ``` 61 | - `Result_` : 62 | - `result_code` : On success, result_code is 0; on error, it is not 0. 63 | 64 | 65 | Example 66 | ------------ 67 | 68 | **circuit breaker settings** 69 | 70 | custom settings 71 | ``` 72 | cppbreaker::Settings st; 73 | st.name = "test_cb"; 74 | st.max_requests = 3; // the maximum number of requests when circuitbreaker is half open state 75 | st.interval = std::chrono::seconds(600); // reset the counts of requests every 600 seconds 76 | st.timeout = std::chrono::seconds(2); // the period of the open state 77 | 78 | // if the number of requests is greater 10 and 60% requests are failed, change circutbreaker to open state 79 | // default condition : consecutive_failures > 5; 80 | st.ready_to_trip = [](const cppbreaker::Counts& counts)->bool { 81 | auto num_reqs = counts.requests; 82 | auto failure_ratio = double(counts.total_failures) / double(num_reqs); 83 | return num_reqs >= 10 && failure_ratio >= 0.6; 84 | }; 85 | 86 | st.on_state_change = [](const std::string& name, cppbreaker::State from, cppbreaker::State to) { 87 | // do something 88 | }; 89 | ``` 90 | 91 | **create circuit breaker** 92 | 93 | ``` 94 | cppbreaker::CircuitBreaker cb(st); 95 | ``` 96 | 97 | **execution** 98 | 99 | ``` 100 | auto rets = cb.Execute([]()-> std::tuple { 101 | int result_code = demo_rpc_call(); 102 | return std::make_tuple(0.4, result_code); 103 | }); 104 | double result = std::get<0>(rets); // 0.4 105 | int code = std::get<1>(rets); 106 | if (code == cppbreaker::ResultCode::ResultCodeErrOpenState) 107 | { // circuit breaker is open 108 | } 109 | ``` 110 | 111 | or 112 | 113 | ``` 114 | std::tuple execution_function() 115 | { 116 | Object object; 117 | rpc::Status ret = rpc_client()->GetObject(&object); 118 | return std::make_tuple(object, ret.Ok() ? 0 : 1); 119 | } 120 | 121 | auto rets = cb.Execut(execution_function); 122 | Object object = std::get<0>(rets); 123 | int ret_code = std::get<1>(rets); 124 | ``` 125 | 126 | -------------------------------------------------------------------------------- /circuit_breaker.cc: -------------------------------------------------------------------------------- 1 | #include "circuit_breaker.h" 2 | 3 | 4 | 5 | 6 | namespace cppbreaker 7 | { 8 | 9 | CircuitBreaker::CircuitBreaker(const Settings& st) 10 | { 11 | settings_ = st; 12 | state_ = STATE_CLOSED; 13 | expiry_ = std::chrono::system_clock::from_time_t(0); 14 | 15 | if (settings_.max_requests == 0) 16 | settings_.max_requests = 1; 17 | 18 | if (settings_.timeout.count() == 0) 19 | settings_.timeout = std::chrono::seconds(60); 20 | 21 | if (settings_.ready_to_trip == nullptr) 22 | { 23 | settings_.ready_to_trip = std::bind(&CircuitBreaker::defaultReadyToTrip, this, std::placeholders::_1); 24 | } 25 | toNewGeneration(std::chrono::system_clock::now()); 26 | } 27 | 28 | State CircuitBreaker::GetState() 29 | { 30 | std::lock_guard lock(mutex_); 31 | auto now = std::chrono::system_clock::now(); 32 | State st; 33 | currentState(now, &st); 34 | return st; 35 | } 36 | 37 | std::string CircuitBreaker::StateString(State st) 38 | { 39 | if (st == STATE_CLOSED) 40 | return "close"; 41 | else if (st == STATE_HALF_OPEN) 42 | return "half open"; 43 | return "open"; 44 | } 45 | 46 | int CircuitBreaker::beforeRequest(uint64_t* gen) 47 | { 48 | std::lock_guard lock(mutex_); 49 | 50 | auto now = std::chrono::system_clock::now(); 51 | State st; 52 | *gen = currentState(now, &st); 53 | if (st == STATE_OPEN) 54 | { 55 | return ResultCodeErrOpenState; 56 | } 57 | else if (st == STATE_HALF_OPEN && 58 | counts_.requests >= settings_.max_requests) 59 | { // too many requests are in flight while state is half open 60 | return ResultCodeErrTooManyRequests; 61 | } 62 | 63 | counts_.onRequest(); 64 | return ResultCodeOK; 65 | } 66 | 67 | void CircuitBreaker::afterRequest(uint64_t before, bool success) 68 | { 69 | std::lock_guard lock(mutex_); 70 | 71 | auto now = std::chrono::system_clock::now(); 72 | State st; 73 | auto generation = currentState(now, &st); 74 | 75 | if (generation != before) 76 | return; 77 | 78 | if (success) 79 | onSuccess(st, now); 80 | else 81 | onFailure(st, now); 82 | } 83 | 84 | void CircuitBreaker::onSuccess(State st, std::chrono::system_clock::time_point now) 85 | { 86 | switch (st) 87 | { 88 | case STATE_CLOSED: 89 | counts_.onSuccess(); 90 | break; 91 | case STATE_HALF_OPEN: 92 | { 93 | counts_.onSuccess(); 94 | if (counts_.consecutive_successes >= settings_.max_requests) 95 | { 96 | setState(STATE_CLOSED, now); 97 | } 98 | break; 99 | } 100 | default: 101 | break; 102 | } 103 | } 104 | 105 | void CircuitBreaker::onFailure(State st, std::chrono::system_clock::time_point now) 106 | { 107 | switch (st) 108 | { 109 | case STATE_CLOSED: 110 | { 111 | counts_.onFailure(); 112 | if (settings_.ready_to_trip(counts_)) 113 | setState(STATE_OPEN, now); 114 | break; 115 | } 116 | case STATE_HALF_OPEN: 117 | { 118 | setState(STATE_OPEN, now); 119 | break; 120 | } 121 | default: 122 | break; 123 | } 124 | } 125 | 126 | uint64_t CircuitBreaker::currentState(std::chrono::system_clock::time_point now, State* st) 127 | { 128 | switch (state_) 129 | { 130 | case STATE_CLOSED: 131 | { 132 | if (expiry_.time_since_epoch().count() != 0 && 133 | expiry_ < now) 134 | { 135 | toNewGeneration(now); 136 | } 137 | break; 138 | } 139 | case STATE_OPEN: 140 | { 141 | if (expiry_ < now) 142 | { 143 | setState(STATE_HALF_OPEN, now); 144 | } 145 | break; 146 | } 147 | default: 148 | break; 149 | } 150 | *st = state_; 151 | return generation_; 152 | } 153 | 154 | void CircuitBreaker::setState(State st, std::chrono::system_clock::time_point now) 155 | { 156 | if (state_ == st) 157 | return; 158 | 159 | auto prev = state_; 160 | state_ = st; 161 | 162 | toNewGeneration(now); 163 | if (settings_.on_state_change != nullptr) 164 | { 165 | settings_.on_state_change(settings_.name, prev, st); 166 | } 167 | } 168 | 169 | void CircuitBreaker::toNewGeneration(std::chrono::system_clock::time_point now) 170 | { 171 | generation_++; 172 | counts_.clear(); 173 | 174 | auto zero = std::chrono::system_clock::from_time_t(0); 175 | 176 | switch (state_) 177 | { 178 | case STATE_CLOSED: 179 | { 180 | if (settings_.interval.count() == 0) 181 | expiry_ = zero; 182 | else 183 | expiry_ = now + settings_.interval; 184 | break; 185 | } 186 | case STATE_OPEN: 187 | expiry_ = now + settings_.timeout; 188 | break; 189 | default: 190 | expiry_ = zero; 191 | break; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /circuit_breaker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cppbreaker 9 | { 10 | class Counts 11 | { 12 | public: 13 | uint32_t requests = 0; 14 | uint32_t total_successes = 0; 15 | uint32_t total_failures = 0; 16 | uint32_t consecutive_successes = 0; 17 | uint32_t consecutive_failures = 0; 18 | 19 | void onRequest() { 20 | requests++; 21 | } 22 | void onSuccess() { 23 | total_successes++; 24 | consecutive_successes++; 25 | consecutive_failures = 0; 26 | } 27 | void onFailure() { 28 | total_failures++; 29 | consecutive_failures++; 30 | consecutive_successes = 0; 31 | } 32 | void clear() { 33 | requests = 0; 34 | total_successes = 0; 35 | total_failures = 0; 36 | consecutive_successes = 0; 37 | consecutive_failures = 0; 38 | } 39 | 40 | bool operator==(const Counts& cc) const 41 | { 42 | return requests == cc.requests && total_successes == cc.total_successes && 43 | total_failures == cc.total_failures && consecutive_successes == cc.consecutive_successes && 44 | consecutive_failures == cc.consecutive_failures ; 45 | } 46 | }; 47 | 48 | enum State 49 | { 50 | STATE_CLOSED = 0, 51 | STATE_HALF_OPEN = 1, 52 | STATE_OPEN = 2 53 | }; 54 | 55 | struct Settings 56 | { 57 | std::string name; 58 | 59 | // max_requests is the maximum number of requests allowed to pass through 60 | // when the CircuitBreaker is half-open. 61 | // If max_requests is 0, the CircuitBreaker allows only 1 request. 62 | uint32_t max_requests = 1; 63 | 64 | // interval is the cyclic period of the closed state 65 | // for the CircuitBreaker to clear the internal Counts. 66 | // If interval is 0, the CircuitBreaker doesn't clear internal Counts during the closed state. 67 | std::chrono::nanoseconds interval = std::chrono::nanoseconds(0); 68 | 69 | // timeout is the period of the open state, 70 | // after which the state of the CircuitBreaker becomes half-open. 71 | // If timeout is 0, the timeout value of the CircuitBreaker is set to 60 seconds. 72 | std::chrono::nanoseconds timeout = std::chrono::seconds(60); 73 | 74 | // ready_to_trip is called with a copy of Counts whenever a request fails in the closed state. 75 | // If ready_to_trip returns true, the CircuitBreaker will be placed into the open state. 76 | // If ready_to_trip is nil, default ready_to_trip is used. 77 | // Default ready_to_trip returns true when the number of consecutive failures is more than 5. 78 | std::function ready_to_trip = nullptr; 79 | 80 | // on_state_change is called whenever the state of the CircuitBreaker changes. 81 | std::function on_state_change = nullptr; 82 | }; 83 | 84 | enum ResultCode 85 | { 86 | ResultCodeOK = 0, 87 | // ErrTooManyRequests is returned when the CB state is half open and the requests count is over the cb maxRequests 88 | ResultCodeErrTooManyRequests = -0x80000000, 89 | // ErrOpenState is returned when the CB state is open 90 | ResultCodeErrOpenState = -0x70000000 91 | }; 92 | 93 | class CircuitBreaker 94 | { 95 | public: 96 | CircuitBreaker(const Settings& st); 97 | virtual ~CircuitBreaker() {} 98 | 99 | // std::get<0>(ret) : get expected result returned by Function_ 100 | // std::get<1>(ret) : get code returned by Function_ or circuit breaker 101 | // -0x80000000 and -0x40000000 are reserved for circuit breaker 102 | template 103 | std::tuple Execute(Function_ req) 104 | { 105 | uint64_t generation = 0; 106 | auto err = beforeRequest(&generation); 107 | if (err != ResultCodeOK) 108 | return std::make_tuple(Result_(), (int)err); 109 | 110 | std::tuple ret = req(); 111 | afterRequest(generation, std::get<1>(ret) == 0); 112 | return ret; 113 | } 114 | 115 | State GetState(); 116 | static std::string StateString(State st); 117 | 118 | std::string GetName() 119 | { 120 | return settings_.name; 121 | } 122 | 123 | protected: 124 | Settings settings_; 125 | 126 | std::mutex mutex_; 127 | State state_; 128 | uint64_t generation_ = 0; 129 | Counts counts_; 130 | std::chrono::system_clock::time_point expiry_; 131 | 132 | protected: 133 | bool defaultReadyToTrip(const Counts& counts) 134 | { 135 | return counts.consecutive_failures > 5; 136 | } 137 | 138 | int beforeRequest(uint64_t* gen); 139 | 140 | void afterRequest(uint64_t before, bool success); 141 | 142 | void onSuccess(State st, std::chrono::system_clock::time_point now); 143 | 144 | void onFailure(State st, std::chrono::system_clock::time_point now); 145 | 146 | uint64_t currentState(std::chrono::system_clock::time_point now, State* st); 147 | 148 | void setState(State st, std::chrono::system_clock::time_point now); 149 | 150 | void toNewGeneration(std::chrono::system_clock::time_point now); 151 | }; 152 | } 153 | 154 | -------------------------------------------------------------------------------- /demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | 3 | project(cppbreaker_demo) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wall") 6 | 7 | include_directories(${GTEST_INCLUDE_DIRS} ../) 8 | 9 | add_executable(cppbreaker_demo ../demo.cc ../../circuit_breaker.cc) 10 | -------------------------------------------------------------------------------- /demo/demo.cc: -------------------------------------------------------------------------------- 1 | #include "circuit_breaker.h" 2 | #include 3 | #include 4 | 5 | 6 | std::tuple execution_demo() 7 | { 8 | return std::make_tuple("hello demo", 1); 9 | } 10 | 11 | int main(int argc, char* argv[]) 12 | { 13 | // mock rpc call 14 | auto mock_rpc_call = [=](int i) -> int { 15 | return i < 3 ? 0 : 1; 16 | }; 17 | 18 | // settings 19 | cppbreaker::Settings st; 20 | st.name = "test_cb"; 21 | st.max_requests = 3; // the maximum number of requests when circuitbreaker is half open state 22 | st.interval = std::chrono::seconds(600); // reset the counts of requests every 600 seconds 23 | st.timeout = std::chrono::seconds(2); // the period of the open state 24 | 25 | // if the number of requests is greater 10 and 60% requests are failed, change circutbreaker to open state 26 | st.ready_to_trip = [](const cppbreaker::Counts& counts)->bool { 27 | auto num_reqs = counts.requests; 28 | auto failure_ratio = double(counts.total_failures) / double(num_reqs); 29 | return num_reqs >= 10 && failure_ratio >= 0.6; 30 | }; 31 | 32 | st.on_state_change = [](const std::string& name, cppbreaker::State from, cppbreaker::State to) { 33 | std::cout << "circuit breaker(" << name << ") : state change from(" << cppbreaker::CircuitBreaker::StateString(from) << ") to(" << cppbreaker::CircuitBreaker::StateString(to) << ")." << std::endl; 34 | }; 35 | 36 | // create circuit breaker 37 | cppbreaker::CircuitBreaker cb(st); 38 | 39 | // close to open 40 | for (int i = 0; i < 10; i++) 41 | { 42 | auto rets = cb.Execute([=]()-> std::tuple { 43 | int result_code = mock_rpc_call(i); 44 | return std::make_tuple(0.4, result_code); 45 | }); 46 | double result = std::get<0>(rets); 47 | int code = std::get<1>(rets); 48 | if (code != 0) 49 | std::cout << "error : " << code << std::endl; 50 | else 51 | std::cout << "ok : " << result << std::endl; 52 | } 53 | 54 | std::cout << "circuit breaker state : " << cb.StateString(cb.GetState()) << std::endl; 55 | 56 | // open to half open 57 | // sleep 2 seconds, 58 | std::this_thread::sleep_for(std::chrono::seconds(2)); 59 | 60 | for (int i = 0; i < 4; i++) 61 | { 62 | auto rets = cb.Execute([=]()-> std::tuple { 63 | int result_code = mock_rpc_call(1); 64 | return std::make_tuple("hello cpp breaker", result_code); 65 | }); 66 | auto result = std::get<0>(rets); 67 | int code = std::get<1>(rets); 68 | if (code != 0) 69 | std::cout << "error : " << code << std::endl; 70 | else 71 | std::cout << "ok : " << result << std::endl; 72 | 73 | std::cout << "circuit breaker state : " << cb.StateString(cb.GetState()) << std::endl; 74 | } 75 | 76 | auto rets = cb.Execute(execution_demo); 77 | std::cout << std::get<0>(rets) << " " << std::get<1>(rets) << std::endl; 78 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | 3 | project(cppbreaker) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wall") 6 | 7 | find_package(GTest REQUIRED) 8 | find_package(Threads REQUIRED) 9 | 10 | include_directories(${GTEST_INCLUDE_DIRS} ../) 11 | 12 | add_executable(cppbreaker ../circuit_breaker_test.cc ../../circuit_breaker.cc) 13 | 14 | target_link_libraries(cppbreaker ${GTEST_BOTH_LIBRARIES}) 15 | target_link_libraries(cppbreaker ${CMAKE_THREAD_LIBS_INIT}) 16 | add_test(Test cppbreaker) 17 | enable_testing() 18 | -------------------------------------------------------------------------------- /test/circuit_breaker_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "circuit_breaker.h" 5 | 6 | using namespace cppbreaker; 7 | 8 | class CbTest : public testing::Test 9 | { 10 | }; 11 | 12 | struct StateChange 13 | { 14 | StateChange() { from = STATE_CLOSED; to = STATE_CLOSED; } 15 | StateChange(const std::string& n, State f, State t) { 16 | name = n; from = f; to = t; 17 | } 18 | std::string name; 19 | State from; 20 | State to; 21 | 22 | bool operator==(const StateChange& sc) const { 23 | return name == sc.name && from == sc.from && to == sc.to; 24 | } 25 | }; 26 | 27 | static StateChange stateChange; 28 | 29 | class testCircuitBreaker : public CircuitBreaker 30 | { 31 | public: 32 | testCircuitBreaker(const Settings& st) : CircuitBreaker(st) 33 | {} 34 | const Settings& settings() { 35 | return settings_; 36 | } 37 | const Counts& counts() { 38 | return counts_; 39 | } 40 | std::chrono::system_clock::time_point expiry() { 41 | return expiry_; 42 | } 43 | void setExpiry(std::chrono::system_clock::time_point ep) 44 | { 45 | expiry_ = ep; 46 | } 47 | 48 | static std::shared_ptr newCustom() 49 | { 50 | Settings st; 51 | st.name = "cb"; 52 | st.max_requests = 3; 53 | st.interval = std::chrono::seconds(30); 54 | st.timeout = std::chrono::seconds(90); 55 | 56 | st.ready_to_trip = [](const Counts& counts)->bool { 57 | auto numReqs = counts.requests; 58 | auto failureRatio = double(counts.total_failures) / double(numReqs); 59 | return numReqs >= 3 && failureRatio >= 0.6; 60 | }; 61 | 62 | st.on_state_change = [](const std::string& name, State from, State to) { 63 | stateChange.name = name; 64 | stateChange.from = from; 65 | stateChange.to = to; 66 | }; 67 | 68 | return std::make_shared(st); 69 | } 70 | 71 | int fail() 72 | { 73 | auto ret = this->Execute([&]()-> std::tuple { 74 | return std::make_tuple(0, 100); 75 | }); 76 | int err = std::get<1>(ret); 77 | if (err == 100) 78 | return 0; 79 | return err; 80 | } 81 | 82 | int succeed() 83 | { 84 | auto ret = Execute([&]()-> std::tuple { 85 | return std::make_tuple(0, 0); 86 | }); 87 | int err = std::get<1>(ret); 88 | return err; 89 | } 90 | 91 | std::future succeedLater(std::chrono::nanoseconds delay) 92 | { 93 | return std::async(std::launch::async, [=]() { 94 | auto ret = Execute([&]()-> std::tuple { 95 | std::this_thread::sleep_for(delay); 96 | return std::make_tuple(0, 0); 97 | }); 98 | return std::get<1>(ret); 99 | }); 100 | } 101 | }; 102 | 103 | static Counts newCounts(uint32_t requests, uint32_t total_successes, 104 | uint32_t total_failures, uint32_t consecutive_successes, uint32_t consecutive_failures) 105 | { 106 | Counts cc; 107 | cc.requests = requests; cc.total_successes = total_successes; 108 | cc.total_failures = total_failures; cc.consecutive_successes = consecutive_successes; 109 | cc.consecutive_failures = consecutive_failures; 110 | return cc; 111 | } 112 | 113 | void pseudoSleep(testCircuitBreaker* cb, std::chrono::nanoseconds period) 114 | { 115 | if (cb->expiry().time_since_epoch().count() != 0) 116 | cb->setExpiry(cb->expiry() + -1 * period); 117 | } 118 | 119 | TEST_F(CbTest, TestStateConstants) 120 | { 121 | ASSERT_EQ(State(0), STATE_CLOSED); 122 | ASSERT_EQ(State(1), STATE_HALF_OPEN); 123 | ASSERT_EQ(State(2), STATE_OPEN); 124 | } 125 | 126 | TEST_F(CbTest, TestNewCircuitBreaker) 127 | { 128 | Settings settings; 129 | Counts defCounts; 130 | testCircuitBreaker defaultCB(settings); 131 | ASSERT_EQ("", defaultCB.GetName()); 132 | ASSERT_EQ(1, defaultCB.settings().max_requests); 133 | ASSERT_EQ(std::chrono::nanoseconds(0), defaultCB.settings().interval); 134 | ASSERT_EQ(std::chrono::seconds(60), defaultCB.settings().timeout); 135 | ASSERT_NE(nullptr, defaultCB.settings().ready_to_trip); 136 | ASSERT_EQ(nullptr, defaultCB.settings().on_state_change); 137 | ASSERT_EQ(STATE_CLOSED, defaultCB.GetState()); 138 | ASSERT_EQ(defCounts, defaultCB.counts()); 139 | ASSERT_EQ(std::chrono::system_clock::from_time_t(0), defaultCB.expiry()); 140 | 141 | auto customCB = testCircuitBreaker::newCustom(); 142 | ASSERT_EQ("cb", customCB->GetName()); 143 | ASSERT_EQ(3, customCB->settings().max_requests); 144 | ASSERT_EQ(std::chrono::seconds(30), customCB->settings().interval); 145 | ASSERT_EQ(std::chrono::seconds(90), customCB->settings().timeout); 146 | ASSERT_NE(nullptr, customCB->settings().ready_to_trip); 147 | ASSERT_NE(nullptr, customCB->settings().on_state_change); 148 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 149 | 150 | ASSERT_EQ(defCounts, customCB->counts()); 151 | ASSERT_NE(0, customCB->expiry().time_since_epoch().count()); 152 | } 153 | 154 | 155 | TEST_F(CbTest, TestDefaultCircuitBreaker) 156 | { 157 | Settings settings; 158 | testCircuitBreaker defaultCB(settings); 159 | ASSERT_EQ(0, defaultCB.expiry().time_since_epoch().count()); 160 | 161 | for (int i = 0; i < 5; i++) 162 | { 163 | ASSERT_EQ(0, defaultCB.fail()); 164 | } 165 | ASSERT_EQ(STATE_CLOSED, defaultCB.GetState()); 166 | ASSERT_EQ(newCounts(5, 0, 5, 0, 5), defaultCB.counts()); 167 | 168 | ASSERT_EQ(0, defaultCB.succeed()); 169 | ASSERT_EQ(STATE_CLOSED, defaultCB.GetState()); 170 | ASSERT_EQ(newCounts(6, 1, 5, 1, 0), defaultCB.counts()); 171 | 172 | ASSERT_EQ(0, defaultCB.fail()); 173 | ASSERT_EQ(STATE_CLOSED, defaultCB.GetState()); 174 | ASSERT_EQ(newCounts(7, 1, 6, 0, 1), defaultCB.counts()); 175 | 176 | // StateClosed to StateOpen 177 | for (int i = 0; i < 5; i++) 178 | { 179 | ASSERT_EQ(0, defaultCB.fail()); 180 | } 181 | ASSERT_EQ(STATE_OPEN, defaultCB.GetState()); 182 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), defaultCB.counts()); 183 | ASSERT_NE(0, defaultCB.expiry().time_since_epoch().count()); 184 | 185 | ASSERT_NE(0, defaultCB.succeed()); 186 | ASSERT_NE(0, defaultCB.fail()); 187 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), defaultCB.counts()); 188 | 189 | pseudoSleep(&defaultCB, std::chrono::seconds(59)); 190 | ASSERT_EQ(STATE_OPEN, defaultCB.GetState()); 191 | 192 | // StateOpen to StateHalfOpen 193 | pseudoSleep(&defaultCB, std::chrono::seconds(1)); 194 | ASSERT_EQ(STATE_HALF_OPEN, defaultCB.GetState()); 195 | ASSERT_EQ(0, defaultCB.expiry().time_since_epoch().count()); 196 | 197 | // StateHalfOpen to StateOpen 198 | ASSERT_EQ(0, defaultCB.fail()); 199 | ASSERT_EQ(STATE_OPEN, defaultCB.GetState()); 200 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), defaultCB.counts()); 201 | ASSERT_NE(0, defaultCB.expiry().time_since_epoch().count()); 202 | 203 | // StateOpen to StateHalfOpen 204 | pseudoSleep(&defaultCB, std::chrono::seconds(60)); 205 | ASSERT_EQ(STATE_HALF_OPEN, defaultCB.GetState()); 206 | ASSERT_EQ(0, defaultCB.expiry().time_since_epoch().count()); 207 | 208 | // StateHalfOpen to StateClosed 209 | ASSERT_EQ(0, defaultCB.succeed()); 210 | ASSERT_EQ(STATE_CLOSED, defaultCB.GetState()); 211 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), defaultCB.counts()); 212 | ASSERT_EQ(0, defaultCB.expiry().time_since_epoch().count()); 213 | } 214 | 215 | 216 | TEST_F(CbTest, TestCustomCircuitBreaker) 217 | { 218 | auto customCB = testCircuitBreaker::newCustom(); 219 | ASSERT_EQ("cb", customCB->GetName()); 220 | 221 | for (int i = 0; i < 5; i++) 222 | { 223 | ASSERT_EQ(0, customCB->succeed()); 224 | ASSERT_EQ(0, customCB->fail()); 225 | } 226 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 227 | ASSERT_EQ(newCounts(10, 5, 5, 0, 1), customCB->counts()); 228 | 229 | pseudoSleep(customCB.get(), std::chrono::seconds(29)); 230 | ASSERT_EQ(0, customCB->succeed()); 231 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 232 | ASSERT_EQ(newCounts(11, 6, 5, 1, 0), customCB->counts()); 233 | 234 | pseudoSleep(customCB.get(), std::chrono::seconds(1)); 235 | ASSERT_EQ(0, customCB->fail()); 236 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 237 | ASSERT_EQ(newCounts(1, 0, 1, 0, 1), customCB->counts()); 238 | 239 | // StateClosed to StateOpen 240 | ASSERT_EQ(0, customCB->succeed()); 241 | ASSERT_EQ(0, customCB->fail()); 242 | ASSERT_EQ(STATE_OPEN, customCB->GetState()); 243 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), customCB->counts()); 244 | ASSERT_NE(0, customCB->expiry().time_since_epoch().count()); 245 | ASSERT_EQ(StateChange("cb", STATE_CLOSED, STATE_OPEN), stateChange); 246 | 247 | // StateOpen to StateHalfOpen 248 | pseudoSleep(customCB.get(), std::chrono::seconds(90)); 249 | ASSERT_EQ(STATE_HALF_OPEN, customCB->GetState()); 250 | ASSERT_EQ(0, customCB->expiry().time_since_epoch().count()); 251 | ASSERT_EQ(StateChange("cb", STATE_OPEN, STATE_HALF_OPEN), stateChange); 252 | 253 | ASSERT_EQ(0, customCB->succeed()); 254 | ASSERT_EQ(0, customCB->succeed()); 255 | ASSERT_EQ(STATE_HALF_OPEN, customCB->GetState()); 256 | ASSERT_EQ(newCounts(2, 2, 0, 2, 0), customCB->counts()); 257 | 258 | // StateHalfOpen to StateClosed 259 | auto ch = customCB->succeedLater(std::chrono::milliseconds(100)); 260 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 261 | ASSERT_EQ(newCounts(3, 2, 0, 2, 0), customCB->counts()); 262 | ASSERT_NE(0, customCB->succeed()); 263 | ASSERT_EQ(0, ch.get()); 264 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 265 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), customCB->counts()); 266 | ASSERT_NE(0, customCB->expiry().time_since_epoch().count()); 267 | ASSERT_EQ(StateChange("cb", STATE_HALF_OPEN, STATE_CLOSED), stateChange); 268 | } 269 | 270 | TEST_F(CbTest, TestCircuitBreakerInParallel) 271 | { 272 | auto customCB = testCircuitBreaker::newCustom(); 273 | pseudoSleep(customCB.get(), std::chrono::seconds(29)); 274 | ASSERT_EQ(0, customCB->succeed()); 275 | auto ch = customCB->succeedLater(std::chrono::milliseconds(1500)); 276 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 277 | ASSERT_EQ(newCounts(2, 1, 0, 1, 0), customCB->counts()); 278 | 279 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 280 | ASSERT_EQ(STATE_CLOSED, customCB->GetState()); 281 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), customCB->counts()); 282 | ASSERT_EQ(0, ch.get()); 283 | ASSERT_EQ(newCounts(0, 0, 0, 0, 0), customCB->counts()); 284 | } 285 | 286 | TEST_F(CbTest, TestGeneration) 287 | { 288 | auto customCB = testCircuitBreaker::newCustom(); 289 | int numReqs = 10000; 290 | auto fn = [&]() { 291 | for (int i = 0; i < numReqs; i++) 292 | { 293 | auto ret = customCB->succeed(); 294 | ASSERT_EQ(0, ret); 295 | } 296 | }; 297 | 298 | auto cpus = std::thread::hardware_concurrency(); 299 | auto totalReqs = cpus * numReqs; 300 | std::vector threads; 301 | for (int i = 0; i < cpus; i++) 302 | { 303 | threads.emplace_back(std::move(std::thread(fn))); 304 | } 305 | 306 | for (auto& t : threads) 307 | { 308 | t.join(); 309 | } 310 | ASSERT_EQ(newCounts(totalReqs, totalReqs, 0, totalReqs, 0), customCB->counts()); 311 | } 312 | 313 | 314 | --------------------------------------------------------------------------------