tags:

views:

78

answers:

3

According to the documentation the maximum dueTime value is 4294967294 milliseconds. But if you set the timer to this value it fires a callback immediately.

Run the below program and click "c" - the callback will fire immediately, click "o", it will fire after 49 days, click "v" it will fire after 1s. Is this a bug or am I doing something wrong?

using System;
using System.Threading;

namespace Test
{
    class Program
    {
        private static bool isRunning;
        private static Timer t;

        static void Main(string[] args)
        {
            t = new Timer(OnTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
            isRunning = true;
            while (isRunning)
            {
                var command = Console.ReadKey();

                switch (command.KeyChar)
                {
                    case 'q':
                        isRunning = false;
                        break;
                    case 'c':
                        t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
                        break;
                    case 'o':
                        t.Change(TimeSpan.FromMilliseconds(4294000000), TimeSpan.FromMilliseconds(-1));
                        break;
                    case 'v':
                        t.Change(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(-1));
                        break;
                }
            }
            Environment.Exit(0);
        }

        private static void OnTimerCallback(object x)
        {
            Console.WriteLine("Timer callback");
            t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
        }
    }
}

UPDATE

If you set timer to the max dueTime value in a callback it will not fire immediatelly and will work as expected :)

+1  A: 

It is actually a funny problem. I tried to look at the method in a reflector but ended up in a native internal call.

But is there a reason why you want to do it? According to the documentation you can change dueTime to -1 or Timeout.Infinite to disable it as you also did in the constructor.

You can also use:

t.Change(4294967294u, -1u);

instead of

t.Change(TimeSpan.FromMilliseconds(4294967294), TimeSpan.FromMilliseconds(-1));
lasseespeholt
I don't want to disable it, it has to fire after a few days, I want to figure out what is the limit.
Al Bundy
Maybe you should use the operating systems scheduler instead if you wants so many days. Otherwise you could set the timer to 24 hours and then increment a counter to show how many days you have waited.
lasseespeholt
yes I'm resetting timer each 24 hours, but still interesting to know timer limits :)
Al Bundy
+1  A: 

According to the documentation the maximum dueTime value is 4294967294 milliseconds.

4294967295 being UIn32.MaxValue, so 4294967294 is bitwise converted to an int would be -2.

But I can't see anything in the documentation that explicitly says this value is OK. 0 and -1 are listed.

[See comments: this just converts to UInt32] Have you considered using the long overload of Timer.Change, this will give a larger range?

Additional: Checking out the documentation of SetWaitableTimer (and I understand that System.Threading.Timer is built on Win32 Waitable Timers) I see, for the due time parameter:

Negative values indicate relative time.

Thus -2 is likely to mean fire 200ns from now, which is essentially immediately.

Richard
the long/uint versions does not work either.
lasseespeholt
And internally, `long` is converted to `uint` in the `Change` method.
lasseespeholt
@lasseespeholt I've updated with some Win32 info, which should explain the behaviour you are seeing.
Richard
check my update, the next time you set the timer within the callback it works fine.
Al Bundy
hhm I'm not convinced yet. Can you explain why `Change(4294967294L,-1L)` not work? If the conversion looks like `long->uint->int` then the documentation have serious lacks.
lasseespeholt
@Al Bundy nice, then it is not the bitwise conversion which bugs it.
lasseespeholt
+1  A: 

This looks like a race in the CLR's implementation of the timer. I need to wave my hands at the exact cause, I don't really understand how it can go wrong. At least from looking at the SSCLI20 source code (clr\src\vm\win32threadpool.cpp), there's a good chance that this is no longer accurate with the currently shipping code.

I can repro the problem easily by setting a breakpoint at the Change() call but not when I let the code run without a break. It behaves like it calculates the due time from the last observed value returned by GetTickCount() instead of the current value. Adding 0xfffffffe to that stale value then calculates a time that already expired. The tick count only has 32-bits of resolution. I think the real race is in the calculation of how long to SleepEx() to wait for the next time due event, but that's a guess.

It is possible that this race can occur also if you don't use the debugger to pause the timer thread. Although it would probably be rare. I'd have to recommend you stay away from any value close to 0xfffffffe to be sure this won't happen. No hardship there, sleeping for 24 days is not less efficient than sleeping for 49 days :) Implement longer intervals by counting sleep periods yourself.

Hans Passant