Home .NET Release Version: debugging is just beginning…

Release Version: debugging is just beginning…

by admin

What are we talking about?

Your program is finally running, the customer is confidently poking the buttons on the test site, everything is running smoothly…but do not get excited: you may have some problems after the release version is released. The software starts crumbling with strange bugs, the customer is ready to tear you apart…
Do you think this is a problem only for C-scribes? You are wrong!

Case study

Usually you are used to putting a debug version on the testbench (this doesn’t apply to everyone, of course, but believe me, it happens very often). It saves time, and there is more debugging information, you can catch remote debugging…
It would seem that the timing of the release is of little concern to you, after all, .NET is a bit of an improvement on C++ which has some problems But all is not as cloudless as it may seem at first.
So, an example of :

    static void Main( string [] args)

  1. {
  2. System.Threading.Timer t = new System.Threading.Timer(state =>
  3. {
  4. Console WriteLine( "Timer!" );
  5. GC.Collect();
  6. }, null , 0, 1000);
  7. Console ReadKey();
  8. }
  9. * This source code was highlighted with Source Code Highlighter

    The simplest code, which according to the author’s idea should write the cherished "Timer!" into the console once per second. Debugging of the debug configured project does not reveal anything suspicious, and a release is prepared… in which the program does not work

    Flight analysis

    Yes, yes, this "bug" is a direct consequence of optimization (and, of course, a little negligence by the programmer). The negligence is that the specified System.Threading.Timer actually inherits IDisposable, and, well, we should call Dispose() method for it. So we don’t have to do that (it’s not really relevant).
    Examine the IL code listing generated in the release and debug versions and see this.
    debug :

    method private hidebysig static void Main(string[] args) cil managed
    {
    entrypoint
    maxstack 5
    locals init (
    [0] class [mscorlib]System.Threading.Timer t)

    L_0000: nop
    L_0001: ldsfld class
    [mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<> 9__CachedAnonymousMethodDelegate1

    and release :

    method private hidebysig static void Main(string[] args) cil managed
    {
    entrypoint
    maxstack 8
    L_0000: ldsfld class [mscorlib]System.Threading.TimerCallback ConsoleApplication11.Program::CS$<> 9__CachedAnonymousMethodDelegate1

    I highlighted distinction.

    For those who haven’t figured it out yet

    In the debug version, the compiler leaves the Timer reference not used anywhere (except in the declaration) in the stack, and the GC seeing that the reference is alive (until the method runs out) does nothing. Timer works.
    In the release version, however, the link is nowhere to be found and the first (not guaranteed) GC.Collect() method will destroy the timer.

    Afterword

    I sincerely hope that you will never have to fall for explicit (or not) bugs of optimizers, as in the above example, in real life. Finding such bugs (especially on customer side) is very difficult and my only recommendation is to build a debug version of all components and gradually compile the parts in the release to find the "defective" build and further analyze it. Also distributing debug information (.pdb file usually) can help.
    Have a great Friday, keep the logs 😎

    You may also like