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

mendersoftware / mender / 974575668

21 Aug 2023 12:04PM UTC coverage: 78.829% (-0.05%) from 78.877%
974575668

push

gitlab-ci

kacf
chore: Implement pushing of logs to the server.

Ticket: MEN-6581

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

18 of 18 new or added lines in 2 files covered. (100.0%)

5492 of 6967 relevant lines covered (78.83%)

238.75 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/common.hpp>
23
#include <common/events.hpp>
24
#include <common/log.hpp>
25

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

30
using namespace std;
31

32
namespace common = mender::common;
33
namespace events = mender::common::events;
34
namespace log = mender::common::log;
35

36
template <typename ContextType, typename EventType>
37
class StateMachineRunner;
38

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

45
        virtual void PostEvent(EventType event) = 0;
46
};
47

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

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

57
enum class TransitionFlag {
58
        Immediate,
59
        Deferred,
60
};
61

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

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

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

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

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

96
        State<ContextType, EventType> *current_state_;
97
        bool state_entered_ {false};
98

99
        unordered_map<TransitionCondition, State<ContextType, EventType> *, Hasher> transitions_;
100
        unordered_set<EventType> deferred_events_;
101

102
        friend class StateMachineRunner<ContextType, EventType>;
103

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

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

126
        ~StateMachineRunner() {
×
127
                DetachFromEventLoop();
×
128
        }
×
129

130
        void PostEvent(EventType event) override {
×
131
                event_queue_.push(event);
×
132
                PostToEventLoop();
×
133
        }
×
134

135
        // Continously run state machinery on event loop.
136
        void AttachToEventLoop(events::EventLoop &event_loop) {
137
                DetachFromEventLoop();
138

139
                cancelled_ = make_shared<bool>(false);
140

141
                // We don't actually own the object, we are just keeping a pointer to it. Use null
142
                // deleter.
143
                event_loop_.reset(&event_loop, [](events::EventLoop *loop) {});
144
        }
145

146
        void DetachFromEventLoop() {
×
147
                if (cancelled_) {
×
148
                        *cancelled_ = true;
×
149
                        cancelled_.reset();
×
150
                }
151
                event_loop_.reset();
×
152
        }
×
153

154
        void AddStateMachine(StateMachine<ContextType, EventType> &machine) {
155
                machines_.push_back(&machine);
156
        }
157

158
private:
159
        void RunOne() {
×
160
                vector<State<ContextType, EventType> *> to_run;
×
161

162
                for (auto machine : machines_) {
×
163
                        if (!machine->state_entered_) {
×
164
                                to_run.push_back(machine->current_state_);
×
165
                                machine->state_entered_ = true;
×
166
                        }
167
                }
168

169
                const size_t size = event_queue_.size();
×
170

171
                for (size_t count = 0; to_run.empty() && count < size; count++) {
×
172
                        bool deferred = false;
×
173
                        auto event = event_queue_.front();
×
174
                        event_queue_.pop();
×
175

176
                        for (auto machine : machines_) {
×
177
                                typename StateMachine<ContextType, EventType>::TransitionCondition cond {
×
178
                                        machine->current_state_, event};
×
179
                                if (machine->deferred_events_.find(event) != machine->deferred_events_.end()) {
×
180
                                        deferred = true;
×
181
                                }
182

183
                                auto match = machine->transitions_.find(cond);
×
184
                                if (match == machine->transitions_.end()) {
×
185
                                        // No match in this machine, continue.
186
                                        continue;
×
187
                                }
188

189
                                auto &target = match->second;
×
190
                                to_run.push_back(target);
×
191
                                machine->current_state_ = target;
×
192
                        }
193

194
                        if (to_run.empty()) {
×
195
                                if (deferred) {
×
196
                                        // Put back in the queue to try later. This won't be tried
197
                                        // again during this run, due to only making `size`
198
                                        // attempts in the for loop.
199
                                        event_queue_.push(event);
×
200
                                } else {
201
                                        string states = common::BestAvailableTypeName(*machines_[0]->current_state_);
×
202
                                        for (size_t i = 1; i < machines_.size(); i++) {
×
203
                                                states += ", ";
×
204
                                                states += common::BestAvailableTypeName(*machines_[i]->current_state_);
×
205
                                        }
206
                                        log::Fatal(
×
207
                                                "State machine event " + StateEventToString(event)
×
208
                                                + " was not handled by any transition. Current states: " + states
×
209
                                                + ". This is a bug and an irrecoverable error. "
×
210
                                                + "Aborting in the hope that restarting will help.");
×
211
                                }
212
                        }
213
                }
214

215
                if (!to_run.empty()) {
×
216
                        for (auto &state : to_run) {
×
217
                                log::Trace("Entering state " + common::BestAvailableTypeName(*state));
×
218
                                state->OnEnter(ctx_, *this);
×
219
                        }
220
                        // Since we ran something, there may be more events waiting to
221
                        // execute. OTOH, if we didn't, it either means that there are no events, or
222
                        // it means that all events currently in the queue are deferred, and not
223
                        // actionable until at least one state machine reaches a different state.
224
                        PostToEventLoop();
×
225
                }
226
        }
×
227

228
        void PostToEventLoop() {
×
229
                if (!event_loop_) {
×
230
                        return;
×
231
                }
232

233
                auto cancelled = cancelled_;
×
234
                event_loop_->Post([cancelled, this]() {
×
235
                        if (!*cancelled) {
×
236
                                RunOne();
×
237
                        }
238
                });
239
        }
240

241
        ContextType &ctx_;
242

243
        shared_ptr<bool> cancelled_;
244
        vector<StateMachine<ContextType, EventType> *> machines_;
245

246
        queue<EventType> event_queue_;
247

248
        // Would be nice with optional<EventLoop &> reference here, but optional doesn't support
249
        // references. Use a pointer with a null deleter instead.
250
        shared_ptr<events::EventLoop> event_loop_;
251
};
252

253
} // namespace state_machine
254
} // namespace common
255
} // namespace mender
256

257
#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