Thunderclap, the newsletter of Rolling Thunder Computing
Volume 1, Number 2 Winter 1999
In this issue:
About Thunderclap
Subscription Information
Feature Article: COM Robustness, Part
2: Watch Out for the Other Guy
New Book on COM+ by David Platt,
published by Microsoft Press
Blatant Self Promotion
New Contest with Prizes: Famous Last Words
Results of Last Contest: The Answer Game
This is the second issue of my quarterly newsletters. Each will bring you a technical article on COM development, which I hope you will find useful. Each will also bring you a contest, allowing you to show off your intelligence and creativity to win prizes. I hope you will find it funny. In between you will find my own blatant self promotional material, telling you about the latest ways I've come up with to separate you from your money. (I could have said "carefully selected products and services that we feel might interest you", or other mealy-mouthed horsepuckey. You want the truth or you want me to waste my time and yours dressing it up?)
I'd like to hear what you think about this newsletter and what types of articles you'd like to see in the future. Would you prefer technical programming articles, such as the one in this current issue? Future topics on this track might be, "What the Heck Is the Free-Threaded Marshaler, Anyway?" or "Our Friend, the Causality ID". Or would you prefer higher-level conceptual articles, like "With All the Fuss About COM+, What's In It For Me Today?" Send your e-mail comments to newsletter@rollthunder.com.
This newsletter may be freely redistributed, provided that it is sent in its entirety. If you enjoyed it, can I ask you to please forward it to a friend who might also enjoy it? The subscription role has grown from nowhere to 400 for this issue.
Thunderclap is free, and is distributed via e-mail only. We never rent, sell or give away our mailing list. Subscription and unsubscription are handled by a human operator reading e-mail messages. To subscribe or unsubscribe, jump to the Rolling Thunder Web site and fill in the subscription form.
Watch Out for the Other Guy
A) Protecting Against Violations of the Apartment Threading ModelMost programmers write in-proc COM servers using the apartment threading model, also known as the single-threaded apartment model. See Chapter 13 of my book The Essence of COM with ActiveX, or my article in the February 1997 Microsoft Systems Journal for a further explanation of that model. Basically a client promises COM that it will call an objects methods only from the thread on which it created the object, and the object's server promises COM that it knows how to live in that kind of environment. Since both parties agree on the rules, COM provides them with a direct, unmarshaled connection instead of a proxy/stub connection, essentially allowing them to have unprotected sex. The objects generally don't protect themselves against concurrent access from multiple threads, since the client has promised not to do this.
Major problems can arise if an objects client doesnt keep that promise. "Sure, cutie object, Ill still respect you in the morning. And only one thread at a time. Are you going to believe the President of These Here United States or your lying eyes?" Suppose a client app's calls CoCreateInstance( ) from Thread A to create an apartment threaded object, puts the objects interface pointer in a global variable, and then Thread B makes a call into that object. The object hasn't protected its instance data against calls from different threads because the apartment threading model says that it shouldn't have to. All kinds of terrible things can happen. If youre lucky, youll crash right away. But more often, this leads to the worst kind of bugs subtle, flaky, and impossible to reproduce. And even though its the client apps fault for not following the rules, its YOUR tech support line that gets the nasty phone calls and the bomb threats. Youd better hope that you havent sold any to the postal service.
As you no doubt tell (or will tell) your daughters, your object needs to protect itself against a dishonest client. You could serialize access to all your member variables, even though the rules of the apartment threading model say that in an ideal world you shouldn't have to. But this is difficult and expensive to write in C++ and impossible in Visual Basic. It's actually quite easy in Visual Java, but the legal wrangles currently going on over Java are causing many of my clients to stay away from it for now. Components written in Java are somewhat slow anyway.
The easiest solution is for your object to check at the beginning of each method call whether the client made the call from the proper thread. It's sort of like performing an instantaneous virus test on your date before actually doing anything. If you come up with a real-life way of doing this, you can leave the geek business and open your own bank.
It's fairly simple to set this up. The operating system assigns each thread a DWORD identifier called the thread ID, which is guaranteed to be unique on your machine. In your object's constructor, you call the API function GetCurrentThreadId( ), which will return the ID of the thread from which it is called, the thread on which the object is being created. You store this thread ID in a member variable of your object. Then whenever your object receives a call, you call GetCurrentThreadId( ) again and compare the result to the one you first got in your constructor. If they match, then you were called from the right thread and everything is cool. If not, you return immediately with an informative error code, before doing anything in your object that might get messed up. You'd really like to strangle the client, but this is a little hard to program. You could pop up a message box that says you've caught him, and the next time it happens, you'll format his hard drive.
The only difficulty in implementing the thread ID check is forcing your programmers to do it every time. [Note: in the last issue, I compared managing programmers to herding cats. A reader named Robin wrote in to say that she has no trouble whatsoever herding her three cats -- just open a can of tuna fish, hold it at knee level, and start walking. What would you use for programmers? Latte? More RAM? Stock options?] Embedding it into macros that you insist programmers use in every class and method seems to be the most dependable way. It's easy to change the code on a project-wide basis if it causes trouble or if you need to add something. You can combine the thread check code with other macro code like the reference counting example I discussed in the previous issue of Thunderclap. I've seen this technique used at Microsoft and Dragon Systems, among other companies. Once you've decided to use it, the problem then becomes making sure it happens every time. The approach I'll show uses the macro idea I got from Dragon Systems, as discussed in the previous issue of ThunderClap.
You define three macros, thus:
/*
Declare a member variable to hold the initial thread ID
*/
#define OBJECT_DECLARATION_MACRO \
protected: DWORD m_InitialThreadId ;
/*
Get the ID of the thread on which the constructor was called, store in the member
variable.
*/
#define OBJECT_CTOR_MACRO { \
m_InitialThreadId = GetCurrentThreadId ( ) ; }
/*
If thread on which this method was called doesn't match thread on which object was
constructed, return an error code.
*/
#define METHOD_ENTRY_MACRO { \
if (m_InitialThreadId != GetCurrentThreadId( )) \
return RPC_E_WRONG_THREAD ; }
You put the first in an object's class declaration, thus:
class CSomeClass : public ISomeInterface
{
OBJECT_DECLARATION_MACRO
<rest of class definition omitted>
};
You put the second in the object's constructor, thus:
CSomeClass::CSomeClass ( )
{
OBJECT_CTOR_MACRO
<rest of ctor omitted>
}
You use the third at the start of every method, thus:
HRESULT CSomeClass::SomeMethod ( )
{
METHOD_ENTRY_MACRO
<rest of method omitted>
}
B) Protecting Against Synchronization Deadlocks in the Free or Both Threading Models
The second problem I'll discuss in this newsletter is the problem of synchronization deadlocks in the multi-threaded apartment. If you're using the free threaded model (or both threading models), don't make an outgoing COM call while you are holding a lock. You could wind up tying yourself into a five-dimensional hyperpretzel. Here's the problem. Consider the following code, made to execute in a multi-threaded apartment:
CSomeMTAClass::CSomeMethod( )
{
/*
Acquire a lock.
*/
EnterCriticalSection (&m_cs) ;
/*
Use resources protected by the lock.
*/
DoSomeWorkRequiringSerialization ( ) ;
/*
Release the lock.
*/
LeaveCriticalSection (&m_cs ) ;
return S_OK ;
}
What could be simpler ? It's classic Win32 multithreaded programming, what could possibly be wrong with it? The problem is that, since COM uses proxies and stubs to marshal calls between apartments, processes, and machines, that the thread you are running on at any given time isn't necessarily the one you think you are running on. The logical thread doesn't always match the physical thread.
The client calls a method on the object [stop me if I get too technical ;-)]. Since the call goes out through a proxy, it originates on Thread 1 and is actually received by the object on Thread 2. The object calls EnterCriticalSection on Thread 2. Now suppose that within the function DoSomeWorkRequiringSerialization( ) the object makes an outgoing call back to the client, for example an event notification. This is pure classic COM, nothing fancy or pushing the envelope. The call leaves the object on Thread 2, goes through proxy and stub and is received by the client's callback interface on Thread 3. Now suppose the client, within the callback method, calls the first method on the object again, or for that matter, any method that uses the same critical section. The call goes out on Thread 3, through the proxy and stub and is received by the object on Thread 4. When the object calls EnterCriticalSection from Thread 4, the call will block because the critical section is owned by Thread 2. And because critical sections don't support timeouts, it will hang forever.
If the second call had come into the object on Thread 2, there would have been no problem. Calling EnterCriticalSection would have succeeded, because the serialization mechanism knows which thread owns it and will detect and allow reentry. However, in this case, it didn't and we're hung. Even though the calls proceeded in a logical sequence (on the same "logical thread"), because of the intermediation of the proxy and stub, the second call actually came in on a different physical thread, and it is the physical thread that all the Win32 operating system synchronization objects use to detect and allow reentry.
This problem is potentially quite bad. Think how you'd feel if you spent five hours writing a document and then got hung before you could save. Hoping it doesn't bite you isn't exactly the gold standard of software robustness. The software industry is remarkably free of professional liability suits, compared to, for example, the medical industry; but it's this kind of crap that could get the lawyers smelling blood.
So what can we do about this? You have a couple of choices, none of them great. If DoSomeWorkRequiringSerialization( ) hadn't made a call out to the original client, which in turn called back into the object, then the deadlock wouldn't have happened. So it looks like we might be safe if we didn't call back into the original client. But this isn't reliable because you don't know how ANY COM object is implemented internally. Even if you called not back into the client but rather into some other COM object, how do you know that this object doesn't call back into the original client (or into another object that does, or into another object that does, etc ) ? You can't and you don't unless you wrote them all. The only absolutely safe thing to do here is not to make any calls to any COM objects while you hold a serialization lock. You'd have to write the code something like this:
CSomeMTAClass::CSomeMethod( )
{
EnterCriticalSection (&m_cs) ;
DoNon_COM_WorkRequiringSerialization ( ) ;
LeaveCriticalSection (&m_cs ) ;pSomeComObject->SomeMethod ( ) ;
EnterCriticalSection (&m_cs) ;
DoMoreNon_COM_WorkRequiringSerialization ( ) ;
LeaveCriticalSection (&m_cs ) ;return S_OK ;
}
It looks like hell and it's not far off, especially since the utility functions you call (and the functions they call, and the functions they call, etc. ...) must not make any outgoing COM calls. The difficulty of writing code of this sort is one of the reasons why developers stay away from these threading models. Got any other ideas?
If you think you will encounter this problem rarely, you could try to detect it and fail the call instead of allowing the deadlock. It's fairly easy to do with a mutex and a timeout. The code would look something like this:
CSomeMTAClass::CSomeSlightlySaferMethod( )
{
/*
Lock using a mutex with a timeout of 1 second.
*/
DWORD result = WaitForSingleObject (&m_hMutex, 1000) ;
/*
If the wait function times out, fail the method and return an error code.
*/
if (result == WAIT_TIMEOUT)
{
return E_FAIL ; // or something more
descriptive
}
/*
Use resources protected by the lock and release it.
*/
DoSomeWorkRequiringSerialization ( ) ;
ReleaseMutex (m_hMutex) ;
return S_OK ;
}
It's extremely ugly. It reminds me of one system that I built many years ago that I just could not keep from crashing occasionally, at which time it had to be rebooted. I put a watchdog timer card on the bus the would reboot the system if the program didn't write to a certain port every few minutes. For $100 per box, I met the design specs for unattended uptime. But like that solution, it's quick and cheap to write and at least you won't get tied into yogic knots. The primary difficulty here is determining the appropriate timeout interval to use while waiting on the mutex. Make it too short and legal calls coming from other threads will time out and fail, while waiting for another thread that would actually return. Make it too long and the user will lose patience and press 'Reset'. You really only want to do this if you think you'll see the problem very rarely and just want to be safe.
As W. C. Fields used to say, "If at first you don't succeed, try, try again. Then quit. There's no sense being a damn fool about it." The easiest solution to this problem would be to bail out and use the apartment model instead. The components are much easier to write, and if your clients are also apartment threaded, they'll run faster than free threaded components. Ask yourself why you are using the free threaded model in the first place. If it's just to be a stud, try banging your head against the wall, it's less painful and about as rewarding.
This entire problem will go away if you switch to Visual Java. Components written in VJ automatically support both threading models. Providing serialization of your Java methods is as simple as adding the keyword "synchronized" to the method definition in your class file, like this:
public synchronized void SayHello ( )
{
MessageBox (0, HelloMessage, "jhellodll", 0) ;
}
It doesn't get a whole lot easier, but many developers aren't ready to make the jump to Java yet. Maybe they will when they have to start writing free-threaded code. Visual Java is a whole lot better than Visual Basic. A C++ geek could honorably switch to Java.
In the final analysis, synchronization of this type is a generic problem. It would be ideal if we could inherit a generic solution from the operating system, as we have to so many other problems. And that's coming. COM+ is adding synchronization to the array of services that any component can inherit from the runtime environment, if it converts to the COM+ religion. I'd like to write about that in the next issue of this newsletter. My NDA and Microsoft's schedule might or might not let me do it. Watch this space for that, or something else cool.
Good luck with it. As Red Green would say, until next time, "Keep your stick on the ice."
Forthcoming Book: Understanding COM+
by David S. Platt, President of Rolling Thunder Computing
Published by Microsoft Press
You'd think I'd learn, wouldn't you? I was finally getting some free time, finally spending at least one day per weekend with my wife, and along comes another book. This one's on COM+ for Microsoft Press. I'm trying very hard to get it in the can by the end of February to get it out by early summer, but the recently announced delay of Windows 2000 will probably delay it. The ISBN is 1-57231-666-0. It's a different approach than my other books, more of a conceptual overview than a wealth of cut-and-paste code. I'm using the same format that David Chappell did in his very successful Understanding ActiveX and OLE, published by Microsoft Press in 1996. The table of contents of Understanding COM+ is:
1. Introduction to COM+ |
2. COM+ Architecture |
3. Transactions |
4. Queued Components |
5. Events |
6. In-Memory Database |
7. Load Balancing and Deployment |
Publicly available classes from Rolling Thunder Computing
All are available in-house as well
COM+ at Harvard beginning in February
I'll be teaching CSCI-E218, Programming COM+, COM, ActiveX, and OLE, at Harvard University Extension in the Spring 1999 term. The class is designed for practicing programmers and is open to the paying public. It meets Thursday nights at 7:35 PM, beginning on February 4 in Lecture Hall E (downstairs) at the Harvard Science Center. The class grants 4 units of graduate credit and costs $1200. The class description and syllabus can be found at http://www.rollthunder.com/harvard.htm. Anyone who thinks they might be interested is encouraged to come to the first class meeting to check it out.
OLE for Life Insurance in New York in March
I'll be teaching the next public class on OLE for Life Insurance in New York NY on March 15-17 and 22-24, sponsored by New York Life. For more information on this class or OLifE in general, see http://www.acord.com.
Microsoft Health Care Briefing in Eindhoven, Netherlands in April
I'll be speaking at the Microsoft & Microsoft Healthcare
Users Group Europe (MS-HUGe)
Healthcare Developer Conference at Eindhoven, Netherlands on April 26-27. On Monday, I'll
be speaking about COM+. On Tuesday, I'll be explaining ActiveX for Health Care. For
more information on this conference, see http://www.mshug-euro.org.
COM+ Enterprise Technology in Orlando in May
I'll be giving a double-session talk on COM+ Enterprise Technology at the ACORD Technology Conference. The conference is at Disney's Contemporary Resort, and my talk is on Monday May 24 from 3:30 - 6:00 PM. For information on this conference, see http://www.acord.com.
New Contest with Prizes: Famous Last Words
Every issue of Thunderclap features a contest allowing you to show off your intelligence and creativity. In this one, we ask you to give us the last words of a famous or familiar figure, real or imaginary, living or dead. For example:
Bill Clinton: "Hilary, where'd you get that flame-thrower?"
Mary Jo Kopechne: "All right, Ted, but only if you promise to keep both hands on the wheel."
Jack Lord: "Box me, Dan-O."
Have some fun with this one. What would Mickey Mouse's last words be? Something about copyright expiration? Or Larry Flynt's? Or Edward Teller's? Or Homer Simpson's? Or Prince Charles's? Or your high school English teacher's? If I get enough of them, maybe I'll do a matching game, with the decedents in one column and their last words in another, and offer more prizes for matching them up.
First prize is $100, second prize $50. Winners will be announced in the next issue of Thunderclap, and the judge's decision (mine) is final. All entries must be original. In the event of duplicate or similar entries, the earliest wins. All entries become property of Rolling Thunder Computing. Submit your entries via e-mail to contest@rollthunder.com.
Results of Last Contest: The Answer Game
Here are the results of the first contest, in which you were asked to supply an answer followed by the question.
The first prize winner, computer related and raunchy as requested, $100:
A: Just-in-time activation
Q: What form of foreplay do American presidents use when their interns are late for an
assignation?
Doug Fulford
The second prize winner, $50:
A: Software
Q: What gives Marv Albert a warm fuzzy feeling?
Joe Pelletier
Honorable mention to all:
A: OLE
Q: What did the Mexican programmer say when he heard about COM+
Doug Fulford
A: COM
Q: How do American presidents interact with their interns?
Doug Fulford
A: Purvey
Q: What is the sound made by an overwrought Jewish cat?
Mary Ann Madden
Legal Notices
Thunderclap does not accept advertising; nor do we sell, rent, or give away our subscriber list. We will make every effort to keep the names of subscribers private; however, if served with a court order, we will sing like a whole flock of canaries. If this bothers you, don't subscribe.
Source code and binaries supplied via this newsletter are provided "as-is", with no warranty of functionality, reliability or suitability for any purpose.
This newsletter is Copyright © 1999 by Rolling Thunder Computing, Inc., Ipswich MA. It may be freely redistributed provided that it is redistributed in its entirety, and that absolutely no changes are made in any way, including the removal of these legal notices.
Thunderclap is a registered trademark ® of Rolling Thunder Computing, Inc., Ipswich MA. All other trademarks are owned by their respective companies.