devxlogo

STA Comes Not from STAbility

STA Comes Not from STAbility

continue a good cause started by Jimmy Nilsson saying “Don’t you hate being told what to do and not to do, without knowing the reason why?” As for now the time has come to STA, the only apartment model available from VB 6 and formers. On the one hand Microsoft proclaims VB 6 and STA components as stable enterprise architecture for the middle tier with bold font in titles of MSDN. On the other hand you can find lots of strong restrictions for STA under COM+ written with smaller font in the very technical articles of the same MSDN. C’est la vie. But as a rule there is only a restriction mentioned and very rarely you can find a short explanation why it is not worth doing so. Keep in mind that some restrictions can be fatal for your architecture and you can never predict when and where it will happen. You should learn all the restrictions as well as investigate their real reasons and possible effects before picking out some particular architecture. So measure thrice and cut once before taking the final decision. And of course don’t buy bold titles of MSDN and other advertisement. You have to learn to read between the lines. And now I will help you to study a piece of Microsoft day-to-day advice Don’t make blocking calls from STA. Terse and clear.

ABSTRACT

First of all, there are two kinds of blocking calls. The first one is an implicitly blocking call. That happens when you are making an outgoing call to other components outside of the caller apartment or context and are synchronously waiting for the call to return back. Explicitly blocking call occurs when you are calling Sleep or WaitForSingleObject or something like that. If you are making a blocking call of any type, the message loop of the caller’s STA thread is blocked, which forces the activity’s workflow to get blocked as well while it is waiting for the blocking call to return. During this time, the message loop cannot serve any other instances that live on that thread.The same is for components marked as Both. Don’t ever forget any Both component can be created from an STA component at any time and therefore it can also get into an STA apartment and thus it can also perform as a usual STA component.

COM+ STA pool has 7 + X threads and potentially can grow up to 10 * X, where X is a number of CPUs on the board. That is if we had 8 CPUs that pool would hold 15 threads initially and 80 threads at the maximum under stress. Suppose all the 15 threads are already busy handling incoming COM calls. After that COM+ starts to reuse these existing 15 threads. Such a technique is called multiplexing.

Michael McKeown, MS Engineer writes in MSDN as follows: Unlike MTS, COM+ binds up to five activities to a thread. That’s only a half of the truth. Actually COM+ operates in the following way. Let we have 1 CPU on the board. In that case COM+ distributes all incoming requests among 8 threads existing from the very beginning. After these existing 8 threads got 5 activities per thread (i.e. 40 activities) COM+ creates the ninth thread and bounds the next 5 activities to it. After that COM+ creates the tenth thread and bounds the next 5 activities to it. Now that COM+ has reached the upper limit of threads it begins to multiplex all incoming activities among 10 threads. That is COM+ binds up to five activities to a thread only when there are 8 initial threads. The upper limit is not five activities per thread. The upper limit is unknown actually. More precisely the upper limit is stack overflow but we won’t forestall.

Under MTS if all the threads are busy making out-of-apartment calls MTS queues incoming calls until some thread gets available to handle a call. But COM+ operates otherwise. Suppose an STA component receives an incoming call A, starts execution on the STA thread and makes out-of-apartment call. COM intermediate layer (so-called COM-channel) enters the thread’s modal message loop and feels ready to receive and handle another COM request to the same STA apartment while working RPC thread makes a real blocking call to the target component and actually waits for that blocking call to return. Another incoming call B to the same apartment can now be also served. Activity A is still waiting for return. Suppose activity B gets an incoming call B, starts execution and makes out-of-apartment call as well. Here lies the key point for understanding. Activity A won’t return control of the STA thread until Activity B completes handling of call B entirely. Imagine, all the components are of the same type and make out-of-apartment calls. That means A calls out, B calls out, C calls out and so on and so forth. So A waits for B to complete, B in its turn waits for C to complete etc. We’ve got a LIFO structure (Last In First Out). Execution time of A consists of its own execution time and B + C + etc execution time. This is the price we are forced to pay for the possibility to work around the blocking caused by out-of-apartment call from STA.

For those who make explicitly blocking call COM+ provides CoWaitForMutlipleHandles function. If the caller resides in an STA, CoWaitForMutlipleHandles enters the STA message modal loop, and the loop will continue to dispatch messages using the thread’s message filter. If no message filter is registered for the thread, the default COM message processing is used. If the calling thread resides in an MTA, CoWaitForMutlipleHandles just forwards the call to Win32 API function MsgWaitForMutlipleObjects.

And that is the double blessing. Can you see where the danger is coming up? Everything said above was just suspense.

ARCHITECTURE

