@@ -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+
129142private:
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
146163template <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 {
0 commit comments