[coap] ensure correct retransmission timer scheduling (#11348)

This commit introduces `CoapBase::ScheduleRetransmissionTimer()`, a
new method that calculates the next retransmission timer fire time
based on all queued messages in `mPendingRequests` and then
schedules (starts or stops) the timer.

This method is now used whenever `mPendingRequests` is updated (a new
message is added or an existing message is removed). It is also used
in `HandleRetransmissionTimer()`, the timer's callback. This change
centralizes timer scheduling, simplifying the logic.

This also addresses an issue in the existing code where
`HandleRetransmissionTimer()` iterated over `mPendingRequests` to
determine expired messages and calculate the next fire time. However,
finalizing an expired message could trigger its `ResponseHandler`
callback, from which the caller may start or abort CoAP message tx
and  modify `mPendingRequests` and reschedule the timer, leading to
incorrect fire time calculations (`NextFireTime` can be incorrect
which would then improperly re-schedules the timer, stop it or
schedule it to a later time).

With this change, `HandleRetransmissionTimer()` first finalizes
expired messages (potentially invoking callbacks) and then calls
`ScheduleRetransmissionTimer()` to calculate the next fire time
based on the updated `mPendingRequests`, ensuring accurate timer
scheduling.
This commit is contained in:
Abtin Keshavarzian
2025-03-18 14:22:17 -05:00
committed by GitHub
parent d095eb3869
commit 21ba5bbea8
2 changed files with 33 additions and 19 deletions
+32 -19
View File
@@ -448,6 +448,31 @@ exit:
return error;
}
void CoapBase::ScheduleRetransmissionTimer(void)
{
NextFireTime nextTime;
Metadata metadata;
for (const Message &message : mPendingRequests)
{
metadata.ReadFrom(message);
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (message.IsRequest() && metadata.mObserve && metadata.mAcknowledged)
{
// This is an RFC7641 subscription which is already acknowledged.
// We do not time it out, so skip it when determining the next
// fire time.
continue;
}
#endif
nextTime.UpdateIfEarlier(metadata.mNextTimerShot);
}
mRetransmissionTimer.FireAt(nextTime);
}
void CoapBase::HandleRetransmissionTimer(Timer &aTimer)
{
static_cast<Coap *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleRetransmissionTimer();
@@ -455,7 +480,7 @@ void CoapBase::HandleRetransmissionTimer(Timer &aTimer)
void CoapBase::HandleRetransmissionTimer(void)
{
NextFireTime nextTime;
TimeMilli now = TimerMilli::GetNow();
Metadata metadata;
Ip6::MessageInfo messageInfo;
@@ -463,7 +488,7 @@ void CoapBase::HandleRetransmissionTimer(void)
{
metadata.ReadFrom(message);
if (nextTime.GetNow() >= metadata.mNextTimerShot)
if (now >= metadata.mNextTimerShot)
{
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (message.IsRequest() && metadata.mObserve && metadata.mAcknowledged)
@@ -483,7 +508,7 @@ void CoapBase::HandleRetransmissionTimer(void)
// Increment retransmission counter and timer.
metadata.mRetransmissionsRemaining--;
metadata.mRetransmissionTimeout *= 2;
metadata.mNextTimerShot = nextTime.GetNow() + metadata.mRetransmissionTimeout;
metadata.mNextTimerShot = now + metadata.mRetransmissionTimeout;
metadata.UpdateIn(message);
// Retransmit
@@ -501,11 +526,9 @@ void CoapBase::HandleRetransmissionTimer(void)
SendCopy(message, messageInfo);
}
}
nextTime.UpdateIfEarlier(metadata.mNextTimerShot);
}
mRetransmissionTimer.FireAt(nextTime);
ScheduleRetransmissionTimer();
}
void CoapBase::FinalizeCoapTransaction(Message &aRequest,
@@ -550,9 +573,8 @@ Message *CoapBase::CopyAndEnqueueMessage(const Message &aMessage, uint16_t aCopy
SuccessOrExit(error = aMetadata.AppendTo(*messageCopy));
mRetransmissionTimer.FireAtIfEarlier(aMetadata.mNextTimerShot);
mPendingRequests.Enqueue(*messageCopy);
ScheduleRetransmissionTimer();
exit:
FreeAndNullMessageOnError(messageCopy, error);
@@ -561,17 +583,8 @@ exit:
void CoapBase::DequeueMessage(Message &aMessage)
{
mPendingRequests.Dequeue(aMessage);
if (mRetransmissionTimer.IsRunning() && (mPendingRequests.GetHead() == nullptr))
{
mRetransmissionTimer.Stop();
}
aMessage.Free();
// No need to worry that the earliest pending message was removed -
// the timer would just shoot earlier and then it'd be setup again.
mPendingRequests.DequeueAndFree(aMessage);
ScheduleRetransmissionTimer();
}
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+1
View File
@@ -821,6 +821,7 @@ private:
Message *InitMessage(Message *aMessage, Type aType, Uri aUri);
Message *InitResponse(Message *aMessage, const Message &aRequest);
void ScheduleRetransmissionTimer(void);
static void HandleRetransmissionTimer(Timer &aTimer);
void HandleRetransmissionTimer(void);