Any serious contemporary n-tier architecture includes number of system services along with main business components. Before launching business logic it should log on to the system, get some context object which will be passed afterwards to all the underlying components ensuring exchange of system parameters and fulfilling other tasks like error collecting. Also it has to check permissions whether that particular user has the right to fulfill the requested task. It has to check if that particular user has bought the license necessary to fulfill the desired feature. As a rule it is system services, which are responsible for all this system functionality. Underlying business objects, as a rule, also make out-of-apartment calls, for example, to the united Locking Service. The services possess several advantages over other variants of implementation. The main advantage is that services can handle incoming requests when nobody is logged on to the system. Anyway, several calls are to be made out-of-process from any tier of n-tier architecture.

It would make sense to take out-of-proc callers out of COM+ but, you know, developers are fond of placing DLLs under COM+. They like that not only because COM+ ensures transactions, synchronization and other stuff. COM+ suggests approved dllhost.exe as a host for DLL to be available remotely. That’s also one of the main reasons where to place components to. So, accept the inevitable. You have got some COM+ components making out-of-apartment, out-of-context, out-of-process or even out-of-machine calls. There would seem to be nothing terrible about it. Even if you trusted titles of MSDN and created all the components in VB that is as STA components. Even in that case, there still would seem to be nothing awful. Slow performance under stress, that is all what it looks. Okay let’s conduct a simple but nice (as usual) test.

TEST

It will take you half an hour to write a test revealing the behavior of STA objects making out-of calls under COM+.

1.      Create some COM EXE. Name that CmpExe. It does not matter what language you use and whether you make it as service or not.

2.      Expose its method via COM interface, let’s name it MtdExe.

3.      Write the code of MtdExe sleeping for a while, say, 300 ms and returning after that.

4.      Create some COM DLL CmpDll in VC++ with public method MtdDll. Mark that as STA. Keep in mind you could also create that component with VB6 but further you will need to insert pieces of Assembler to measure things.

5.      Write the code of MtdDll creating that COM EXE and calling its method MtdExe.

6.      Place that COM DLL under COM+ in a server application.

Now that we have got a simple infrastructure emulating a real system we can stress that system. Use Microsoft Application Center Test from VS.NET Enterprise Edition or something like that. As for me, I used simple bomber process whose goal was just to create a new thread, so that later that thread creates the COM DLL, calls its method MtdDll and measures time of the call.

FIRST RESULTS

Launch that and see after a while system will just hang. On my machine system hangs after about 200 calls to MtdDll. CPU activity drops to near zero and the system remains in that state forever. The system hangs during the call from CmpDll to CmdExe. Just hangs silently.

INVESTIGATION

After I had installed debug symbols for Windows 2000 Service Pack 3, adjusted AD+ (MS KB PSS ID Number: 286350 HOWTO: Use Autodump+ to Troubleshoot “Hangs” and “Crashes”)and worked with WinDbg I caught the cause. The reason was a stack overflow in dllhost.exe process (COM+ Application). Let’s see the whole picture of stack exhausting.

As expected, all STA COM DLLs shared a thread pool of 10 threads (from 7+1 to 10*1). Below comes a sample table (Fig. 1) that shows how free stack space was changing when the COM DLL was serving 50 requests. Each line corresponds to one thread; the first column shows stack space while serving the first request (for each thread), and so forth. To avoid excessive clattering of the table, when stack space didn’t change in comparison to the previous measurement, the actual value is replaced with an austerisk. Mind you, that initial stack size, allocated by COM for every thread, is 256K, and tests have shown that a single request consumes 20K of stack space (at the point of logging, that is the beginning of MtdExe).

                

Thread 1

139264

118784

*

*

*

*

         

Thread 2

143360

122880

*

*

*

*

*

        

Thread 3

143360

122880

*

*

*

*

         

Thread 4

143360

122880

*

*

*

          

Thread 5

139264

118784

*

*

*

          

Thread 6

139264

118784

*

*

*

          

Thread 7

143360

122880

*

*

           

Thread 8

139264

118784

*

*

           

Thread 9

131072

110592

*

*

           

Thread 10

135168

114688

*

*

           

Fig. 1

Number of Requests

Minimum Free Stack Space

100

110592

150

86016

200

Stack Overflow

Fig. 2 shows the flashpoint where stack gets overflowed.

Below are the functions that were used to obtain the total stack size and the available stack space.

DWORD GetStackSize()

{

Here is the function that returns COM-related flags variable from the TLS. It also proved useful in the course of the investigation for example, to find out whether the thread is running in Main STA or not.

enum OLETLSFLAGS

{

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist