Enterprise Services에서의 JIT 설정 관련 정리되지 않은 기술

 만일, .NET기반의 Enterprise Services 개발에 고려하고 있다면, 문서 Understanding Enterprise Services (COM+) in .NET 코드 개발의 Step by Step 절차 뿐만이 아니라 내부 구조에 대해서 자세히 설명해 준다는 점에서 개발 뿐만아니라 추후 troubleshooting이나 디버깅시에 도움이 되는 좋은 자료가 것이다. 특히 메모리 덤프기반의 debugging 하다보면, Activation이나 메소드 호출시의 remoting 대한 코드나 tp(TranspaentProxy) , scp(ServicedComponentsProxy) 같은 proxy오브젝트를 있는 , 이에 대한 궁금증 역시 아래의 그림과 같이 Activation Path 통해 해결이 된다.

 

물론, COM+ Catalog정보로써 COM관련 DLL정보인 inprocserver32의정보에 mscoree.dll 일괄적으로 채워져있다는 부분에 있어서도 크게 놀라지 않게 된다.

사실 언급하고 싶은 내용은 JITA(Just-In-Time Activation) 대해서이다. 이유는 COM+사용하는 이유가 천차만별이겠지만, Transaction 위해서 사용하지 않는 다면, 굳이 필요가 있을 하는 것이 개인적인 생각이다. 특히 TransactionRequired 같은 설정은 JITA default enable하는 사실을 적이 있다. (확인이 필요할 있다. Program상에서 JITA disable 있지만, TransactionRequired 설정에서는 Component Services UI에서 JITA enabled 되어있는 것을 확인했는 , 이둘은 가까운 사이인 하다.) 해당 문서는 object lifecycle의 관점에서 JIT에 대한 몇가지 중요한 Design Pattern 언급이 있다. 

 

먼저, 언급된 것은 object의 생성과 해제의 시점에 몇몇의 필요한 코드를 처리하기위한 Activate Deactivate 메소드의 override처리나(사실 해당 두 메소드의 override는 canbepooled와 함께 Object Pooling을 위한 코드에서 주로 처리했었다.) 실제로 class constructor에서의 activate관련 코드, 그리고 Dispose(bool)메소드에서 deactivate 관련 코드가 삽입되는 것이 중요할 듯 하다. 흔히 격는 문제가 Object 대한 메모리 누수나, prematurely drop 의한 Exception정도라고 본다면 object lifecycle과 관련하여 이들 메소드에서 명시적으로 처리하는 것은 문제를 제거하는 올바른 방법이라 생각한다.

 

The class derives from ServicedComponent and uses the JIT attribute to indicate the specific service required. In order to override the Activate and Deactivate methods in unmanaged code, the class is required to implement the IObjectControl interface. The class ServicedComponent instead has virtual methods that may be overridden to handle the Activate and Deactivate events. However, neither ServicedComponent nor its real proxy, SCP, implement IObjectControl. Instead, the SCP creates a proxy tearoff when the IObjectControl interface is requested by COM+. The calls by COM+ on the tearoff are then forwarded to the ServicedComponent's virtual methods. The DeactivateOnReturn bit is set using either the AutoComplete attribute on the method, calling ContextUtil.SetComplete(), ContextUtil.SetAbort() or setting ContextUtil.DeactivateOnReturn. Assuming the DeactivateOnReturn bit is set during each method call, the sequence of method calls would be: the class's constructor, Activate, the actual method call, Deactivate, Dispose(true) and eventually the class's finalizer if one exists. The same sequence is repeated when another method call is made. A good design practice is to only override the Activate and Deactivate methods to know when the object is being taken out and put back into the object pool. The remaining logic of Activate and Deactivate should be placed in the class's constructor and Dispose(bool) methods.

 

여기서 메소드 호출의 sequence 따라 DeativateOnReturn bit 설정이 오브젝트의 deactivate 이끈다는 사실이며, 가장 손쉬운 방법은 AutoComplete 속성의 설정이 것이다. 또한, Object Pooling 사용하지 않는 다면,  Deactivate Object Pool 인도하지 않고 그대로 Drop 이끌것이다. 이는 물론, pooling 사용하는 경우에 비해 Performance impact 있을 것이다. 계속해서 DeactivateOnReturn bit 설정에 대한 자세한 언급이 이어진다.

The DeactivateOnReturn bit can be set using one of the following approaches:

