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

mendersoftware / mender / 2281564137

23 Jan 2026 10:59AM UTC coverage: 81.48% (+1.7%) from 79.764%
2281564137

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

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

327 existing lines in 44 files now uncovered.

8839 of 10848 relevant lines covered (81.48%)

20226.53 hits per line

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

87.18
/src/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 {
1,542✔
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) :
279✔
66
                current_state_(&start_state) {
279✔
67
        }
279✔
68
        StateMachine(StateMachine &) = delete;
69

70
        void SetState(State<ContextType, EventType> &state) {
71
                current_state_ = &state;
129✔
72
                state_entered_ = false;
123✔
73
        }
36✔
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 {
4,924✔
84
                        return state == t.state && event == t.event;
4,924✔
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)
31,987✔
92
                                   ^ std::hash<int>()(static_cast<int>(obj.event));
31,987✔
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(
20,449✔
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;
20,449✔
111
                if (flag == TransitionFlag::Deferred) {
20,449✔
112
                        // Event is involved in at least one deferred transition, so mark that.
113
                        deferred_events_.insert(event);
114
                }
115
        }
20,449✔
116
};
117

118
template <typename ContextType, typename EventType>
119
class StateMachineRunner : virtual public EventPoster<EventType> {
120
public:
121
        using IterationCallback = function<void()>;
122

123
        StateMachineRunner(ContextType &ctx) :
366✔
124
                ctx_(ctx) {
183✔
125
        }
366✔
126
        StateMachineRunner(StateMachineRunner &) = delete;
127

128
        ~StateMachineRunner() {
366✔
129
                DetachFromEventLoop();
183✔
130
        }
366✔
131

132
        void PostEvent(EventType event) override {
4,080✔
133
                event_queue_.push(event);
134
                PostToEventLoop();
4,080✔
135
        }
4,080✔
136

137
        // Continously run state machinery on event loop.
138
        void AttachToEventLoop(events::EventLoop &event_loop) {
183✔
139
                DetachFromEventLoop();
183✔
140

141
                cancelled_ = make_shared<bool>(false);
183✔
142

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

147
                PostToEventLoop();
183✔
148
        }
183✔
149

150
        void DetachFromEventLoop() {
366✔
151
                if (cancelled_) {
366✔
152
                        *cancelled_ = true;
183✔
153
                        cancelled_.reset();
183✔
154
                }
155
                event_loop_.reset();
366✔
156
        }
366✔
157

158
        void AddStateMachine(StateMachine<ContextType, EventType> &machine) {
279✔
159
                machines_.push_back(&machine);
279✔
160
        }
279✔
161

162
        void SetIterationCallback(IterationCallback callback) {
163
                iteration_callback_ = callback;
164
        }
165

166
private:
167
        void RunOne() {
6,652✔
168
                vector<State<ContextType, EventType> *> to_run;
169

170
                for (auto machine : machines_) {
18,833✔
171
                        if (!machine->state_entered_) {
12,181✔
172
                                to_run.push_back(machine->current_state_);
277✔
173
                                machine->state_entered_ = true;
277✔
174
                        }
175
                }
176

177
                const size_t size = event_queue_.size();
178

179
                for (size_t count = 0; to_run.empty() && count < size; count++) {
12,939✔
180
                        bool deferred = false;
181
                        auto event = event_queue_.front();
6,287✔
182
                        event_queue_.pop();
183

184
                        for (auto machine : machines_) {
17,825✔
185
                                typename StateMachine<ContextType, EventType>::TransitionCondition cond {
11,538✔
186
                                        machine->current_state_, event};
11,538✔
187
                                if (machine->deferred_events_.find(event) != machine->deferred_events_.end()) {
11,538✔
188
                                        deferred = true;
189
                                }
190

191
                                auto match = machine->transitions_.find(cond);
192
                                if (match == machine->transitions_.end()) {
11,538✔
193
                                        // No match in this machine, continue.
194
                                        continue;
7,570✔
195
                                }
196

197
                                auto &target = match->second;
3,968✔
198
                                to_run.push_back(target);
3,968✔
199
                                machine->current_state_ = target;
3,968✔
200
                        }
201

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

223
                if (!to_run.empty()) {
6,652✔
224
                        for (auto &state : to_run) {
8,246✔
225
                                log::Trace("Entering state " + common::BestAvailableTypeName(*state));
4,245✔
226
                                state->OnEnter(ctx_, *this);
4,245✔
227
                        }
228
                        // Since we ran something, there may be more events waiting to
229
                        // execute. OTOH, if we didn't, it either means that there are no events, or
230
                        // it means that all events currently in the queue are deferred, and not
231
                        // actionable until at least one state machine reaches a different state.
232
                        PostToEventLoop();
4,001✔
233
                }
234
        }
6,652✔
235

236
        void PostToEventLoop() {
8,264✔
237
                if (!event_loop_) {
8,264✔
UNCOV
238
                        return;
×
239
                }
240

241
                auto cancelled = cancelled_;
242
                event_loop_->Post([cancelled, this]() {
39,713✔
243
                        if (!*cancelled) {
6,657✔
244
                                RunOne();
6,652✔
245
                        }
246
                });
247
        }
248

249
        ContextType &ctx_;
250

251
        shared_ptr<bool> cancelled_;
252
        vector<StateMachine<ContextType, EventType> *> machines_;
253

254
        queue<EventType> event_queue_;
255

256
        // Would be nice with optional<EventLoop &> reference here, but optional doesn't support
257
        // references. Use a pointer with a null deleter instead.
258
        shared_ptr<events::EventLoop> event_loop_;
259

260
        IterationCallback iteration_callback_;
261
};
262

263
} // namespace state_machine
264
} // namespace common
265
} // namespace mender
266

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

© 2026 Coveralls, Inc