Skip to content

Commit 86e901b

Browse files
committed
Update DelegateMQ library
1 parent e6f0328 commit 86e901b

31 files changed

Lines changed: 816 additions & 376 deletions

DelegateMQ/delegate/DelegateAsyncWait.h

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,24 @@ class DelegateAsyncWaitMsg : public DelegateMsg
121121
/// the target function invoke.
122122
bool GetInvokerWaiting() { return m_invokerWaiting; }
123123

124-
/// Set to true when source thread is waiting for destination thread to complete the
124+
/// Set to true when source thread is waiting for destination thread to complete the
125125
/// function call.
126126
/// @param[in] invokerWaiting The status of the invoker waiting flag.
127127
void SetInvokerWaiting(bool invokerWaiting) { m_invokerWaiting = invokerWaiting; }
128128

129+
/// True if the destination thread's target function invoke completed without an
130+
/// exception propagating out of it. False if the target function threw (or hasn't
131+
/// been invoked yet). Distinct from the source thread's semaphore wait succeeding:
132+
/// the semaphore is always signaled on scope exit from `Invoke()`, including via an
133+
/// exception, so this flag is what lets the source thread tell the two cases apart.
134+
/// @return `true` if the target function invoke completed successfully.
135+
bool GetInvokeSucceeded() { return m_invokeSucceeded; }
136+
137+
/// Set to true by the destination thread immediately after the target function
138+
/// invoke returns without throwing.
139+
/// @param[in] invokeSucceeded The status of the invoke-succeeded flag.
140+
void SetInvokeSucceeded(bool invokeSucceeded) { m_invokeSucceeded = invokeSucceeded; }
141+
129142
private:
130143
/// An empty starting tuple
131144
std::tuple<> m_start;
@@ -141,6 +154,10 @@ class DelegateAsyncWaitMsg : public DelegateMsg
141154

142155
/// True if source thread is waiting for destination thread invoke to complete
143156
bool m_invokerWaiting = false;
157+
158+
/// True once the destination thread's target function invoke has completed
159+
/// without an exception propagating out of it
160+
bool m_invokeSucceeded = false;
144161
};
145162

