开发者

How can this Ambient Context become null?

Can anyone help me explain how TimeProvider.Current can become null in the following class?

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
        get { return TimeProvider.current; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            TimeProvider.current = value;
        }
    }

    public abstract DateTime UtcNow { get; }

    public static void ResetToDefault()
    {
        TimeProvider.current = DefaultTimeProvider.Instance;
    }
}
开发者_Go百科

Observations

  • All unit tests that directly reference TimeProvider also invokes ResetToDefault() in their Fixture Teardown.
  • There is no multithreaded code involved.
  • Once in a while, one of the unit tests fail because TimeProvider.Current is null (NullReferenceException is thrown).
  • This only happens when I run the entire suite, but not when I just run a single unit test, suggesting to me that there is some subtle test interdependence going on.
  • It happens approximately once every five or six test runs.
  • When a failure occurs, it seems to be occuring in the first executed tests that involves TimeProvider.Current.
  • More than one test can fail, but only one fails in a given test run.

FWIW, here's the DefaultTimeProvider class as well:

public class DefaultTimeProvider : TimeProvider
{
    private readonly static DefaultTimeProvider instance =
        new DefaultTimeProvider();

    private DefaultTimeProvider() { }

    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }

    public static DefaultTimeProvider Instance
    {
        get { return DefaultTimeProvider.instance; }
    }
}

I suspect that there's some subtle interplay going on with static initialization where the runtime is actually allowed to access TimeProvider.Current before all static initialization has finished, but I can't quite put my finger on it.

Any help is appreciated.


FWIW I just threw

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

in the getter, and it consistently reports the same ID for all test cases in a test run, so the issue seems not related to threading.


Based solely on this code, Current could be null based on it being set to null. This obviously isn't helpful to you.

Could you provide the code for the tests? If there's a test interdependence, it would be helpful for readers in order to provide any feedback.

In the mean time, possibly Jon Skeet's article on singletons might be helpful, since DefaultTimeProvider is effectively acting as a singleton: http://csharpindepth.com/Articles/General/Singleton.aspx


I may have a partial answer to this, thanks to the links provided by Peter Ritchie, although I can't fully explain what's going on. It would seem that there was some kind of race going on between static initilization of TimeProvider and DefaultTimeProvider. It may have to do with beforefieldinit.

Changing the implementation seems to have resolved the issue. If not, it has certainly made the race condition much rarer, to a point where I have yet to see it.

I changed initialization of TimeProvider to this:

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    //...
}

And DefaultTimeProvider simply to this:

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}

There is now only one static initializer in play (TimeProvider) and since it's an explicit static constructor the class is not marked beforefieldinit.

This seems to have done the trick...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