• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 947394036

pending completion
947394036

push

gitlab-ci

kacf
chore: Treat events with no state transitions as fatal.

This was discussed with the team members. Since an unhandled event is
almost guaranteed to hang the state machine, then it's better to
terminate and let systemd try to restart us, in the hopes that
recovery will still work.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

3 of 3 new or added lines in 1 file covered. (100.0%)

4268 of 5997 relevant lines covered (71.17%)

148.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/common/state_machine.hpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#ifndef MENDER_COMMON_STATE_MACHINE_HPP
16
#define MENDER_COMMON_STATE_MACHINE_HPP
17

18
#include <queue>
19
#include <unordered_map>
20
#include <unordered_set>
21

22
#include <common/events.hpp>
23
#include <common/log.hpp>
24

25
namespace mender {
26
namespace common {
27
namespace state_machine {
28

29
using namespace std;
30

31
namespace events = mender::common::events;
32
namespace log = mender::common::log;
33

34
template <typename ContextType, typename EventType>
35
class StateMachineRunner;
36

37
template <typename EventType>
38
class EventPoster {
39
public:
40
        virtual ~EventPoster() {
×
41
        }
×
42

43
        virtual void PostEvent(EventType event) = 0;
44
};
45

46
template <typename ContextType, typename EventType>
47
class State {
48
public:
49
        virtual ~State() {
×
50
        }
×
51

52
        virtual void OnEnter(ContextType &ctx, EventPoster<EventType> &poster) = 0;
53
};
54

55
enum class TransitionFlag {
56
        Immediate,
57
        Deferred,
58
};
59

60
template <typename ContextType, typename EventType>
61
class StateMachine {
62
public:
63
        StateMachine(State<ContextType, EventType> &start_state) :
64
                current_state_(&start_state) {
65
        }
66
        StateMachine(StateMachine &) = delete;
67

68
        void SetState(State<ContextType, EventType> &state) {
69
                current_state_ = &state;
70
                state_entered_ = false;
71
        }
72

73
private:
74
        struct TransitionCondition {
75
                // Note: Comparing address-of states. We don't want to rely on comparison operators
76
                // in the states themselves, and we just want to know if they are different
77
                // instances.
78
                State<ContextType, EventType> *state;
79
                EventType event;
80

81
                bool operator==(const TransitionCondition &t) const {
×
82
                        return state == t.state && event == t.event;
×
83
                }
84
        };
85

86
        class Hasher {
87
        public:
88
                size_t operator()(const TransitionCondition &obj) const {
×
89
                        return std::hash<State<ContextType, EventType> *>()(obj.state)
×
90
                                   ^ std::hash<int>()(static_cast<int>(obj.event));
×
91
                }
92
        };
93

94
        State<ContextType, EventType> *current_state_;
95
        bool state_entered_ {false};
96

97
        unordered_map<TransitionCondition, State<ContextType, EventType> *, Hasher> transitions_;
98
        unordered_set<EventType> deferred_events_;
99

100
        friend class StateMachineRunner<ContextType, EventType>;
101

102
public:
103
        void AddTransition(
104
                State<ContextType, EventType> &source_state,
105
                EventType event,
106
                State<ContextType, EventType> &target_state,
107
                TransitionFlag flag) {
108
                transitions_[TransitionCondition {&source_state, event}] = &target_state;
109
                if (flag == TransitionFlag::Deferred) {
110
                        // Event is involved in at least one deferred transition, so mark that.
111
                        deferred_events_.insert(event);
112
                }
113
        }
114
};
115

116
template <typename ContextType, typename EventType>
117
class StateMachineRunner : virtual public EventPoster<EventType> {
118
public:
119
        StateMachineRunner(ContextType &ctx) :
120
                ctx_(ctx) {
121
        }
122
        StateMachineRunner(StateMachineRunner &) = delete;
123

124
        void PostEvent(EventType event) override {
×
125
                event_queue_.push(event);
×
126
                PostToEventLoop();
×
127
        }
×
128

129
        // Continously run state machinery on event loop.
130
        void AttachToEventLoop(events::EventLoop &event_loop) {
131
                DetachFromEventLoop();
132

133
                cancelled_ = make_shared<bool>(false);
134

135
                // We don't actually own the object, we are just keeping a pointer to it. Use null
136
                // deleter.
137
                event_loop_.reset(&event_loop, [](events::EventLoop *loop) {});
138
        }
139

140
        void DetachFromEventLoop() {
141
                if (cancelled_) {
142
                        *cancelled_ = true;
143
                        cancelled_.reset();
144
                }
145
                event_loop_.reset();
146
        }
147

148
        void AddStateMachine(StateMachine<ContextType, EventType> &machine) {
149
                machines_.push_back(&machine);
150
        }
151

152
private:
153
        void RunOne() {
×
154
                vector<State<ContextType, EventType> *> to_run;
×
155

156
                for (auto machine : machines_) {
×
157
                        if (!machine->state_entered_) {
×
158
                                to_run.push_back(machine->current_state_);
×
159
                                machine->state_entered_ = true;
×
160
                        }
161
                }
162

163
                const size_t size = event_queue_.size();
×
164

165
                for (size_t count = 0; to_run.empty() && count < size; count++) {
×
166
                        bool deferred = false;
×
167
                        auto event = event_queue_.front();
×
168
                        event_queue_.pop();
×
169

170
                        for (auto machine : machines_) {
×
171
                                typename StateMachine<ContextType, EventType>::TransitionCondition cond {
×
172
                                        machine->current_state_, event};
×
173
                                if (machine->deferred_events_.find(event) != machine->deferred_events_.end()) {
×
174
                                        deferred = true;
×
175
                                }
176

177
                                auto match = machine->transitions_.find(cond);
×
178
                                if (match == machine->transitions_.end()) {
×
179
                                        // No match in this machine, continue.
180
                                        continue;
×
181
                                }
182

183
                                auto &target = match->second;
×
184
                                to_run.push_back(target);
×
185
                                machine->current_state_ = target;
×
186
                        }
187

188
                        if (to_run.empty()) {
×
189
                                if (deferred) {
×
190
                                        // Put back in the queue to try later. This won't be tried
191
                                        // again during this run, due to only making `size`
192
                                        // attempts in the for loop.
193
                                        event_queue_.push(event);
×
194
                                } else {
195
                                        log::Fatal(
×
196
                                                "State machine event " + to_string(static_cast<int>(event))
×
197
                                                + " was not handled by any state. This is a bug and an irrecoverable error. "
×
198
                                                + "Aborting in the hope that restarting will help.");
×
199
                                }
200
                        }
201
                }
202

203
                if (!to_run.empty()) {
×
204
                        for (auto &state : to_run) {
×
205
                                state->OnEnter(ctx_, *this);
×
206
                        }
207
                        // Since we ran something, there may be more events waiting to
208
                        // execute. OTOH, if we didn't, it either means that there are no events, or
209
                        // it means that all events currently in the queue are deferred, and not
210
                        // actionable until at least one state machine reaches a different state.
211
                        PostToEventLoop();
×
212
                }
213
        }
×
214

215
        void PostToEventLoop() {
×
216
                if (!event_loop_) {
×
217
                        return;
×
218
                }
219

220
                auto cancelled = cancelled_;
×
221
                event_loop_->Post([cancelled, this]() {
×
222
                        if (!*cancelled_) {
×
223
                                RunOne();
×
224
                        }
225
                });
226
        }
227

228
        ContextType &ctx_;
229

230
        shared_ptr<bool> cancelled_;
231
        vector<StateMachine<ContextType, EventType> *> machines_;
232

233
        queue<EventType> event_queue_;
234

235
        // Would be nice with optional<EventLoop &> reference here, but optional doesn't support
236
        // references. Use a pointer with a null deleter instead.
237
        shared_ptr<events::EventLoop> event_loop_;
238
};
239

240
} // namespace state_machine
241
} // namespace common
242
} // namespace mender
243

244
#endif // MENDER_COMMON_STATE_MACHINE_HPP
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc