Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static Constructor is called twice in the same appdomain?

This question is more about C# than it is about log4net (I think).

I created a custom appender and let it read a static field that was set previously by the program.

To my astonishment the static field was reinitialized and the set value dit not make it to the appender.

I fired up debugview and saw that the static constructor is called twice (!). This should not be possible in the same appdomain right? Only debugview brought this to light since VS didn't hit a second time on the breakpoint.

Note that this is not a question about avoiding the use of static variable with log4net. I'm interested in what kind of magic log4net uses to accomplish this?

Edit #1

Hello Jon, Big fan.

I isolated it further as requested. First I started blank and worked towards the target situation that exposes the error. Since I almost matched the target character by character and still didn't repro I went the other way round.

Starting from the error situation I stripped out everything that I deemed not essential till it started ... working as expected.

It seems there is some weird thing hoing on when the runtime tries to resolved the log4net assembly (as observed in debug mode)

This is what I see with debugview:

[7756] General: WARN - Failed to parse module's 'log4net' version . Exception: System.NullReferenceException: Object reference not set to an instance of an object. [7756] at DebuggerShared.Services.EventArgs.ModuleLoadedInDebuggerEventArgs..ctor(String modulePath, String moduleLoadMessage, Boolean isUserCode, String name, String version) [7756] General: WARN - Failed to parse module's 'FollowUp.Common' version . Exception: System.NullReferenceException: Object reference not set to an instance of an object. [7756] at DebuggerShared.Services.EventArgs.ModuleLoadedInDebuggerEventArgs..ctor(String modulePath, String moduleLoadMessage, Boolean isUserCode, String name, String version)

And VS shows no value for the path in the debug-module screen. Now how did I managed to come to that situation? Weird that it managed to load an assembly but can't tell anymore from where :)

Here is the isolated situation to the point that if I modify it further it starts working as expected.

https://www.sugarsync.com/pf/D6486369_1701716_00940

I'm still interested in the technical details but after removing the reference to log4net and adding it again it all started to work again. I'm happy that it works but it nags me that I don't have a thorough explanation

Also, the static constructor iscalled twice now which makes sense since the type is initializes again when log4net gets its hands on it.

I think it isn't worthwhile to spent more time on this cause I think the solution was in a weird state and to understand all this has marginal value. Still, if you can think of something to explain this I'd be glad to here.

Edit #2

It turned out that some assemblies were indeed loaded twice including the one with the static constructor. I'll investigate later how this is possible but I have a workaround by disabling and enabling Costura. Costura is a msbuild task that merges all the assemblies into one. I'm not saying that Costura is the root cause. It could easily be that the csproj/sln files were in strange state.

Thinking on how to diagnose this problem faster in the future I fired up sysinternals ProcessExplorer. Now I expected to see the assemblies being loaded only once but I found saw that they were loaded twice. Seems this is a bug in the runtime thats fixed only in .NET 4

http://forum.sysinternals.com/why-some-net-assemblies-are-duplicated-in-memory_topic15279.html https://connect.microsoft.com/VisualStudio/feedback/details/467560/clr-maps-assemblies-into-the-virtual-address-space-twice

Edit #3 Costura made the assemblies load twice. The issue was fixed on the same day by the project owner :) http://code.google.com/p/costura/issues/detail?id=17&thanks=17&ts=1328826304

We need a Costura tag but I don't have the necessary 1500 reputation points. Please create it if you have the rights. Thanks.

Kind Regards, Tom

like image 600
buckley Avatar asked Feb 08 '12 11:02

buckley


People also ask

Is static constructor called only once?

A static constructor is used to initialize any static data, or to perform a particular action that needs to be performed only once. It is called automatically before the first instance is created or any static members are referenced.

Which statement is true about static constructor?

Which among the following is true for static constructor? Explanation: Static constructors can't be parameterized constructors. Those are used to initialize the value of static members only.

Why static constructor is Parameterless in C#?

When a data member is shared among different instances it is imperative that data should be consistent among all the instances of the class. And also there is no way to call static constructor explicitly. Therefore the purpose of having a parameterized static constructor is useless.

What is static constructor in Java?

A static constructor is the piece of code used to initialize static data, which means that a particular task needs to be executed only once throughout the program. It is usually called automatically before any static members referenced or a first instance is generated.


2 Answers

It looks like you managed to load two separate instances of log4net into the same AppDomain.

One project references:

<Reference Include="log4net">
  <HintPath>..\packages\log4net.1.2.11\lib\net35-full\log4net.dll</HintPath>
</Reference>

The other:

<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>..\ExternalReferences\log4net.dll</HintPath>
</Reference>

One of them is strongly named, the other isn't, this resulted in .net giving them different identities. And the hint path differs too. Also one seems to be 1.2.10, the other 1.2.11.

try calling AppDomain.GetAssemblies() and check if log4net occurs twice.

like image 111
CodesInChaos Avatar answered Oct 23 '22 03:10

CodesInChaos


Well it could be explcitly invoking the type initializer:

var initializer = typeof(Foo).TypeInitializer;
initializer.Invoke(null);

However, I'd hope that it's not doing that. Can you come up with a short but complete program which demonstrates this happening?

like image 33
Jon Skeet Avatar answered Oct 23 '22 04:10

Jon Skeet