.." Use the static modifier to declare a static member, which belongs to the type itself rather than to a specific object. The static modifier can be used with classes, fields, methods, properties, operators, events and constructors, but cannot be used with indexers, destructors, or types other than classes. " ... http://msdn.microsoft.com/en-us/library/98f28cdx(VS.80).aspx
하지만, static keyword가 Thread Safe와는 친하지 않기 때문에 Multiple users 나 Threads 환경에서 race condition에 따라 Data가 Corruption되거나 NullReferenceException이 발생할 수도 있다.
0:031> kb
ChildEBP RetAddr Args to Child
0d4ff0d4 7923b543 e0434f4d 00000001 00000000 kernel32!RaiseException+0x53
0d4ff12c 7923b4c6 069703b0 00000000 0d4ff388 mscorsvr!RaiseTheException+0xa0
0d4ff154 7923b47a 069703b0 00000000 0d4ff398 mscorsvr!RealCOMPlusThrow+0x48
0d4ff164 79239c8c 069703b0 0000002b 00000000 mscorsvr!RealCOMPlusThrow+0xd
0d4ff398 792b718e 0000002b 00000000 00000000 mscorsvr!CreateMethodExceptionObject+0x67b
0d4ff42c 792b71b2 80004003 00000000 00000000 mscorsvr!RealCOMPlusThrowHRWorker+0xb9
0d4ff448 792b731c 80004003 00000000 00000000 mscorsvr!RealCOMPlusThrowHRWorker+0x15
0d4ff4d4 792a8552 80004003 00000000 0d4ff514 mscorsvr!RealCOMPlusThrowHR+0x168
0d4ff4e8 0268435d 00000000 00000000 80004003 mscorsvr!Interop::ThrowExceptionForHR+0x3a
WARNING: Frame IP not in any known module. Following frames may be wrong.
0d4ff660 791f6049 00000001 0d4ff69c 791f609d 0x268435d
0d4ff66c 791f609d 0cafa2d8 0695800c 00000000 mscorsvr!CTPMethodTableCallTargetHelper+0xf
0d4ff69c 791fa7f8 0cafa2d8 0695800c 00000000 mscorsvr!CTPMethodTable::CallTarget+0x4e
0d4ff6c0 791b2682 0cb06e5c 00000000 00000001 mscorsvr!CRemotingServices::CreateProxyOrObject+0x5f
0d4ff730 791b273c 0cb06e5c 0caff013 00000000 mscorsvr!AppDomain::GetOffsetOfSlotsCount+0xa0
0d4ff74c 791b7f92 0d4ff864 791bdd4e 0d4ff7a0 mscorsvr!JIT_NewCrossContext+0x3a
0d4ff754 791bdd4e 0d4ff7a0 00000000 0d4ff778 mscorsvr!CallDescrWorker+0x30
0d4ff864 791d5bd5 00b07f53 026e8a10 024547a0 mscorsvr!MethodDesc::CallDescr+0x1b8
0d4ff88c 79236659 0d4ff8a8 024547a0 0d4ffc8c mscorsvr!MethodDesc::Call+0x8e
0d4ff8b8 792cec0a 069700c8 0d4ff934 02683c9a mscorsvr!CallDefaultConstructor+0x10c
0d4ff8c4 02683c9a 0d4ff8d0 069700c8 0cafec57 mscorsvr!CRemotingServices::CallDefaultCtor+0xd
0:031> !do 069703b0
Name: System.NullReferenceException
0:031> !dumpstack -EE
Thread 31
Current frame:
ChildEBP RetAddr Caller,Callee
0d4ff51c 0cafd1f5 (MethodDesc 0xcb03410 +0x255 System.EnterpriseServices.Thunk.Proxy.CoCreateObject)
0d4ff548 0cafd107 (MethodDesc 0xcb03410 +0x167 System.EnterpriseServices.Thunk.Proxy.CoCreateObject)
0d4ff600 0cafc0e4 (MethodDesc 0x2643ea0 +0x64 System.Runtime.Remoting.RemotingConfigHandler.IsRemotelyActivatedClientType)
0d4ff608 0cafc6df (MethodDesc 0xcb037d0 +0x1f System.EnterpriseServices.ServicedComponentInfo.SCICachedLookup)
0d4ff610 0cafa8a3 (MethodDesc 0xcb015c8 +0xb3 System.EnterpriseServices.ServicedComponentProxyAttribute.CreateInstance)
0d4ff654 0cafa320 (MethodDesc 0x24fe618 +0x48 System.Runtime.Remoting.Activation.ActivationServices.IsCurrentContextOK)
0d4ff6f0 0caff8f6 (MethodDesc 0xcb07600 +0x6e System.EnterpriseServices.RWHashTableEx.Put)
0d4ff738 0caff013 (MethodDesc 0xcb01710 +0x2b ttt.obj.objMTS..ctor)
0d4ff748 0d50071e (MethodDesc 0xcb07f58 +0x6 ttt.objMain.obj_11..ctor)
0d4ff8d0 0cafec57 (MethodDesc 0x21c9240 +0x7f System.Runtime.Remoting.Proxies.RealProxy.InitializeServerObject)
0d4ff8f4 0cafec95 (MethodDesc 0x21c9240 +0xbd System.Runtime.Remoting.Proxies.RealProxy.InitializeServerObject)
0d4ff920 0cafe77d (MethodDesc 0xcb02e88 +0x85 System.EnterpriseServices.ServicedComponentProxy.SetupContext)
0d4ff934 0cafe683 (MethodDesc 0xcb02d28 +0x1b System.EnterpriseServices.ServicedComponentProxy.ConstructServer)
0d4ff940 0cafe441 (MethodDesc 0xcb02bc8 +0x141 System.EnterpriseServices.ServicedComponentProxy..ctor)
0d4ff96c 0cafe0f2 (MethodDesc 0xcb015f8 +0x8a System.EnterpriseServices.ServicedComponentProxyAttribute.System.Runtime.InteropServices.ICustomFactory.CreateInstance)
0d4ff994 0cafdda3 (MethodDesc 0x24fe628 +0x4b System.Runtime.Remoting.Activation.ActivationServices.CreateObjectForCom)
0d4ffc74 0cafdd24 (MethodDesc 0xcb034a0 +0x6c System.EnterpriseServices.Thunk.Proxy.CallFunction)
0d4ffc98 0cafdd24 (MethodDesc 0xcb034a0 +0x6c System.EnterpriseServices.Thunk.Proxy.CallFunction)
0d4ffcc0 0cafdc87 (MethodDesc 0xcb05a28 +0x67 System.EnterpriseServices.Internal.AppDomainHelper.System.EnterpriseServices.Internal.IAppDomainHelper.DoCallback)
상위 Callstack은 NullReferenceException이 발생한 것을 보여주는 데, ttt.obj.objMTS..ctor Constructor에서의 NulLReferenceException이므로, ttt.obj.objMTS 라는 Class의 초기화에서 NullReferenceException이 발생한 것을 알 수 있다. 하지만, 해당 Dump에서는 몇몇의 Thread가 상위의 NullReferenceException이 발생한 Class의 Constructor와 관련이 있다는 것이다. 아래의 callstack은 상위의 NullReferenceException이 발생한 Constructor의 진행과정을 보여주고 있다. 이게 무엇을 의미하는 가.
0:030> kb
ChildEBP RetAddr Args to Child
0d48f0bc 7943b437 024327c0 02430010 02431e5c mscorjit!Compiler::fgSetStmtSeq+0x38
0d48f0d0 7943b466 02431e5c 0d48f328 02430010 mscorjit!Compiler::fgSetBlockOrder+0x17
0d48f0e0 794311b5 00000000 02430010 0d48f13c mscorjit!Compiler::fgSetBlockOrder+0x26
0d48f0f0 79431581 0d48f204 0d48f380 0d48f1f8 mscorjit!Compiler::compCompile+0xa4
0d48f13c 79431622 00130628 0d48f2dc 0d48f328 mscorjit!Compiler::compCompile+0x1e8
0d48f1c4 794316ac 0d48f2dc 0d48f328 0d48f204 mscorjit!jitNativeCode+0x95
0d48f208 791bfd04 79479124 0d48f2dc 0d48f328 mscorjit!CILJit::compileMethod+0xa2
0d48f250 791b91f4 000e38a0 0d48f2dc 0d48f328 mscorsvr!CallCompileMethodWithSEHWrapper+0x52
0d48f3b8 791b95bf 0cb0bab0 0d48f580 0d48f4bc mscorsvr!JITFunction+0x2c7
0d48f4ec 791b4428 0cb0bab0 0d48f580 00000000 mscorsvr!MakeJitWorker+0x2c0
0d48f5d8 791b453f 00000000 0d48f8ec 0d48f614 mscorsvr!MethodDesc::DoPrestub+0x4d3
0d48f5f0 02102f76 0d48f614 00000000 029203b0 mscorsvr!PreStubWorker+0x42
WARNING: Frame IP not in any known module. Following frames may be wrong.
00000000 00000000 00000000 00000000 00000000 0x2102f76
0:030> !dumpstack -EE
Thread 30
Current frame:
ChildEBP RetAddr Caller,Callee
0d48f61c 0d509747 (MethodDesc 0xcb0baa0 +0x17 System.Xml.XPath.XPathParser.ParseAdditiveExpr)
0d48f634 0d50968f (MethodDesc 0xcb0ba90 +0x17 System.Xml.XPath.XPathParser.ParseRelationalExpr)
0d48f64c 0d509537 (MethodDesc 0xcb0ba80 +0x17 System.Xml.XPath.XPathParser.ParseEqualityExpr)
0d48f664 0d509332 (MethodDesc 0xcb0ba70 +0x12 System.Xml.XPath.XPathParser.ParseAndExpr)
0d48f678 0d5092ba (MethodDesc 0xcb0ba60 +0x12 System.Xml.XPath.XPathParser.ParseOrExpr)
0d48f68c 0d5086d0 (MethodDesc 0xcb0ba30 +0x38 System.Xml.XPath.XPathParser.ParseXPathExpresion)
0d48f698 0d507738 (MethodDesc 0xcb0a3d8 +0x38 System.Xml.XPath.XPathNavigator.Compile)
0d48f6b0 0d5076df (MethodDesc 0xcb0a458 +0x1f System.Xml.XPath.XPathNavigator.Select)
0d48f6c0 0d506e6c (MethodDesc 0xc8609e8 +0x24 System.Xml.XmlNode.SelectSingleNode)
0d48f6e4 0d500391 (MethodDesc 0xcb06a98 +0x79 aaa.ClassMain.Config.xmlLoad)
0d48f728 0caff1d1 (MethodDesc 0xcb06a38 +0x79 aaa.ClassMain.Config..ctor)
0d48f730 0caff0d0 (MethodDesc 0xcb06a58 +0x28 aaa.ClassMain.Config.GetConfig)
0d48f738 0cafeff9 (MethodDesc 0xcb01710 +0x11 ttt.obj.objMTS..ctor)
0d48f748 0caff08e (MethodDesc 0xcb01820 +0x6 ttt.objMain.obj_Comp..ctor)
0d48f8d0 0cafec57 (MethodDesc 0x21c9240 +0x7f System.Runtime.Remoting.Proxies.RealProxy.InitializeServerObject)
0d48f8f4 0cafec95 (MethodDesc 0x21c9240 +0xbd System.Runtime.Remoting.Proxies.RealProxy.InitializeServerObject)
0d48f920 0cafe77d (MethodDesc 0xcb02e88 +0x85 System.EnterpriseServices.ServicedComponentProxy.SetupContext)
0d48f934 0cafe683 (MethodDesc 0xcb02d28 +0x1b System.EnterpriseServices.ServicedComponentProxy.ConstructServer)
0d48f940 0cafe441 (MethodDesc 0xcb02bc8 +0x141 System.EnterpriseServices.ServicedComponentProxy..ctor)
0d48f96c 0cafe0f2 (MethodDesc 0xcb015f8 +0x8a System.EnterpriseServices.ServicedComponentProxyAttribute.System.Runtime.InteropServices.ICustomFactory.CreateInstance)
0d48f994 0cafdda3 (MethodDesc 0x24fe628 +0x4b System.Runtime.Remoting.Activation.ActivationServices.CreateObjectForCom)
0d48fc74 0cafdd24 (MethodDesc 0xcb034a0 +0x6c System.EnterpriseServices.Thunk.Proxy.CallFunction)
0d48fc98 0cafdd24 (MethodDesc 0xcb034a0 +0x6c System.EnterpriseServices.Thunk.Proxy.CallFunction)
0d48fcc0 0cafdc87 (MethodDesc 0xcb05a28 +0x67 System.EnterpriseServices.Internal.AppDomainHelper.System.EnterpriseServices.Internal.IAppDomainHelper.DoCallback)
Callstack만 봐서는 문제가 없어 보일 수 있다. 단지, ttt.obj.objMTS..ctor 가 호출되는 2개의 Threads를 본 것이기 때문이다. 하지만, 여기서 ttt.obj.objMTS class의 Constructor안에서 호출되는 aaa.ClassMain.Config.GetConfig function을 들여다 보면, 다음과 같다.
.method public hidebysig static class aaa.ClassMain.Config.GetConfig
GetConfig() cil managed
{
// Code size 23 (0x17)
.maxstack 1
IL_0000: ldsfld class aaa.ClassMain.Config aaa.ClassMain.Config::self
IL_0005: brtrue.s IL_0011
IL_0007: newobj instance void aaa.ClassMain.Config::.ctor()
IL_000c: stsfld class aaa.ClassMain.Config aaa.ClassMain.Config::self
IL_0011: ldsfld class aaa.ClassMain.Config aaa.ClassMain.Config::self
IL_0016: ret
} // end of method Config::GetConfig
.field private static class aaa.ClassMain.Config self
즉, aaa.ClassMain.Config.GetConfig 는 static function이며, 그 보다, 그 안에서 Initialize 되는 aaa.ClassMain.Config Class는 self 라는 static variable 로 assign 이 된다. 물론, 예상대로 aaa.ClassMain.Config 는 Config File의 XML data를 load 하여 parsing 및 저장하여 가지고 있는 개체이다. 하지만, 이러한 data 들은 결코 Thread-safe 하지 않는 상태로 Multiple Threads에 의해서 접근이 가능하다는 것이며, 이러한 data의 초기화 시점에 race condition이 발생한다는 데 있다. 상위의 Callstack (System.EnterpriseServices.Thunk.Proxy.CoCreateObject )을 보면, 예상할 수 있겠지만, 이는 COM+ 혹은 Enterprise Services Application Call이며, Object Pooling과 같은 구조의 Component 초기화는 그러한 위험성을 내포하고 있다.
이와 같은 상황에 대해서 문서 http://support.microsoft.com/kb/893666/en-us " Troubleshooting ASP.NET applications with the use of static keywords" 에서는 여러 가지 우려한 시나리오를 언급하고 있다. 이것도 그 중 하나일 것이다.
상위의 문제를 해결하기 위해서는 우선적으로 Thread-Safe 하게 접근하는 것이 우선적일 것이다. Static Keyword 를 사용하지 않는 것도 필요하다면 고려할 수 있지만, Data의 Locking이나 Synchronization 을 고려한 코드의 일부 수정으로써 문제를 풀어나가는 것도 고려해 볼 수 있지 않나 본다.
덧글