Skip to content
This repository was archived by the owner on Dec 20, 2023. It is now read-only.

Commit 99f5464

Browse files
committed
Ensure that expired timer processing is bounded
Restructure the RTOS-based timers to handle a bounded number of expired timers per expiration of the platform timer. Previously, the `HandleExpiredTimers` method would continously evaluate the current list of timers, and keep evaluating the currently expired timers. If the timer handler code resulted in posting the timer with an expiration code in the past (e.g. a timer with expiration time of 0), the control would not be returned from the timer handling code, and the as a result, processing of other Weave events would be starved. The new approach commits to processing all timers expired at the beginning of the `HandleExpiredTimers` function; any timers created and expired as a result of the timer handlers will be processed on a subsequent invocation of the `HandleExpiredTimers`. Added a unit test to check for the condition.
1 parent b758187 commit 99f5464

File tree

2 files changed

+94
-23
lines changed

2 files changed

+94
-23
lines changed

src/system/SystemTimer.cpp

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -296,39 +296,77 @@ void Timer::HandleComplete()
296296
Error Timer::HandleExpiredTimers(Layer& aLayer)
297297
{
298298
// expire each timer in turn until an unexpired timer is reached or the timerlist is emptied.
299-
while (aLayer.mTimerList)
299+
300+
// The platform timer API has MSEC resolution so expire any timer with less than 1 msec remaining.
301+
Epoch currentEpoch = Timer::GetCurrentEpoch() + 1;
302+
303+
VerifyOrExit(aLayer.mTimerList != NULL, );
304+
305+
// Verify that there are any timers to be processed
306+
if (Timer::IsEarlierEpoch(aLayer.mTimerList->mAwakenEpoch, currentEpoch))
300307
{
301-
const Epoch kCurrentEpoch = Timer::GetCurrentEpoch();
308+
// Bound the number of timers processed to those that are already on the
309+
// expired queue. We copy the expired timers onto the queue pointed to
310+
// by headTimer, and then reset the timer queue in the system layer to
311+
// point to the first unexpired timer.
312+
Timer * headTimer = aLayer.mTimerList;
313+
Timer * cursorTimer = aLayer.mTimerList;
314+
315+
while (cursorTimer->mNextTimer != NULL &&
316+
Timer::IsEarlierEpoch(cursorTimer->mNextTimer->mAwakenEpoch, currentEpoch))
317+
{
318+
cursorTimer = cursorTimer->mNextTimer;
319+
}
302320

303-
// The platform timer API has MSEC resolution so expire any timer with less than 1 msec remaining.
304-
if (Timer::IsEarlierEpoch(aLayer.mTimerList->mAwakenEpoch, kCurrentEpoch + 1))
321+
// unexpired timer list points to the next unexpired timer
322+
aLayer.mTimerList = cursorTimer->mNextTimer;
323+
324+
// list pointed to by the headTimer terminates
325+
cursorTimer->mNextTimer = NULL;
326+
327+
aLayer.mTimerComplete = true;
328+
329+
while (headTimer != NULL)
305330
{
306-
Timer& lTimer = *aLayer.mTimerList;
307-
aLayer.mTimerList = lTimer.mNextTimer;
308-
lTimer.mNextTimer = NULL;
331+
cursorTimer = headTimer;
332+
headTimer = headTimer->mNextTimer;
333+
cursorTimer->mNextTimer = NULL;
309334

310-
aLayer.mTimerComplete = true;
311-
lTimer.HandleComplete();
312-
aLayer.mTimerComplete = false;
335+
cursorTimer->HandleComplete();
336+
}
337+
aLayer.mTimerComplete = false;
338+
}
339+
340+
if (aLayer.mTimerList)
341+
{
342+
// timers still exist so restart the platform timer.
343+
uint64_t delayMilliseconds;
344+
345+
// re-read the timer to account for the time that has elapsed while we
346+
// were processing the expired timers
347+
348+
currentEpoch = Timer::GetCurrentEpoch();
349+
350+
if (aLayer.mTimerList->mAwakenEpoch < currentEpoch)
351+
{
352+
delayMilliseconds = 0;
313353
}
314354
else
315355
{
316-
// timers still exist so restart the platform timer.
317-
const uint64_t kDelayMilliseconds = aLayer.mTimerList->mAwakenEpoch - kCurrentEpoch;
318-
319-
/*
320-
* Original kDelayMilliseconds was a 32 bit value. The only way in which this could overflow is if time went backwards
321-
* (e.g. as a result of a time adjustment from time synchronization). Verify that the timer can still be executed
322-
* (even if it is very late) and exit if that is the case. Note: if the time sync ever ends up adjusting the clock, we
323-
* should implement a method that deals with all the timers in the system.
324-
*/
325-
VerifyOrDie(kDelayMilliseconds <= UINT32_MAX);
326-
327-
aLayer.StartPlatformTimer(static_cast<uint32_t>(kDelayMilliseconds));
328-
break; // all remaining timers are still ticking.
356+
delayMilliseconds = aLayer.mTimerList->mAwakenEpoch - currentEpoch;
329357
}
358+
/*
359+
* StartPlatformTimer() accepts a 32bit value in milliseconds. Epochs are 64bit numbers. The only way in which this could
360+
* overflow is if time went backwards (e.g. as a result of a time adjustment from time synchronization). Verify that the
361+
* timer can still be executed (even if it is very late) and exit if that is the case. Note: if the time sync ever ends up
362+
* adjusting the clock, we should implement a method that deals with all the timers in the system.
363+
*/
364+
VerifyOrDie(delayMilliseconds <= UINT32_MAX);
365+
366+
aLayer.StartPlatformTimer(static_cast<uint32_t>(delayMilliseconds));
330367
}
331368

369+
exit:
332370
return WEAVE_SYSTEM_NO_ERROR;
333371
}
334372
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP

