Skip to content

Commit ec3ea8e

Browse files
committed
Update DelegateMQ library source
1 parent b629b19 commit ec3ea8e

14 files changed

Lines changed: 170 additions & 33 deletions

File tree

DelegateMQ/delegate/DelegateAsync.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class DelegateFreeAsync<RetType(Args...)> : public DelegateFree<RetType(Args...)
146146
/// set the state of the new instance.
147147
/// @param[in] rhs The object to copy from.
148148
DelegateFreeAsync(const ClassType& rhs) :
149-
BaseType(rhs), m_thread(rhs.m_thread), m_priority(rhs.m_priority) {
149+
BaseType(rhs) {
150150
Assign(rhs);
151151
}
152152

@@ -420,14 +420,14 @@ class DelegateMemberAsync<TClass, RetType(Args...)> : public DelegateMember<TCla
420420
/// set the state of the new instance.
421421
/// @param[in] rhs The object to copy from.
422422
DelegateMemberAsync(const ClassType& rhs) :
423-
BaseType(rhs), m_thread(rhs.m_thread) {
423+
BaseType(rhs) {
424424
Assign(rhs);
425425
}
426426

427427
/// @brief Move constructor that transfers ownership of resources.
428428
/// @param[in] rhs The object to move from.
429429
DelegateMemberAsync(ClassType&& rhs) noexcept :
430-
BaseType(rhs), m_thread(rhs.m_thread) {
430+
BaseType(rhs), m_thread(rhs.m_thread), m_priority(rhs.m_priority) {
431431
rhs.Clear();
432432
}
433433

@@ -709,14 +709,14 @@ class DelegateFunctionAsync<RetType(Args...)> : public DelegateFunction<RetType(
709709
/// set the state of the new instance.
710710
/// @param[in] rhs The object to copy from.
711711
DelegateFunctionAsync(const ClassType& rhs) :
712-
BaseType(rhs), m_thread(rhs.m_thread) {
712+
BaseType(rhs) {
713713
Assign(rhs);
714714
}
715715

716716
/// @brief Move constructor that transfers ownership of resources.
717717
/// @param[in] rhs The object to move from.
718718
DelegateFunctionAsync(ClassType&& rhs) noexcept :
719-
BaseType(rhs), m_thread(rhs.m_thread) {
719+
BaseType(rhs), m_thread(rhs.m_thread), m_priority(rhs.m_priority) {
720720
rhs.Clear();
721721
}
722722

DelegateMQ/delegate/DelegateAsyncWait.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ class DelegateMemberAsyncWait<TClass, RetType(Args...)> : public DelegateMember<
542542
/// @brief Move constructor that transfers ownership of resources.
543543
/// @param[in] rhs The object to move from.
544544
DelegateMemberAsyncWait(ClassType&& rhs) noexcept :
545-
BaseType(rhs), m_thread(rhs.m_thread), m_timeout(rhs.m_timeout), m_success(rhs.m_success), m_retVal(rhs.m_retVal) {
545+
BaseType(rhs), m_thread(rhs.m_thread), m_priority(rhs.m_priority), m_timeout(rhs.m_timeout), m_success(rhs.m_success), m_retVal(rhs.m_retVal) {
546546
rhs.Clear();
547547
}
548548

@@ -923,7 +923,7 @@ class DelegateFunctionAsyncWait<RetType(Args...)> : public DelegateFunction<RetT
923923
/// @brief Move constructor that transfers ownership of resources.
924924
/// @param[in] rhs The object to move from.
925925
DelegateFunctionAsyncWait(ClassType&& rhs) noexcept :
926-
BaseType(rhs), m_thread(rhs.m_thread), m_timeout(rhs.m_timeout), m_success(rhs.m_success), m_retVal(rhs.m_retVal) {
926+
BaseType(rhs), m_thread(rhs.m_thread), m_priority(rhs.m_priority), m_timeout(rhs.m_timeout), m_success(rhs.m_success), m_retVal(rhs.m_retVal) {
927927
rhs.Clear();
928928
}
929929

DelegateMQ/delegate/DelegateRemote.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class DelegateFreeRemote<RetType(Args...)> : public DelegateFree<RetType(Args...
147147
/// set the state of the new instance.
148148
/// @param[in] rhs The object to copy from.
149149
DelegateFreeRemote(const ClassType& rhs) :
150-
BaseType(rhs), m_id(rhs.m_id) {
150+
BaseType(rhs) {
151151
Assign(rhs);
152152
}
153153

@@ -621,7 +621,7 @@ class DelegateMemberRemote<TClass, RetType(Args...)> : public DelegateMember<TCl
621621
/// set the state of the new instance.
622622
/// @param[in] rhs The object to copy from.
623623
DelegateMemberRemote(const ClassType& rhs) :
624-
BaseType(rhs), m_id(rhs.m_id) {
624+
BaseType(rhs) {
625625
Assign(rhs);
626626
}
627627

@@ -1109,7 +1109,7 @@ class DelegateFunctionRemote<RetType(Args...)> : public DelegateFunction<RetType
11091109
/// set the state of the new instance.
11101110
/// @param[in] rhs The object to copy from.
11111111
DelegateFunctionRemote(const ClassType& rhs) :
1112-
BaseType(rhs), m_id(rhs.m_id) {
1112+
BaseType(rhs) {
11131113
Assign(rhs);
11141114
}
11151115

DelegateMQ/delegate/IThread.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class IThread
2121

2222
/// Dispatch a `DelegateMsg` onto this thread. The implementer is responsible for
2323
/// getting the `DelegateMsg` into an OS message queue. Once `DelegateMsg` is
24-
/// on the destination thread of control, the `IDelegateInvoker::Invoke()` function
24+
/// on the destination thread of control, the `IInvoker::Invoke()` function
2525
/// must be called to execute the target function.
2626
/// @param[in] msg A shared pointer to the message.
2727
/// @post The destination thread calls `IThreadInvoker::Invoke()` when `DelegateMsg`

DelegateMQ/predef/os/freertos/Thread.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
/// @TODO: Implement a priority queue for the thread messages like
1717
/// std::priority_queue<ThreadMsg*, std::vector<ThreadMsg*>, ThreadMsgComparator>
1818
/// as implemented in stdlib\Thread.h if necessary.
19+
/// Optionally implement a watchdog mechanism as shown in stdlib\Thread.h.
1920

2021
class ThreadMsg;
2122

DelegateMQ/predef/os/stdlib/Thread.cpp

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "DelegateMQ.h"
12
#include "Thread.h"
23
#include "predef/util/Fault.h"
34

@@ -29,7 +30,7 @@ Thread::~Thread()
2930
//----------------------------------------------------------------------------
3031
// CreateThread
3132
//----------------------------------------------------------------------------
32-
bool Thread::CreateThread()
33+
bool Thread::CreateThread(std::optional<dmq::Duration> watchdogTimeout)
3334
{
3435
if (!m_thread)
3536
{
@@ -43,6 +44,27 @@ bool Thread::CreateThread()
4344
// Wait for the thread to enter the Process method
4445
m_threadStartFuture.get();
4546

47+
m_lastAliveTime.store(Timer::GetTime());
48+
49+
// Caller wants a watchdog timer?
50+
if (watchdogTimeout.has_value())
51+
{
52+
// Create watchdog timer
53+
m_watchdogTimeout = watchdogTimeout.value();
54+
55+
// Timer to ensure the Thread instance runs periodically. ThreadCheck invoked
56+
// on this thread instance.
57+
m_threadTimer = std::make_unique<Timer>();
58+
m_threadTimer->Expired = MakeDelegate(this, &Thread::ThreadCheck, *this);
59+
m_threadTimer->Start(m_watchdogTimeout.load() / 4);
60+
61+
// Timer to check that this Thread instance runs. WatchdogCheck invoked
62+
// on Timer::ProcessTimers() thread.
63+
m_watchdogTimer = std::make_unique<Timer>();
64+
m_watchdogTimer->Expired = MakeDelegate(this, &Thread::WatchdogCheck);
65+
m_watchdogTimer->Start(m_watchdogTimeout.load() / 2);
66+
}
67+
4668
LOG_INFO("Thread::CreateThread {}", THREAD_NAME);
4769
}
4870
return true;
@@ -100,6 +122,11 @@ void Thread::ExitThread()
100122
if (!m_thread)
101123
return;
102124

125+
if (m_watchdogTimer)
126+
m_watchdogTimer->Stop();
127+
if (m_threadTimer)
128+
m_threadTimer->Stop();
129+
103130
// Create a new ThreadMsg
104131
std::shared_ptr<ThreadMsg> threadMsg(new ThreadMsg(MSG_EXIT_THREAD, 0));
105132

@@ -147,6 +174,34 @@ void Thread::DispatchDelegate(std::shared_ptr<dmq::DelegateMsg> msg)
147174
typeid(*threadMsg->GetData()->GetInvoker()).name());
148175
}
149176

177+
//----------------------------------------------------------------------------
178+
// WatchdogCheck
179+
//----------------------------------------------------------------------------
180+
void Thread::WatchdogCheck()
181+
{
182+
auto now = Timer::GetTime();
183+
auto lastAlive = m_lastAliveTime.load();
184+
185+
auto delta = Timer::Difference(lastAlive, now);
186+
187+
// Watchdog expired?
188+
if (delta > m_watchdogTimeout.load())
189+
{
190+
LOG_ERROR("Watchdog detected unresponsive thread: {}", THREAD_NAME);
191+
192+
// @TODO You can optionally trigger recovery, restart, or further actions here
193+
// For example, throw or notify external system
194+
}
195+
}
196+
197+
//----------------------------------------------------------------------------
198+
// ThreadCheck
199+
//----------------------------------------------------------------------------
200+
void Thread::ThreadCheck()
201+
{
202+
// Do nothing
203+
}
204+
150205
//----------------------------------------------------------------------------
151206
// Process
152207
//----------------------------------------------------------------------------
@@ -155,10 +210,12 @@ void Thread::Process()
155210
// Signal that the thread has started processing to notify CreateThread
156211
m_threadStartPromise.set_value();
157212

158-
LOG_INFO("Thread::Process {}", THREAD_NAME);
213+
LOG_INFO("Thread::Process Start {}", THREAD_NAME);
159214

160215
while (1)
161216
{
217+
m_lastAliveTime.store(Timer::GetTime());
218+
162219
std::shared_ptr<ThreadMsg> msg;
163220
{
164221
// Wait for a message to be added to the queue
@@ -193,11 +250,15 @@ void Thread::Process()
193250

194251
case MSG_EXIT_THREAD:
195252
{
253+
LOG_INFO("Thread::Process Exit Thread {}", THREAD_NAME);
196254
return;
197255
}
198256

199257
default:
258+
{
259+
LOG_INFO("Thread::Process Invalid Message {}", THREAD_NAME);
200260
throw std::invalid_argument("Invalid message ID");
261+
}
201262
}
202263
}
203264
}

DelegateMQ/predef/os/stdlib/Thread.h

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
// David Lafreniere, Feb 2017.
66

77
#include "delegate/IThread.h"
8+
#include "./predef/util/Timer.h"
89
#include "ThreadMsg.h"
910
#include <thread>
1011
#include <queue>
1112
#include <mutex>
1213
#include <atomic>
1314
#include <condition_variable>
1415
#include <future>
16+
#include <optional>
1517

1618
// Comparator for priority queue
1719
struct ThreadMsgComparator {
@@ -20,6 +22,21 @@ struct ThreadMsgComparator {
2022
}
2123
};
2224

25+
/// @brief Cross-platform thread for any system supporting C++11 std::thread (e.g. Windows, Linux).
26+
/// @details The Thread class creates a worker thread capable of dispatching and
27+
/// invoking asynchronous delegates.
28+
///
29+
/// // Create thread with a watchdog timeout
30+
/// thread->CreateThread(std::chrono::milliseconds(2000));
31+
///
32+
/// // Or create without a watchdog
33+
/// thread->CreateThread();
34+
///
35+
/// WatchdogCheck() is invoked by a Timer instance. In a real-time operating system (RTOS),
36+
/// Timer::ProcessTimers() is typically called from the highest-priority task in the system.
37+
/// This ensures that any user thread becoming unresponsive can still be detected,
38+
/// since WatchdogCheck() runs at a higher priority. For mission-critical systems,
39+
/// a hardware watchdog should also be used as a fail-safe.
2340
class Thread : public dmq::IThread
2441
{
2542
public:
@@ -29,11 +46,13 @@ class Thread : public dmq::IThread
2946
/// Destructor
3047
~Thread();
3148

32-
/// Called once to create the worker thread
49+
/// Called once to create the worker thread. If watchdogTimeout value
50+
/// provided, the maximum watchdog interval is used. Otherwise no watchdog.
51+
/// @param[in] watchdogTimeout - optional watchdog timeout.
3352
/// @return TRUE if thread is created. FALSE otherise.
34-
bool CreateThread();
53+
bool CreateThread(std::optional<dmq::Duration> watchdogTimeout = std::nullopt);
3554

36-
/// Called once a program exit to exit the worker thread
55+
/// Called once at program exit to shut down the worker thread
3756
void ExitThread();
3857

3958
/// Get the ID of this thread instance
@@ -48,6 +67,9 @@ class Thread : public dmq::IThread
4867
/// Get size of thread message queue.
4968
size_t GetQueueSize();
5069

70+
/// Dispatch and invoke a delegate target on the destination thread.
71+
/// @param[in] msg - Delegate message containing target function
72+
/// arguments.
5173
virtual void DispatchDelegate(std::shared_ptr<dmq::DelegateMsg> msg);
5274

5375
private:
@@ -59,6 +81,18 @@ class Thread : public dmq::IThread
5981

6082
void SetThreadName(std::thread::native_handle_type handle, const std::string& name);
6183

84+
/// Check watchdog is expired. This function is called by the thread
85+
/// the calls Timer::ProcessTimers(). This function is thread-safe.
86+
/// In a real-time OS, Timer::ProcessTimers() typically is called by the highest
87+
/// priority task in the system.
88+
void WatchdogCheck();
89+
90+
/// Timer expiration function used to check that thread loop is running.
91+
/// This function is called by this thread context (m_thread). The
92+
/// Thread::Process() function must be called periodically even if no
93+
/// other user delegate events to be handled.
94+
void ThreadCheck();
95+
6296
std::unique_ptr<std::thread> m_thread;
6397
std::priority_queue<std::shared_ptr<ThreadMsg>,
6498
std::vector<std::shared_ptr<ThreadMsg>>,
@@ -72,6 +106,12 @@ class Thread : public dmq::IThread
72106
std::future<void> m_threadStartFuture;
73107

74108
std::atomic<bool> m_exit;
109+
110+
// Watchdog related members
111+
std::atomic<dmq::Duration> m_lastAliveTime;
112+
std::unique_ptr<Timer> m_watchdogTimer;
113+
std::unique_ptr<Timer> m_threadTimer;
114+
std::atomic<dmq::Duration> m_watchdogTimeout;
75115
};
76116

77117
#endif

DelegateMQ/predef/transport/mqtt/MqttTransport.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class IMqttReceiveHandler
2727
virtual void Receive() = 0;
2828
};
2929

30-
void debugTrace(enum MQTTCLIENT_TRACE_LEVELS level, char* message) {
30+
static void debugTrace(enum MQTTCLIENT_TRACE_LEVELS level, char* message) {
3131
//printf("TRACE [%d]: %s\n", level, message);
3232
}
3333

DelegateMQ/predef/transport/win32-udp/Win32UdpTransport.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class UdpTransport : public ITransport
3535

3636
UdpTransport() : m_thread("UdpTransport"), m_sendTransport(this), m_recvTransport(this)
3737
{
38-
m_thread.CreateThread();
38+
// Create thread with a 5s watchdog timeout
39+
m_thread.CreateThread(std::chrono::milliseconds(5000));
3940
}
4041

4142
~UdpTransport()

DelegateMQ/predef/transport/zeromq/ZeroMqTransport.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class ZeroMqTransport : public ITransport
3333

3434
ZeroMqTransport() : m_thread("ZeroMQTransport"), m_sendTransport(this), m_recvTransport(this)
3535
{
36-
m_thread.CreateThread();
36+
// Create thread with a 5s watchdog timeout
37+
m_thread.CreateThread(std::chrono::milliseconds(5000));
3738
}
3839

3940
~ZeroMqTransport()

0 commit comments

Comments
 (0)