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

Commit da3e76f

Browse files
authored
Merge pull request #54 from openweave/bug/szewczyk/timer-starvation
Ensure that expired timer processing is bounded
2 parents 5838c27 + 99f5464 commit da3e76f

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)