.Net’s time traveling StopWatch
One of programming’s many ‘gotcha’ bugs is measuring elapsed time by checking the system time twice. It feels like the natural solution but creates an easy to exploit bug. The ‘current time’ on a computer is not guaranteed to be consistent over time. It may be updated by the OS (i.e. synchronization), the user, and even programs (probably malicious ones).
Example:
var startTime = DateTime.Now;
Thread.Sleep(10000); // sure hope the user doesn't change the system date during this 10s wait...
var elapsedTime = DateTime.Now - startTime;
// elapsedTime can be ANYTHING. It can even be negative!
Anyone can make this mistake. I’ve made this mistake. You’ve probably made this mistake. But more importantly… the .Net framework contains this mistake. In the System.Diagnostics.StopWatch class.
Normally StopWatch uses a high frequency timer to measure elapsed time but, when a high frequency timer is not available, it uses DateTime.UtcNow as a fallback. If your code uses StopWatch and one of your users has an old machine without a high frequency timer and they happen to change the date while a duration is being measured… Well, let’s just hope they didn’t change the year.
—
Update: Microsoft has marked this issue has Won’t Fix, at least for now.
If you happen to have an old machine without a high frequency timer, you can confirm this bug using the example code I included in the issue:
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool QueryPerformanceCounter(out long freq);
static void Main(string[] args) {
long x;
var hasHighRes = QueryPerformanceCounter(out x);
Console.WriteLine("High res timer available: " + hasHighRes);
if (hasHighRes) Console.WriteLine("Issue only appears WITHOUT high res timer");
var s = System.Diagnostics.Stopwatch.StartNew();
var starttick = Environment.TickCount;
var startdate = DateTime.UtcNow;
while (true) {
System.Threading.Thread.Sleep(1000);
unchecked {
var dtick = (uint)(Environment.TickCount - starttick);
var ddate = DateTime.UtcNow - startdate;
Console.WriteLine(String.Format(
"stopwatch: {0:0.0}s, tickcount: {1:0.0}s, datedif: {2:0.0}s",
s.Elapsed.TotalSeconds,
dtick/1000.0,
ddate.TotalSeconds));
}
}
}
Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.
Older Posts
- Eventual Exceptions vs Programming in a Minimal Functional Style
- The Mystery of Flunf
- Explain it like I’m Five: The Socialist Millionaire Problem and Secure Multi-Party Computation
- Computer Science Blows My Mind
- A visit to Execution Labs in Montréal
- Transmuting Dice, Conserving Entropy
- Rule of Thumb: Ask for the Clock
- Rule of Thumb: Use Purposefully Weakened Methods
- Rule of thumb: Preconditions Should be Checked Explicitly
- Intersecting Linked Lists Faster
- Mouse Path Smoothing for Jack Lumber
- My Bug, My Bad #2: Sunk by Float
- Repeat Yourself Differently
- Grover’s Quantum Search Algorithm
- Followup to Non-Nullable Types vs C#
- Optimizing Just in Time with Expression Trees
- When One-Way Latency Doesn’t Matter
- Determining exactly if/when/where a moving line intersected a moving point
- Emulating Actors in C# with Async/Await
- Making an immutable queue with guaranteed constant time operations
- Improving Checked Exceptions
- Perishable Collections: The Benefits of Removal-by-Lifetime
- Decoupling shared control
- Decoupling inlined UI code
- Linq to Collections: Beyond IEnumerable<T>
- Publish your .Net library as a NuGet package
- When null is not enough: an option type for C#
- Unfathomable Bugs #5: Readonly or not
- Minkowski sums: examples
- My Bug, My Bad #1: Fractal Spheres
- Working around the brittle UI Virtualization in Windows 8
- Encapsulating Angles
- Unfathomable Bugs #4: Keys that aren’t
- How would I even use a monad (in C#)?
- Useful/Interesting Methods #1: Observable.WhenEach
- Unfathomable Bugs #3: Stringing you along
- Anonymous Implementation Classes – A Design Pattern for C#
- Tasks for ActionScript 3 – Improving on Event-Driven Programming
- Minkowski sums and differences
- Non-Nullable Types vs C#: Fixing the Billion Dollar Mistake
- Unfathomable Bugs #2: Slashing Out
- Script templates and base classes
- Unity font extraction
- Abusing “Phantom Types” to Encode List Lengths Into Their Type
- Constructive Criticism of the Reactive Extensions API
- Quaternions part 3
- Quaternions part 2
- Quaternions part 1
- Unfathomable Bugs #1: You can have things! You can have things IN things! You can have …
- Coroutines – More than you want to know
- Asset Bundle Helper
- The Visual Studio goes away
- Polish
- Introducing Catalyst
