다중(Multi) Threads 환경에서 static type을 사용하는 것에 대한 우려 Windows debugging

.." 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 을 고려한 코드의 일부 수정으로써 문제를 풀어나가는 것도 고려해 볼 수 있지 않나 본다.


덧글

댓글 입력 영역