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

igor-krechetov / hsmcpp / 24052618394

06 Apr 2026 09:40PM UTC coverage: 91.441%. Remained the same
24052618394

push

github

igor-krechetov
[1.0.4][r] fix potential multithreading issues

Fix
- Fixed potential race-conditions in dispatchers
- Replaced copy with move operations in hsm

22 of 39 new or added lines in 7 files covered. (56.41%)

16 existing lines in 4 files now uncovered.

2329 of 2547 relevant lines covered (91.44%)

1621.93 hits per line

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

98.4
/src/HsmEventDispatcherSTD.cpp
1
// Copyright (C) 2021 Igor Krechetov
2
// Distributed under MIT license. See file LICENSE for details
3

4
#include "hsmcpp/HsmEventDispatcherSTD.hpp"
5

6
#include "hsmcpp/logging.hpp"
7
#include "hsmcpp/os/CriticalSection.hpp"
8

9
namespace hsmcpp {
10

11
#undef HSM_TRACE_CLASS
12
#define HSM_TRACE_CLASS "HsmEventDispatcherSTD"
13

14
HsmEventDispatcherSTD::HsmEventDispatcherSTD(const size_t eventsCacheSize)
68✔
15
    // cppcheck-suppress misra-c2012-10.4 ; false-positive. thinks that ':' is arithmetic operation
16
    : HsmEventDispatcherBase(eventsCacheSize) {
68✔
17
    HSM_TRACE_CALL_DEBUG();
68✔
18
}
68✔
19

20
HsmEventDispatcherSTD::~HsmEventDispatcherSTD() {
136✔
21
    HSM_TRACE_CALL_DEBUG();
68✔
22

23
    HsmEventDispatcherSTD::stop();
68✔
24
    join();
68✔
25
}
136✔
26

27
std::shared_ptr<HsmEventDispatcherSTD> HsmEventDispatcherSTD::create(const size_t eventsCacheSize) {
68✔
28
    return std::shared_ptr<HsmEventDispatcherSTD>(new HsmEventDispatcherSTD(eventsCacheSize),
68✔
29
                                                  &HsmEventDispatcherBase::handleDelete);
136✔
30
}
31

32
bool HsmEventDispatcherSTD::deleteSafe() {
68✔
33
    // NOTE: just delete the instance. Calling destructor from any thread is safe
34
    return true;
68✔
35
}
36

37
void HsmEventDispatcherSTD::emitEvent(const HandlerID_t handlerID) {
1,872✔
38
    HSM_TRACE_CALL_DEBUG();
1,872✔
39

40
    if (true == mDispatcherThread.joinable()) {
1,872✔
41
        HsmEventDispatcherBase::emitEvent(handlerID);
1,872✔
42
    }
43
}
1,872✔
44

45
bool HsmEventDispatcherSTD::start() {
532✔
46
    HSM_TRACE_CALL_DEBUG();
532✔
47
    bool result = false;
532✔
48

49
    if (false == mDispatcherThread.joinable()) {
532✔
50
        HSM_TRACE_DEBUG("starting thread...");
64✔
51
        mStopDispatcher = false;
64✔
52
        mDispatcherThread = std::thread(&HsmEventDispatcherSTD::doDispatching, this);
64✔
53
        result = mDispatcherThread.joinable();
64✔
54
    } else {
55
        result = (mDispatcherThread.get_id() != std::thread::id());
56
    }
57

58
    return result;
532✔
59
}
60

61
void HsmEventDispatcherSTD::stop() {
140✔
62
    HSM_TRACE_CALL_DEBUG();
140✔
63
    UniqueLock lck(mEmitSync);
140✔
64

65
    HsmEventDispatcherBase::stop();
140✔
66
    unregisterAllEventHandlers();
140✔
67
    notifyDispatcherAboutEvent();
140✔
68
    notifyTimersThread();
140✔
69
}
140✔
70

71
void HsmEventDispatcherSTD::join() {
68✔
72
    HSM_TRACE_CALL_DEBUG();
68✔
73

74
    if (true == mDispatcherThread.joinable()) {
68✔
75
        mDispatcherThread.join();
64✔
76
    }
77

78
    if (true == mTimersThread.joinable()) {
68✔
79
        mTimersThread.join();
4✔
80
    }
81
}
68✔
82

83
void HsmEventDispatcherSTD::startTimerImpl(const TimerID_t timerID, const unsigned int intervalMs, const bool isSingleShot) {
92✔
84
    HSM_TRACE_CALL_ARGS("timerID=%d, intervalMs=%d, isSingleShot=%d", SC2INT(timerID), intervalMs, BOOL2INT(isSingleShot));
92✔
85

86
    // lazy initialization of timers thread
87
    if (false == mTimersThread.joinable()) {
92✔
88
        mTimersThread = std::thread(&HsmEventDispatcherSTD::handleTimers, this);
4✔
89
    }
90

91
    {
92✔
92
        CriticalSection cs(mRunningTimersSync);
92✔
93
        auto it = mRunningTimers.find(timerID);
92✔
94

95
        // new timer
96
        if (mRunningTimers.end() == it) {
92✔
97
            RunningTimerInfo newTimer;
92✔
98

99
            newTimer.startedAt = std::chrono::steady_clock::now();
92✔
100
            newTimer.elapseAfter = newTimer.startedAt + std::chrono::milliseconds(intervalMs);
92✔
101

102
            mRunningTimers[timerID] = newTimer;
92✔
103
        } else {
104
            // restart timer
105
            it->second.startedAt = std::chrono::steady_clock::now();
×
UNCOV
106
            it->second.elapseAfter = it->second.startedAt + std::chrono::milliseconds(intervalMs);
×
107
        }
108
    }
92✔
109

110
    // wakeup timers thread
111
    notifyTimersThread();
92✔
112
}
184✔
113

114
void HsmEventDispatcherSTD::stopTimerImpl(const TimerID_t timerID) {
44✔
115
    HSM_TRACE_CALL_ARGS("timerID=%d", SC2INT(timerID));
44✔
116

117
    {
44✔
118
        CriticalSection cs(mRunningTimersSync);
44✔
119
        mRunningTimers.erase(timerID);
44✔
120
    }
44✔
121

122
    // wakeup timers thread
123
    notifyTimersThread();
44✔
124
}
44✔
125

126
void HsmEventDispatcherSTD::notifyDispatcherAboutEvent() {
2,056✔
127
    mEmitEvent.notify();
2,056✔
128
}
2,056✔
129

130
void HsmEventDispatcherSTD::doDispatching() {
64✔
131
    HSM_TRACE_CALL_DEBUG();
64✔
132

133
    while (false == mStopDispatcher) {
1,340✔
134
        HsmEventDispatcherBase::dispatchPendingEvents();
1,212✔
135

136
        if (false == mStopDispatcher) {
1,212✔
137
            UniqueLock lck(mEmitSync);
1,172✔
138

139
            if (true == mPendingEvents.empty()) {
1,172✔
140
                HSM_TRACE_DEBUG("wait for emit...");
676✔
141
                // NOTE: false-positive. "A function should have a single point of exit at the end" is not vialated because
142
                //       "return" statement belogs to a lamda function, not doDispatching.
143
                // cppcheck-suppress misra-c2012-15.5
144
                mEmitEvent.wait(lck, [=]() {
676✔
145
                    // cppcheck-suppress misra-c2012-15.5 ; false-positive. "return" statement belongs to lambda function
146
                    // NOTE: it's assumed that calling empty() is thread-safe. Even if due to a race condition we get
147
                    //       wrong value it will only cause a small delay in event processing, but won't cause any critical issues
148
                    return (false == mPendingEvents.empty()) || (false == mEnqueuedEvents.empty()) || (true == mStopDispatcher);
1,352✔
149
                });
150
                HSM_TRACE_DEBUG("woke up. pending events=%lu", mPendingEvents.size());
676✔
151
            }
152
        }
1,172✔
153
    }
154

155
    HSM_TRACE_DEBUG("EXIT");
64✔
156
}
64✔
157

158
void HsmEventDispatcherSTD::notifyTimersThread() {
276✔
159
    HSM_TRACE_CALL_DEBUG();
276✔
160
    mNotifiedTimersThread = true;
276✔
161
    mTimerEvent.notify();
276✔
162
}
276✔
163

164
void HsmEventDispatcherSTD::handleTimers() {
4✔
165
    HSM_TRACE_CALL_DEBUG();
4✔
166
    Mutex timerEventSync;
4✔
167
    UniqueLock lck(timerEventSync);
4✔
168

169
    while (false == mStopDispatcher) {
260✔
170
        mRunningTimersSync.lock();
256✔
171

172
        // NOTE: it's assumed that calling empty() is thread-safe. Even if due to
173
        //       a race condition we get wrong value it will only cause a small delay in timer processing,
174
        //       but won't cause any critical issues
175
        if (false == mRunningTimers.empty()) {
256✔
176
            auto itTimeout = mRunningTimers.begin();
192✔
177

178
            for (auto it = mRunningTimers.begin(); it != mRunningTimers.end(); ++it) {
428✔
179
                if (it->second.elapseAfter < itTimeout->second.elapseAfter) {
236✔
180
                    itTimeout = it;
16✔
181
                }
182
            }
183

184
            TimerID_t waitingTimerId = itTimeout->first;
192✔
185
            const int waitDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(itTimeout->second.elapseAfter -
192✔
186
                                                                                         std::chrono::steady_clock::now())
192✔
187
                                       .count();
192✔
188

189
            // unlock after itTimeout value is not needed anymore
190
            mRunningTimersSync.unlock();
192✔
191
            // NOTE: make sure we don't use itTimeout value after this line
192

193
            bool waitResult = false;
192✔
194

195
            // if waitDurationMs <= 0 it means that timer already expired and we only need to trigger event
196
            if (waitDurationMs > 0) {
192✔
197
                // NOTE: false-positive. "A function should have a single point of exit at the end" is not vialated because
198
                //       "return" statement belogs to a lamda function, not doDispatching.
199
                // cppcheck-suppress misra-c2012-15.5
200
                waitResult = mTimerEvent.wait_for(lck, waitDurationMs, [&]() { return mNotifiedTimersThread; });
184✔
201
            }
202

203
            mNotifiedTimersThread = false;
192✔
204

205
            if (false == waitResult) {
192✔
206
                // timeout expired
207

208
                // store wakeup time in case we'll need to calculate new elapseAfter value
209
                // needed to avoid potential delays caused by CriticalSection and handleTimerEvent()
210
                const auto wakeupTime = std::chrono::steady_clock::now();
88✔
211
                CriticalSection lckExpired(mRunningTimersSync);
88✔
212

213
                itTimeout = mRunningTimers.find(waitingTimerId);
88✔
214

215
                if (itTimeout != mRunningTimers.end()) {
88✔
216
                    const unsigned int nextIntervalMs = handleTimerEvent(waitingTimerId);
88✔
217

218
                    if (nextIntervalMs > 0u) {
88✔
219
                        // restart timer
220
                        itTimeout->second.startedAt = wakeupTime;
40✔
221
                        itTimeout->second.elapseAfter = itTimeout->second.startedAt + std::chrono::milliseconds(nextIntervalMs);
40✔
222
                    } else {
223
                        // single shot timer. remove from queue
224
                        mRunningTimers.erase(itTimeout);
48✔
225
                    }
226
                } else {
227
                    // do nothing
228
                }
229
            } else {
88✔
230
                // thread was woken up by start/stop operation. no need to do anything
231
            }
232
        } else {
233
            mRunningTimersSync.unlock();
64✔
234

235
            // wait for timer events
236
            mTimerEvent.wait(lck);
128✔
237
        }
238
    }
239

240
    HSM_TRACE_DEBUG("EXIT");
4✔
241
}
4✔
242

243
}  // namespace hsmcpp
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