146163
template <class R>
@@ -356,7 +373,10 @@ class DelegateFreeAsyncWait<RetType(Args...)> : public DelegateFree<RetType(Args
356373
// Single lock: read return value and clear InvokerWaiting atomically
357374
const dmq::LockGuard<Mutex> lock(msg->GetLock());
358375
if (waited) {
359-
m_success = true;
376+
// Only report success if the target function invoke actually completed;
377+
// the semaphore is signaled even when the target function threw, so
378+
// `waited` alone is not sufficient to know the call succeeded.
379+
m_success = msg->GetInvokeSucceeded();
360380
m_retVal = delegate->m_retVal;
361381
}
362382
// Set flag that source is not waiting anymore
@@ -419,18 +439,26 @@ class DelegateFreeAsyncWait<RetType(Args...)> : public DelegateFree<RetType(Args
419439
// Invoke the delegate function synchronously
420440
m_sync = true;
421441

442+
// Signals the source thread when this scope exits, including via an exception
443+
// propagating out of the target function invoke below. Without this, a target
444+
// function that throws would leave the source thread blocked until timeout.
445+
struct SemaSignalGuard {
446+
Semaphore& sema;
447+
~SemaSignalGuard() { sema.Signal(); }
448+
} semaSignalGuard{ delegateMsg->GetSema() };
449+
422450
// Does target function have a void return value?
423451
if constexpr (std::is_void<RetType>::value == true) {
424452
// Invoke the target function using the source thread supplied function arguments
425453
std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
426454
} else {
427-
// Invoke the target function using the source thread supplied function arguments
455+
// Invoke the target function using the source thread supplied function arguments
428456
// and get the return value
429457
m_retVal = std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
430458
}
431459

432-
// Signal the source thread that the destination thread function call is complete
433-
delegateMsg->GetSema().Signal();
460+
// Only reached if the std::apply() call above did not throw
461+
delegateMsg->SetInvokeSucceeded(true);
434462
}
435463
return true;
436464
}
@@ -452,9 +480,8 @@ class DelegateFreeAsyncWait<RetType(Args...)> : public DelegateFree<RetType(Args
452480
// Optional: If you want to trap this error in debug mode
453481
#if defined(DMQ_ASSERTS)
454482
ASSERT();
455-
#else
456-
return RetType();
457483
#endif
484+
return RetType();
458485
#else
459486
// Standard C++ behavior with Exception Handling
460487
try {
@@ -794,7 +821,10 @@ class DelegateMemberAsyncWait<TClass, RetType(Args...)> : public DelegateMember<
794821
// Single lock: read return value and clear InvokerWaiting atomically
795822
const dmq::LockGuard<Mutex> lock(msg->GetLock());
796823
if (waited) {
797-
m_success = true;
824+
// Only report success if the target function invoke actually completed;
825+
// the semaphore is signaled even when the target function threw, so
826+
// `waited` alone is not sufficient to know the call succeeded.
827+
m_success = msg->GetInvokeSucceeded();
798828
m_retVal = delegate->m_retVal;
799829
}
800830
// Set flag that source is not waiting anymore
@@ -857,18 +887,26 @@ class DelegateMemberAsyncWait<TClass, RetType(Args...)> : public DelegateMember<
857887
// Invoke the delegate function synchronously
858888
m_sync = true;
859889

890+
// Signals the source thread when this scope exits, including via an exception
891+
// propagating out of the target function invoke below. Without this, a target
892+
// function that throws would leave the source thread blocked until timeout.
893+
struct SemaSignalGuard {
894+
Semaphore& sema;
895+
~SemaSignalGuard() { sema.Signal(); }
896+
} semaSignalGuard{ delegateMsg->GetSema() };
897+
860898
// Does target function have a void return value?
861899
if constexpr (std::is_void<RetType>::value == true) {
862900
// Invoke the target function using the source thread supplied function arguments
863901
std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
864902
} else {
865-
// Invoke the target function using the source thread supplied function arguments
903+
// Invoke the target function using the source thread supplied function arguments
866904
// and get the return value
867905
m_retVal = std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
868906
}
869907

870-
// Signal the source thread that the destination thread function call is complete
871-
delegateMsg->GetSema().Signal();
908+
// Only reached if the std::apply() call above did not throw
909+
delegateMsg->SetInvokeSucceeded(true);
872910
}
873911
return true;
874912
}
@@ -890,9 +928,8 @@ class DelegateMemberAsyncWait<TClass, RetType(Args...)> : public DelegateMember<
890928
// Optional: If you want to trap this error in debug mode
891929
#if defined(DMQ_ASSERTS)
892930
ASSERT();
893-
#else
894-
return RetType();
895931
#endif
932+
return RetType();
896933
#else
897934
// Standard C++ behavior with Exception Handling
898935
try {
@@ -1149,7 +1186,10 @@ class DelegateMemberAsyncWaitSp<TClass, RetType(Args...)> : public DelegateMembe
11491186
// Single lock: read return value and clear InvokerWaiting atomically
11501187
const dmq::LockGuard<Mutex> lock(msg->GetLock());
11511188
if (waited) {
1152-
m_success = true;
1189+
// Only report success if the target function invoke actually completed;
1190+
// the semaphore is signaled even when the target function threw, so
1191+
// `waited` alone is not sufficient to know the call succeeded.
1192+
m_success = msg->GetInvokeSucceeded();
11531193
m_retVal = delegate->m_retVal;
11541194
}
11551195
// Set flag that source is not waiting anymore
@@ -1212,18 +1252,26 @@ class DelegateMemberAsyncWaitSp<TClass, RetType(Args...)> : public DelegateMembe
12121252
// Invoke the delegate function synchronously
12131253
m_sync = true;
12141254

1255+
// Signals the source thread when this scope exits, including via an exception
1256+
// propagating out of the target function invoke below. Without this, a target
1257+
// function that throws would leave the source thread blocked until timeout.
1258+
struct SemaSignalGuard {
1259+
Semaphore& sema;
1260+
~SemaSignalGuard() { sema.Signal(); }
1261+
} semaSignalGuard{ delegateMsg->GetSema() };
1262+
12151263
// Does target function have a void return value?
12161264
if constexpr (std::is_void<RetType>::value == true) {
12171265
// Invoke the target function using the source thread supplied function arguments
12181266
std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
12191267
} else {
1220-
// Invoke the target function using the source thread supplied function arguments
1268+
// Invoke the target function using the source thread supplied function arguments
12211269
// and get the return value
12221270
m_retVal = std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
12231271
}
12241272

1225-
// Signal the source thread that the destination thread function call is complete
1226-
delegateMsg->GetSema().Signal();
1273+
// Only reached if the std::apply() call above did not throw
1274+
delegateMsg->SetInvokeSucceeded(true);
12271275
}
12281276
return true;
12291277
}
@@ -1245,9 +1293,8 @@ class DelegateMemberAsyncWaitSp<TClass, RetType(Args...)> : public DelegateMembe
12451293
// Optional: If you want to trap this error in debug mode
12461294
#if defined(DMQ_ASSERTS)
12471295
ASSERT();
1248-
#else
1249-
return RetType();
12501296
#endif
1297+
return RetType();
12511298
#else
12521299
// Standard C++ behavior with Exception Handling
12531300
try {
@@ -1506,7 +1553,10 @@ class DelegateFunctionAsyncWait<RetType(Args...)> : public DelegateFunction<RetT
15061553
// Single lock: read return value and clear InvokerWaiting atomically
15071554
const dmq::LockGuard<Mutex> lock(msg->GetLock());
15081555
if (waited) {
1509-
m_success = true;
1556+
// Only report success if the target function invoke actually completed;
1557+
// the semaphore is signaled even when the target function threw, so
1558+
// `waited` alone is not sufficient to know the call succeeded.
1559+
m_success = msg->GetInvokeSucceeded();
15101560
m_retVal = delegate->m_retVal;
15111561
}
15121562
// Set flag that source is not waiting anymore
@@ -1569,18 +1619,26 @@ class DelegateFunctionAsyncWait<RetType(Args...)> : public DelegateFunction<RetT
15691619
// Invoke the delegate function synchronously
15701620
m_sync = true;
15711621

1622+
// Signals the source thread when this scope exits, including via an exception
1623+
// propagating out of the target function invoke below. Without this, a target
1624+
// function that throws would leave the source thread blocked until timeout.
1625+
struct SemaSignalGuard {
1626+
Semaphore& sema;
1627+
~SemaSignalGuard() { sema.Signal(); }
1628+
} semaSignalGuard{ delegateMsg->GetSema() };
1629+
15721630
// Does target function have a void return value?
15731631
if constexpr (std::is_void<RetType>::value == true) {
15741632
// Invoke the target function using the source thread supplied function arguments
15751633
std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
15761634
} else {
1577-
// Invoke the target function using the source thread supplied function arguments
1635+
// Invoke the target function using the source thread supplied function arguments
15781636
// and get the return value
15791637
m_retVal = std::apply(&BaseType::operator(), std::tuple_cat(std::make_tuple(this), delegateMsg->GetArgs()));
15801638
}
15811639

1582-
// Signal the source thread that the destination thread function call is complete
1583-
delegateMsg->GetSema().Signal();
1640+
// Only reached if the std::apply() call above did not throw
1641+
delegateMsg->SetInvokeSucceeded(true);
15841642
}
15851643
return true;
15861644
}
@@ -1602,9 +1660,8 @@ class DelegateFunctionAsyncWait<RetType(Args...)> : public DelegateFunction<RetT
16021660
// Optional: If you want to trap this error in debug mode
16031661
#if defined(DMQ_ASSERTS)
16041662
ASSERT();
1605-
#else
1606-
return RetType();
16071663
#endif
1664+
return RetType();
16081665
#else
16091666
// Standard C++ behavior with Exception Handling
16101667
try {

DelegateMQ/delegate/DelegateOpt.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@
7777
#error "RTTI compiler option is disabled but required by the DelegateMQ library."
7878
#endif
7979

80+
// Some OS port headers below (e.g. ThreadXMutex.h, ThreadXConditionVariable.h) use
81+
// ASSERT_TRUE in constructors. Include Fault.h here, ahead of those port headers,
82+
// so the macro is defined before first use. Fault.h is include-guarded, so the
83+
// later #include "extras/util/Fault.h" below is a harmless no-op.
84+
#include "extras/util/Fault.h"
85+
8086
#if defined(DMQ_THREAD_STDLIB) || defined(DMQ_THREAD_WIN32) || defined(DMQ_THREAD_QT)
8187
// Windows / Linux / macOS / Qt (Standard Library)
8288
#include <condition_variable>

0 commit comments

Comments
 (0)