@@ -2191,4 +2191,74 @@ DEFINE_TEST_CLASS(TaskQueueTests)
21912191 {
21922192 }
21932193 }
2194+
2195+ DEFINE_TEST_CASE (VerifyStaleDelayedCallbackDoesNotEarlyPromoteNextPendingEntry)
2196+ {
2197+ struct CallbackState
2198+ {
2199+ std::atomic<bool > invoked{ false };
2200+ std::atomic<bool > canceled{ false };
2201+ std::atomic<uint64_t > firedAtTicks{ 0 };
2202+
2203+ static void CALLBACK Invoke (void * ctx, bool canceled)
2204+ {
2205+ auto * self = static_cast <CallbackState*>(ctx);
2206+ self->canceled .store (canceled, std::memory_order_release);
2207+ self->firedAtTicks .store (GetTickCount64 (), std::memory_order_release);
2208+ self->invoked .store (true , std::memory_order_release);
2209+ }
2210+ };
2211+
2212+ AutoQueueHandle queue;
2213+ VERIFY_SUCCEEDED (XTaskQueueCreate (
2214+ XTaskQueueDispatchMode::Manual,
2215+ XTaskQueueDispatchMode::Manual,
2216+ &queue));
2217+
2218+ CallbackState firstState;
2219+ CallbackState secondState;
2220+
2221+ constexpr uint32_t firstDelayMs = 10 ;
2222+ constexpr uint32_t secondDelayMs = 1000 ;
2223+ const uint64_t submittedAt = GetTickCount64 ();
2224+
2225+ VERIFY_SUCCEEDED (XTaskQueueSubmitDelayedCallback (
2226+ queue,
2227+ XTaskQueuePort::Work,
2228+ firstDelayMs,
2229+ &firstState,
2230+ &CallbackState::Invoke));
2231+
2232+ VERIFY_SUCCEEDED (XTaskQueueSubmitDelayedCallback (
2233+ queue,
2234+ XTaskQueuePort::Work,
2235+ secondDelayMs,
2236+ &secondState,
2237+ &CallbackState::Invoke));
2238+
2239+ // Dispatch the first callback. The timer callback that promoted it has
2240+ // already re-armed the shared delayed-callback timer for secondState.
2241+ VERIFY_IS_TRUE (XTaskQueueDispatch (queue, XTaskQueuePort::Work, 5000 ));
2242+
2243+ VERIFY_IS_TRUE (firstState.invoked .load (std::memory_order_acquire));
2244+ VERIFY_IS_FALSE (firstState.canceled .load (std::memory_order_acquire));
2245+ VERIFY_IS_FALSE (secondState.invoked .load (std::memory_order_acquire));
2246+ VERIFY_IS_LESS_THAN (GetTickCount64 () - submittedAt, static_cast <uint64_t >(500 ));
2247+
2248+ // Simulate a stale delayed-callback notification that was already
2249+ // queued before the timer was re-armed for secondState. This must not
2250+ // promote the later pending entry before its own deadline.
2251+ VERIFY_SUCCEEDED (XTaskQueueSubmitPendingCallbackForTests (queue, XTaskQueuePort::Work));
2252+
2253+ VERIFY_IS_FALSE (XTaskQueueDispatch (queue, XTaskQueuePort::Work, 0 ));
2254+ VERIFY_IS_FALSE (XTaskQueueDispatch (queue, XTaskQueuePort::Work, 200 ));
2255+ VERIFY_IS_FALSE (secondState.invoked .load (std::memory_order_acquire));
2256+
2257+ VERIFY_IS_TRUE (XTaskQueueDispatch (queue, XTaskQueuePort::Work, 2000 ));
2258+ VERIFY_IS_TRUE (secondState.invoked .load (std::memory_order_acquire));
2259+ VERIFY_IS_FALSE (secondState.canceled .load (std::memory_order_acquire));
2260+ VERIFY_IS_GREATER_THAN_OR_EQUAL (
2261+ secondState.firedAtTicks .load (std::memory_order_acquire) - submittedAt,
2262+ static_cast <uint64_t >(600 ));
2263+ }
21942264};
0 commit comments