| /* |
| * Copyright 2015 Facebook, Inc. |
| * |
| * 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 <atomic> |
| #include <mutex> |
| #include <folly/MicroSpinLock.h> |
| |
| namespace folly { namespace detail { |
| |
| /// Finite State Machine helper base class. |
| /// Inherit from this. |
| /// For best results, use an "enum class" for Enum. |
| template <class Enum> |
| class FSM { |
| private: |
| // I am not templatizing this because folly::MicroSpinLock needs to be |
| // zero-initialized (or call init) which isn't generic enough for something |
| // that behaves like std::mutex. :( |
| using Mutex = folly::MicroSpinLock; |
| Mutex mutex_ {0}; |
| |
| // This might not be necessary for all Enum types, e.g. anything |
| // that is atomically updated in practice on this CPU and there's no risk |
| // of returning a bogus state because of tearing. |
| // An optimization would be to use a static conditional on the Enum type. |
| std::atomic<Enum> state_; |
| |
| public: |
| explicit FSM(Enum startState) : state_(startState) {} |
| |
| Enum getState() const { |
| return state_.load(std::memory_order_acquire); |
| } |
| |
| /// Atomically do a state transition with accompanying action. |
| /// The action will see the old state. |
| /// @returns true on success, false and action unexecuted otherwise |
| template <class F> |
| bool updateState(Enum A, Enum B, F const& action) { |
| if (!mutex_.try_lock()) { |
| mutex_.lock(); |
| } |
| if (state_.load(std::memory_order_acquire) != A) { |
| mutex_.unlock(); |
| return false; |
| } |
| action(); |
| state_.store(B, std::memory_order_release); |
| mutex_.unlock(); |
| return true; |
| } |
| |
| /// Atomically do a state transition with accompanying action. Then do the |
| /// unprotected action without holding the lock. If the atomic transition |
| /// fails, returns false and neither action was executed. |
| /// |
| /// This facilitates code like this: |
| /// bool done = false; |
| /// while (!done) { |
| /// switch (getState()) { |
| /// case State::Foo: |
| /// done = updateState(State::Foo, State::Bar, |
| /// [&]{ /* do protected stuff */ }, |
| /// [&]{ /* do unprotected stuff */}); |
| /// break; |
| /// |
| /// Which reads nicer than code like this: |
| /// while (true) { |
| /// switch (getState()) { |
| /// case State::Foo: |
| /// if (!updateState(State::Foo, State::Bar, |
| /// [&]{ /* do protected stuff */ })) { |
| /// continue; |
| /// } |
| /// /* do unprotected stuff */ |
| /// return; // or otherwise break out of the loop |
| /// |
| /// The protected action will see the old state, and the unprotected action |
| /// will see the new state. |
| template <class F1, class F2> |
| bool updateState(Enum A, Enum B, |
| F1 const& protectedAction, F2 const& unprotectedAction) { |
| bool result = updateState(A, B, protectedAction); |
| if (result) { |
| unprotectedAction(); |
| } |
| return result; |
| } |
| }; |
| |
| #define FSM_START(fsm) {\ |
| bool done = false; \ |
| while (!done) { auto state = fsm.getState(); switch (state) { |
| |
| #define FSM_UPDATE2(fsm, b, protectedAction, unprotectedAction) \ |
| done = fsm.updateState(state, (b), (protectedAction), (unprotectedAction)); |
| |
| #define FSM_UPDATE(fsm, b, action) FSM_UPDATE2(fsm, (b), (action), []{}) |
| |
| #define FSM_CASE(fsm, a, b, action) \ |
| case (a): \ |
| FSM_UPDATE(fsm, (b), (action)); \ |
| break; |
| |
| #define FSM_CASE2(fsm, a, b, protectedAction, unprotectedAction) \ |
| case (a): \ |
| FSM_UPDATE2(fsm, (b), (protectedAction), (unprotectedAction)); \ |
| break; |
| |
| #define FSM_BREAK done = true; break; |
| #define FSM_END }}} |
| |
| |
| }} // folly::detail |