1.       The client uses the object's state only for a single method call. On entry to the method, a new real object is created and attached to the SCP. On exiting the method, the real object is deactivated, first by making calls to Dispose(true) followed by the real objects finalizer if one exists. However, the associated COM+ context, SCP and TP remain alive. The client code will still maintain its reference to what it believes is an actual object (the transparent proxy). The next method call made by the client on the same reference will result in a new real object being created and attached to the SCP in order to service the method call (see the section on object pooling to remove the requirement of creating a new object). To deactivate the real object, the real object needs to indicate doneness when a method call exits. This can be accomplished by using:

a.       the AutoComplete attribute on a method of the class

b.      either one of two method calls on the ContextUtil class, DeactivateOnReturn or SetComplete

2.       The client makes multiple method calls on the same object without deactivating the object after each method call by setting the doneness bit to false before exiting the method. For example, scoping a serviced component that uses JIT on the form level and having two form buttons call methods on the same object instance by having the methods explicitly set the doneness bit to false. At some point, the doneness bit should be set to true. This approach implies a contract exists between the client and object. This can be done implicitly or explicitly by the client:

a.       The client knows to call a certain method on the object when it is done in order to deactivate the object. The method implementation uses the ideas in option 1. The object reference can be called again using the same calling sequence, implying a new real object will be created.

b.      The object is explicitly destroyed by the client when it calls the Dispose() method on the object. Dispose() is a method defined on ServicedComponent and in-turn calls Dispose(true), the class's finalizer (if one exists) and then tears down the associated COM+ context. In this case, no further method calls can be made on the object reference. An exception will be thrown if this is attempted. If there are many clients using the same object, calling Dispose() should be done only when the last client is done with the object. However, the stateless nature of JIT objects leads design practices towards a single instance per client model.

c.       The object never sets its doneness bit to true and the client never calls Dispose(). The real object, proxies and context gets destroyed when garbage collection takes place. The method call order initiated by the GC would be Deactivate, Dispose(false) then the classes finalizer (if one exists).

무엇보다도 중요한 것은 해당 오브젝트를 사용하는 Client에서의 명시적인 Dispose 호출이다. 또한 정말 중요하다. 물론, GC 의해서 오브젝트가 해제되는 기회가 있음을 언급하고 있지만, 리소스 해제의 delay 관련한 이슈의 언급도 다음과 같이  있다.


All serviced components have an associated COM+ context, which is stored as a reference in the SCP (or RSCP in the remote case). The reference is released only when the GC takes place or if the client calls Dispose(). It is better not to rely on the GC to clean up the context: The COM+ context holds onto one OS handle and some memory possibly delaying the release of these handles until a GC occurs. Also, although ServicedComponent does not have a finalizer, the SCP does implement a finalizer, meaning that the COM+ context reference will never get garbage collected on a first collection. In fact, when the finalizer on the SCP eventually gets called, the context is still not destroyed by the finalizer thread, instead, the work of destroying contexts is removed from the finalizer thread and placed on an internal queue. This was done because it was found that the finalizer thread can get consumed by work in certain stress environments where serviced components are being rapidly created, used and go out of scope. Instead, an internal thread services the queue, destroying old contexts. In addition, any application thread creating a new ServicedComponent will first attempt to take an item off the queue and destroy an old context. Therefore, calling Dispose() from the client will tear down the COM+ context sooner using the client thread and it will release the handle and memory resources that the context consumes. Sometimes Dispose() can throw exceptions. One case is if the object lives in a non-root transaction context that has aborted—the Dispose() call may observe a CONTEXT_E_ABORTED exception.

From a performance viewpoint, it is better not to implement a finalizer in a ServicedComponent derived class and instead place this logic in the Dispose(bool) method. Although the SCP does implement a finalizer, the real object's finalizer is called using reflection.

결론적으로 JIT 위한 좋은 design 이라면, 다음을 명심하는 것이 좋을 하다.

 먼저, Custom Activation 코드는 Constructor, 그리고, Finalization관련 코드는 Dispose(bool) 메소드에서 소화하고, Finalizer 구현하지 않는 것이 좋겠다. 또한, DeactivateOnReturn Bit설정과 관련해서 AutoComplete 속성을 믿어보자. 그리고, Client 에서 오브젝트가 더이상 필요하지 않는 다면, 명시적으로 Dispose() 호출을 해주는 것이 좋겠다. 물론, using 문을 쓴다면 더할나위 없겠다.


트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://byung.egloos.com/tb/5480557 [도움말]

덧글

댓글 입력 영역