2

First, I do understand that static initializers can be called in different sequences... that the only guarantee is that they will be executed prior to the first use of any member of that class.

What I am saying is that in Release Mode of .NET 4.x that guarantee is being violated.

During auto-generated deserialization code for class X, it calls the default constructor for class X, and in that code it is accessing a static member that is not yet initialized!

This never happened with .NET 3.5; and it never happens in Debug Mode in .NET 4.x (at least not yet). But in Release Mode in .NET 4.x it happens fairly consistently.

Questions:

(1) Is there a compiler flag or setting that will turn off the optimization that .NET 4.x added that evaluates static initializers more lazily??

(2) Is there a way to setup Debug mode to do all the optimizations (except the static initializer one)?

(3) Is there some #pragma or directive that we can put in the class to cause its initializers to run less lazy?

(4) I saw one mention that static constructors might affect the lazy evaluation of static initializers, but it was unclear whether they were over-relying on static initializer ordering. Does anyone know if going through every class with static initializers and adding a static contractor will solve this problem?

(5) Any other suggestions on how to fix this? (Going back to .NET 3.5 is highly undesirable because we need to move to 64b and we do NOT want to lose Edit & Continue.)

Brian Kennedy
  • 3,499
  • 3
  • 21
  • 27
  • 1
    Would like to see an MVCE of this – Sami Kuhmonen Jan 24 '16 at 06:52
  • 1
    @Sami, an MVCE will likely be tough. So far it seems to only happen during auto-generated deserializers... which means MVCE would need client and server OR would need to be reading in a file that it needs to deserialize as its first action such that it doesn't touch the subject class's members prior to deserialization. We'll continue to give some thought to how to create a MVCE. But we need to get our release out... so, we're hoping somebody has answers to those questions way sooner than we'll be able to devise an MVCE. – Brian Kennedy Jan 24 '16 at 07:04
  • Do you have a repro that I can grab and run? – Andrew Au Jan 24 '16 at 07:12
  • @Andrew, sorry, no... since we have no control over the static initializers AND no control over the auto-generated deserialization code, we have no deterministic way to reproduce this with a small program. We'll experiment with doing that, but it may not be possible without a lot of blind trial & error of what more to stick in. – Brian Kennedy Jan 24 '16 at 07:22
  • 1
    Looks like there were some real changes from 3.5 to 4.0. See http://codeblog.jonskeet.uk/2010/01/26/type-initialization-changes-in-net-4-0/. I see the following possibilities: 1) The change surfaced a [static initializer sequencing bug](http://codeblog.jonskeet.uk/2012/04/07/type-initializer-circular-dependencies/) in your code. To find it, give all your classes explicit static constructors, which will make the ordering deterministic. 2) Maybe the change requires you to declare some static variables as `volatile`? Doubt it since it would be a .Net bug, but not impossible. – dbc Jan 24 '16 at 19:02
  • 1
    The quickest way to force the 3.5 behavior would be, in your startup code, to access the value of at least one static variable in each and every class in question. – dbc Jan 24 '16 at 19:35
  • @dbc, yes, that is what we are doing now... we can fix each problem that way. But as we continue to test, we keep running into new cases. So, it seems we need to put a call to EVERY single class that has statics in our system into our application startup code... essentially hand-coding static initialization for our whole program... hundreds of calls naming every class in our system in one place seems a hideous solution... but perhaps necessary with .NET 4.x. – Brian Kennedy Jan 25 '16 at 00:47

1 Answers1

2

Thanks for your comments above, @dbc; after following those links I finally realized the key issue:

My statement of the guarantee above is WRONG... static initializers are NOT guaranteed to be executed prior to the first use of any member of the class... rather, they are guaranteed to be executed prior to the first use of any STATIC member of the class. So, when the deserialization code calls a constructor to instantiate an instance of the class, that is NOT guaranteed trigger the static initializers.

The rule for static constructors is different... it is guaranteed to be executed prior to the first use of any constructor (the first instantiation of any object of that class). And since it is static, it will trigger all static initializers to run.

So, that is indeed the solution: add a static constructor to every class that has static initializers that need to run before the class is used. (I had read prior suggestions on using static constructors, but they never made clear that static initializers are ONLY triggered by accessing STATIC members, unlike static constructors.)

( Based on 10+ years of .NET coding, I'm pretty sure the implementation of 2.0 thru 3.5 was such that static initializers would be triggered by instantiating an instance of the class. So, I still assert its a breaking change; but the language spec would argue not, that I had buggy code for the last 10 years and just got lucky it worked. )

Brian Kennedy
  • 3,499
  • 3
  • 21
  • 27