views:

1307

answers:

8

If a thread is doing something like this:

const DWORD interval = 20000;
DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    if( GetTickCount() - ticks > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

Eventually, ticks is going to wrap when the value doesn't fit in a DWORD.

I've been discussing this with a colleague. One of us believes the code will still behave 'nicely' when the wrap occurs, because the subtraction operation will also wrap. The other of us, believes it won't always work, especially if the interval is large.

Who's right, and why?

Thanks.

+7  A: 

From the docs:

The elapsed time is stored as a DWORD value. Therefore, the time will wrap around to zero if the system is run continuously for 49.7 days. To avoid this problem, use GetTickCount64. Otherwise, check for an overflow condition when comparing times.

However, DWORD is unsigned - so you should be okay. 0 - "very big number" = "small number" (assuming you don't have any overflow checking active, of course). I had a previous edit which suggested you'd get a negative number, but that was before I took into account that DWORD is unsigned.

You'll still have a problem if the operation takes just under 49.7 days though. That may not be an issue for you ;)

One way to test would be to stub out the GetTickCount() method so you could write unit tests where you explicitly make it wrap. Then again, if you're really only doubting the arithmetic part, you can easily write unit tests for that :) Really, the fact that the number is coming from a system clock is pretty much irrelevant so long as you know the behaviour when it wraps - and that's specified in the documentation.

Jon Skeet
+2  A: 

I ran across that problem recently. The code I was working on used GetTickCount() in a bunch of places to determine if the program was spending too much time on a particular task and if so it would time-out that task and re-schedule it for later execution. What would happen is that if the GetTickCount() wrapped during one of the measurement periods, it would cause the code to time-out prematurely. This was a service which runs constantly, so every 49 days, it would have a slight hiccup.

I fixed it by writing a timer class which used GetTickCount() internally but detected when the value wrapped and compensated for it.

Ferruccio
A: 

Surely You need to handle this tick wrap problem.

Linux kernel handles such tick wrap problem with following trick:

#define time_after(a,b) ((long)(b) - (long)(a) < 0))

The idea is cast unsigned to signed and compare their value, then only if the |a-b|<2^30, then the wrap does not influence the result.

You can have a try with this trick and get learn why it works.

Since DWORD is also unsigned int, this trick should also works for windows.

So you code could be sth like:

const DWORD interval = 20000;

DWORD ticks = GetTickCount() + interval;

while(true) {

DoTasksThatTakeVariableTime();

timeout = GetTickCount() + interval;
if(time_after(ticks, GetTickCount())
{
    DoIntervalTasks();
    ticks = GetTickCount() + interval;
}

}

Only if interval less than 0x2^30, it works.

arsane
+1  A: 

You could test it ;) - I have a simple test application here which will launch an app and hook GetTickCount() in it so that you can control it from the GUI of the test app. I wrote it as stubbing out the GetTickCount() calls in some apps isn't that easy.

TickShifter is free and is available here: http://www.lenholgate.com/archives/000648.html

I wrote it whilst writing a series of articles on Test Driven Development which used some code that used GetTickCount() in a broken way.

Articles are available here in case you're interested: http://www.lenholgate.com/archives/000306.html

however, in summary, your code will work...

Len Holgate
+2  A: 

If you want to test what happens when GetTickCount() wraps, you could enable Application Verifier's TimeRollOver test.

From Using Application Verifier Within Your Software Development Lifecycle:

TimeRollOver forces the GetTickCount and TimeGetTime APIs to roll over faster than they normally would. This allows applications to test their handling of time rollover more easily.

bk1e
A: 

I would suggest calculating the actual elapsed period between the two ticks, not relying on the compiler to handle it for you:

const DWORD interval = 20000;

#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))

DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    DWORD curticks = GetTickCount();
    if( TICKS_DIFF(ticks, curticks) > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}
Remy Lebeau - TeamB
A: 

A big bang occurs. Oh, sorry, a big bang wraps.

botismarius
I thought a big bang got to the limit, and then went back the other way again.
Scott Langham
A: 

This article helped me, but I think there is a bug in:

define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))

When I tested this at the wrap around point, I found it was off by 1.

What worked for me was: define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur)+1)

Dr.C
Or have you tried the simpler equivalent: TICKS_DIFF(prev,cur) ((cur)-(prev)) It works even with wrap around.
Scott Langham