blob: b6c7866b770efb328a21f7e7f2f7dfeb55971c01 [file] [log] [blame]
/*
* 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