(FSM) updateState with unprotected action
[folly.git] / folly / wangle / detail / FSM.h
1 /*
2  * Copyright 2014 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #pragma once
18
19 #include <atomic>
20 #include <mutex>
21 #include <folly/SmallLocks.h>
22
23 namespace folly { namespace wangle { namespace detail {
24
25 /// Finite State Machine helper base class.
26 /// Inherit from this.
27 /// For best results, use an "enum class" for Enum.
28 template <class Enum>
29 class FSM {
30 private:
31   // I am not templatizing this because folly::MicroSpinLock needs to be
32   // zero-initialized (or call init) which isn't generic enough for something
33   // that behaves like std::mutex. :(
34   using Mutex = folly::MicroSpinLock;
35   Mutex mutex_ {0};
36
37   // This might not be necessary for all Enum types, e.g. anything
38   // that is atomically updated in practice on this CPU and there's no risk
39   // of returning a bogus state because of tearing.
40   // An optimization would be to use a static conditional on the Enum type.
41   std::atomic<Enum> state_;
42
43 public:
44   explicit FSM(Enum startState) : state_(startState) {}
45
46   Enum getState() const {
47     return state_.load(std::memory_order_relaxed);
48   }
49
50   /// Atomically do a state transition with accompanying action.
51   /// @returns true on success, false and action unexecuted otherwise
52   template <class F>
53   bool updateState(Enum A, Enum B, F const& action) {
54     std::lock_guard<Mutex> lock(mutex_);
55     if (state_ != A) return false;
56     state_ = B;
57     action();
58     return true;
59   }
60
61   /// Atomically do a state transition with accompanying action. Then do the
62   /// unprotected action without holding the lock. If the atomic transition
63   /// fails, returns false and neither action was executed.
64   ///
65   /// This facilitates code like this:
66   ///   bool done = false;
67   ///   while (!done) {
68   ///     switch (getState()) {
69   ///     case State::Foo:
70   ///       done = updateState(State::Foo, State::Bar,
71   ///           [&]{ /* do protected stuff */ },
72   ///           [&]{ /* do unprotected stuff */});
73   ///       break;
74   ///
75   /// Which reads nicer than code like this:
76   ///   while (true) {
77   ///     switch (getState()) {
78   ///     case State::Foo:
79   ///       if (!updateState(State::Foo, State::Bar,
80   ///           [&]{ /* do protected stuff */ })) {
81   ///         continue;
82   ///       }
83   ///       /* do unprotected stuff */
84   ///       return; // or otherwise break out of the loop
85   template <class F1, class F2>
86   bool updateState(Enum A, Enum B,
87                    F1 const& protectedAction, F2 const& unprotectedAction) {
88     bool result = updateState(A, B, protectedAction);
89     if (result) {
90       unprotectedAction();
91     }
92     return result;
93   }
94 };
95
96 #define FSM_START \
97   {bool done = false; while (!done) { auto state = getState(); switch (state) {
98
99 #define FSM_UPDATE2(b, protectedAction, unprotectedAction) \
100     done = updateState(state, (b), (protectedAction), (unprotectedAction));
101
102 #define FSM_UPDATE(b, action) FSM_UPDATE2((b), (action), []{})
103
104 #define FSM_CASE(a, b, action) \
105   case (a): \
106     FSM_UPDATE((b), (action)); \
107     break;
108
109 #define FSM_CASE2(a, b, protectedAction, unprotectedAction) \
110   case (a): \
111     FSM_UPDATE2((b), (protectedAction), (unprotectedAction)); \
112     break;
113
114 #define FSM_BREAK done = true; break;
115 #define FSM_END }}}
116
117
118 }}}