Question

I need a robust way of getting system uptime, and ended up using something as follows. Added some comments to help people read it. I cannot use Task's as this has to run on a .NET 3.5 application.

// This is a structure, can't be marked as volatile
// need to implement MemoryBarrier manually as appropriate
private static TimeSpan _uptime;

private static TimeSpan GetUptime()
{
    // Try and set the Uptime using per counters
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    // If our thread hasn't finished in 5 seconds, perf counters are broken
    if (!uptimeThread.Join(5 * 1000))
    {
        // Kill the thread and use Environment.TickCount
        uptimeThread.Abort();
        _uptime = TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }

    Thread.MemoryBarrier();
    return _uptime;
}

// This sets the System uptime using the perf counters
// this gives the best result but on a system with corrupt perf counters
// it can freeze
private static void GetPerformanceCounterUptime()
{
    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = TimeSpan.FromSeconds(uptime.NextValue());
    }
}

The part I am struggling with is where should Thread.MemoryBarrier() be placed? I am placing it before reading the value, but either the current thread or a different thread could have written to it. Does the above look correct?

Edit, Answer based on Daniel

This is what I eneded up implementing, thank you both for the insight.

private static TimeSpan _uptime;

private static TimeSpan GetUptime()
{
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    if (uptimeThread.Join(5*1000))
    {
        return _uptime;
    }
    else
    {
        uptimeThread.Abort();
        return TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }
}

private static void GetPerformanceCounterUptime()
{
    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = TimeSpan.FromSeconds(uptime.NextValue());
    }
}

Edit 2

Updated based on Bob's comments.

private static DateTimeOffset _uptime;

private static DateTimeOffset GetUptime()
{
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    if (uptimeThread.Join(5*1000))
    {
        return _uptime;
    }
    else
    {
        uptimeThread.Abort();
        return DateTimeOffset.Now.Subtract(TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue));
    }
}

private static void GetPerformanceCounterUptime()
{
    if (_uptime != default(DateTimeOffset))
    {
        return;
    }

    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = DateTimeOffset.Now.Subtract(
            TimeSpan.FromSeconds(uptime.NextValue()));
    }
}
Was it helpful?

Solution

Thread.Join already ensures that writes performed by the uptimeThread are visible on the main thread. You don't need any explicit memory barrier. (without the synchronization performed by Join, you'd need barriers on both threads - after the write and before the read)

However, there's a potential problem with your code: writing to a TimeSpan struct isn't atomic, and the main thread and the uptimeThread may write to it at the same time (Thread.Abort just signals abortion, but doesn't wait for the thread to finish aborting), causing a torn write. My solution would be to not use the field at all when aborting. Also, multiple concurrent calls to GetUptime() may cause the same problem, so you should use an instance field instead.

private static TimeSpan GetUptime()
{
    // Try and set the Uptime using per counters
    var helper = new Helper();
    var uptimeThread = new Thread(helper.GetPerformanceCounterUptime);
    uptimeThread.Start();

    // If our thread hasn't finished in 5 seconds, perf counters are broken
    if (uptimeThread.Join(5 * 1000))
    {
        return helper._uptime;
    } else {
        // Kill the thread and use Environment.TickCount
        uptimeThread.Abort();
        return TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }
}

class Helper
{
    internal TimeSpan _uptime;

    // This sets the System uptime using the perf counters
    // this gives the best result but on a system with corrupt perf counters
    // it can freeze
    internal void GetPerformanceCounterUptime()
    {
        using (var uptime = new PerformanceCounter("System", "System Up Time"))
        {
            uptime.NextValue();
            _uptime = TimeSpan.FromSeconds(uptime.NextValue());
        }
    }
}

However, I'm not sure if aborting the performance counter thread will work correctly at all - Thread.Abort() only aborts managed code execution. If the code is hanging within a Windows API call, the thread will keep running.

OTHER TIPS

AFAIK writes in .NET are volatile, so the only place where you would need a memory fence would be before each read, since they are subject to reordering and/or caching. To quote from a post by Joe Duffy:

For reference, here are the rules as I have come to understand them stated as simply as I can:

Rule 1: Data dependence among loads and stores is never violated.
Rule 2: All stores have release semantics, i.e. no load or store may move after one.
Rule 3: All volatile loads are acquire, i.e. no load or store may move before one.
Rule 4: No loads and stores may ever cross a full-barrier. 
Rule 5: Loads and stores to the heap may never be introduced.
Rule 6: Loads and stores may only be deleted when coalescing adjacent loads and 
stores from/to the same location.

Note that by this definition, non-volatile loads are not required to have any sort of barrier associated with them. So loads may be freely reordered, and writes may move after them (though not before, due to Rule 2). With this model, the only true case where you’d truly need the strength of a full-barrier provided by Rule 4 is to prevent reordering in the case where a store is followed by a volatile load. Without the barrier, the instructions may reorder.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top