src/test-apps/TestSystemTimer.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,38 @@ static void CheckOverflow(nlTestSuite* inSuite, void* aContext)
156156
lSys.CancelTimer(HandleTimer10Success, aContext);
157157
}
158158

159+
static uint32_t sNumTimersHandled = 0;
160+
static const uint32_t MAX_NUM_TIMERS = 1000;
161+
162+
163+
void HandleGreedyTimer(Layer *aLayer, void * aState, Error aError)
164+
{
165+
TestContext& lContext = *static_cast<TestContext*>(aState);
166+
NL_TEST_ASSERT(lContext.mTestSuite, sNumTimersHandled < MAX_NUM_TIMERS);
167+
168+
if (sNumTimersHandled >= MAX_NUM_TIMERS)
169+
{
170+
return;
171+
}
172+
173+
aLayer->StartTimer(0, HandleGreedyTimer, aState);
174+
sNumTimersHandled ++;
175+
176+
}
177+
178+
static void CheckStarvation(nlTestSuite* inSuite, void* aContext)
179+
{
180+
TestContext& lContext = *static_cast<TestContext*>(aContext);
181+
Layer& lSys = *lContext.mLayer;
182+
struct timeval sleepTime;
183+
184+
lSys.StartTimer(0, HandleGreedyTimer, aContext);
185+
186+
sleepTime.tv_sec = 0;
187+
sleepTime.tv_usec = 1000; // 1 ms tick
188+
ServiceEvents(lSys, sleepTime);
189+
}
190+
159191

160192
// Test Suite
161193

@@ -165,6 +197,7 @@ static void CheckOverflow(nlTestSuite* inSuite, void* aContext)
165197
*/
166198
static const nlTest sTests[] = {
167199
NL_TEST_DEF("Timer::TestOverflow", CheckOverflow),
200+
NL_TEST_DEF("Timer::TestTimerStarvation", CheckStarvation),
168201
NL_TEST_SENTINEL()
169202
};
170203

0 commit comments

Comments
 (0)