Apache Mesos
state_machine.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License
12 
13 #ifndef __PROCESS_STATE_MACHINE_HPP__
14 #define __PROCESS_STATE_MACHINE_HPP__
15 
16 #include <string>
17 
18 #include <process/future.hpp>
19 
20 #include <stout/hashmap.hpp>
21 #include <stout/option.hpp>
22 #include <stout/result_of.hpp>
23 #include <stout/traits.hpp>
24 #include <stout/try.hpp>
25 
26 namespace process {
27 
28 // An asbtraction to help build state machines within an actor.
29 //
30 // Actors are natural state machines but there is still quite a bit of
31 // boilerplate that one needs to end up writing. For example, let's
32 // say you have the following actor:
33 //
34 // class CoinOperatedTurnstile : public Process<CoinOperatedTurnstile>
35 // {
36 // public:
37 // // Returns nothing if you've been able to deposit a coin.
38 // Future<Nothing> deposit()
39 // {
40 // if (state != State::LOCKED) {
41 // return Failure("Coin already deposited in turnstile");
42 // }
43 //
44 // state = State::UNLOCKED;
45 //
46 // ... perform any side effects related to depositing a coin ...;
47 //
48 // return Nothing();
49 // }
50 //
51 // // Returns nothing if you've been able to push through the turnstile.
52 // Future<Nothing> push()
53 // {
54 // if (state != State::UNLOCKED) {
55 // return Failure("Turnstile is locked");
56 // }
57 //
58 // state = State::LOCKED;
59 //
60 // ... perform any side effects from opening the turnstile ...;
61 //
62 // return Nothing();
63 // }
64 //
65 // private:
66 // enum class State {
67 // UNLOCKED,
68 // LOCKED,
69 // } state = State::LOCKED;
70 // };
71 //
72 // With the `StateMachine` abstraction below this becomes:
73 //
74 // class CoinOperatedTurnstile : public Process<CoinOperatedTurnstile>
75 // {
76 // public:
77 // // Returns nothing if you've been able to deposit a coin.
78 // Future<Nothing> deposit()
79 // {
80 // return state.transition<State::LOCKED, State::UNLOCKED>(
81 // []() -> Future<Nothing> {
82 // ... perform any side effects related to depositing a coin ...;
83 // },
84 // "Coin already deposited in turnstile");
85 // }
86 //
87 // // Returns nothing if you've been able to push through the turnstile.
88 // Future<Nothing> push()
89 // {
90 // return state.transition<State::UNLOCKED, State::LOCKED>(
91 // []() -> Future<Nothing> {
92 // ... perform any side effects from opening the turnstile ...;
93 // },
94 // "Turnstile is locked");
95 // }
96 //
97 // private:
98 // enum class State {
99 // UNLOCKED,
100 // LOCKED,
101 // };
102 //
103 // StateMachine<State> state = State::LOCKED;
104 // };
105 //
106 //
107 // *** Transition Semantics ***
108 //
109 // The semantics of `StateMachine::transition()` are:
110 //
111 // (1) Transition the state if the state is currently in the
112 // specified "from" state. If not, return an `Error()`.
113 //
114 // (2) Notify anyone waiting on that state transition (i.e., anyone
115 // that called `StateMachine::when()`).
116 //
117 // (3) Invoke the side effect function (if provided).
118 //
119 //
120 // *** Asynchronous Transitions ***
121 //
122 // In many circumstances you'll need to perform some work, often
123 // asynchronously, in order to transition to a particular state. For
124 // example, consider some actor that needs to perform some asynchronous
125 // action in order to transition from a "running" state to a
126 // "stopped" state. To do that you'll need to introduce an
127 // intermediate state and _only_ complete the transition after the
128 // asynchronous action has completed. For example:
129 //
130 // // Returns nothing once we have stopped.
131 // Future<Nothing> stop()
132 // {
133 // return state.transition<State::RUNNING, State::STOPPING>(
134 // []() {
135 // return asynchronously_stop()
136 // .then([]() -> Future<Nothing> {
137 // return state.transition<State::STOPPING, State::STOPPED>();
138 // });
139 // });
140 // }
141 //
142 // It can be tempting to skip this pattern even when you're not
143 // calling any asynchronous function, but be careful because you
144 // shouldn't be peforming ANY action to perform a transition without
145 // first changing the state to reflect the fact that you're doing the
146 // transition.
147 //
148 // The rule of thumb is this: if you need to perform _ANY_ action
149 // (synchronous or asynchronous) to transition to a state you _MUST_
150 // introduce an intermediate state to represent that you're performing
151 // that transition.
152 template <typename State>
154 {
155 public:
156  StateMachine(State initial) : state(initial) {}
157 
158  template <State from, State to, typename F>
160  F&& f,
161  Option<std::string>&& message = None())
162  {
163  if (state != from) {
164  return Error(message.getOrElse("Invalid current state"));
165  }
166 
167  state = to;
168 
169  foreach (Promise<Nothing>& promise, promises[state]) {
170  promise.set(Nothing());
171  }
172 
173  promises[state].clear();
174 
175  return f();
176  }
177 
178  template <State from, State to>
180  {
181  return transition<from, to>([]() { return Nothing(); }, std::move(message));
182  }
183 
184  template <State s>
185  bool is() const
186  {
187  return state == s;
188  }
189 
190  template <State s>
192  {
193  if (state != s) {
194  promises[s].emplace_back();
195  return promises[s].back().future();
196  }
197 
198  return Nothing();
199  }
200 
201 private:
202  State state;
203 
205 };
206 
207 } // namespace process {
208 
209 #endif // __PROCESS_STATE_MACHINE_HPP__
Definition: nothing.hpp:16
Definition: errorbase.hpp:36
F && f
Definition: defer.hpp:270
bool set(const T &_t)
Definition: future.hpp:827
Definition: check.hpp:33
Definition: state_machine.hpp:153
Try< Nothing > transition(Option< std::string > &&message=None())
Definition: state_machine.hpp:179
Future< Nothing > when()
Definition: state_machine.hpp:191
Definition: hashmap.hpp:38
StateMachine(State initial)
Definition: state_machine.hpp:156
Protocol< PromiseRequest, PromiseResponse > promise
Try< typename result_of< F()>::type > transition(F &&f, Option< std::string > &&message=None())
Definition: state_machine.hpp:159
Definition: none.hpp:27
Definition: executor.hpp:48
bool is() const
Definition: state_machine.hpp:185