Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Sporadic CDO failure when called in multithreaded VBScript

68 views
Skip to first unread message

Paul Baker [MVP, Windows Desktop Experience]

unread,
Jan 12, 2010, 4:21:59 PM1/12/10
to
I am using VBScript to send emails message using CDO on Windows Server 2003.
The script host is multi-threaded (I wrote it myself) which gets up to a
maximum load of a few dozen simultaneous script threads. A script may be
called from an STA, which is the main UI thread, or the MTA.

My problem is that sporadically, the script never completes when called from
the STA. The call stack never changes and the last five entries are
somewhere around cdosys!DllUnregisterServer.

Looking at the call stack, I would guess that the last thing it is doing is
disposing the CDO.Message object because it went out of scope. I know there
is some debate about when, if ever, you should clear a reference to an
object when you are done with it ("Set msg = Nothing"), but this should not
be a problem in VBScript. Interestingly, though, this page claims that if
you do not do so in an ASP page "your web server will hang and the form will
be stuck in a loop".

http://forums.aspfree.com/asp-development-5/cdo-message-1-error-80040220t-295902.html

Also of interest, perhaps a major clue, the thread continues to handle
messages. It handles even queued ones such as WM_PAINT, when this happens. I
do not know why the script would end up in a message loop, I do not see a
message loop in the call stack and I do not have a theory why it does that.
Certainly, doing something with messages that the caller is not expecting
has the potential to cause havoc.

I am wondering whether CDO and VBScript are thread safe in this scenario and
whether my On Error statements are safe. Those are the only other theories I
have so far.

If anyone can offer any pointers at all, I would greatly appreciate it. The
top of my call stack and the code are below.

Paul

-- This is the top of my call stack --

cdosys!DllUnregisterServer+0x1BA56 0x49F49D50
cdosys!DllUnregisterServer+0x1BF21 0x49F4A21B
cdosys!DllUnregisterServer+0x192C9 0x49F475C3
cdosys!DllUnregisterServer+0x1772B 0x49F45A25
cdosys!DllUnregisterServer+0xEDC4 0x49F3D0BE
oleaut32!DispCallFunc+0xAB 0x77D05186
oleaut32!SafeArrayCopyData+0x397 0x77D14ACF
cdosys!DllUnregisterServer+0x78C 0x49F2EA86

-- This is my VBScript code (sorry, I censored some of it), I am calling
SendMailStatus() --

Option Explicit

Sub AppendBody(ByRef body, name)
Dim fso, filename, stream

Set fso = CreateObject("Scripting.FileSystemObject")
filename = <...>
Set stream = fso.OpenTextFile(filename, 1)
If Not stream.AtEndOfStream Then body = body & stream.ReadAll
End Sub

Sub SendMail2(<...>)
Dim msg, body, config
AppendBody body, <...>
Set msg = CreateObject("CDO.Message")
msg.Subject = <...>
msg.From = <...>
msg.To = <...>
msg.HTMLBody = body
Set config = CreateObject("CDO.Configuration")
config.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing")
= 2
config.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver")
= <...>
config.Fields.Update
msg.Configuration = config
msg.Send
End Sub

Sub SendMail(<...>)
Dim errnumber, errdesc

On Error Resume Next
SendMail2 <...>
errnumber = Err.Number
errdesc = Err.Description
On Error Goto 0
If errnumber <> 0 Then <...>
End Sub

Sub SendMailStatus(<...>)
SendMail <...>
End Sub


Paul Baker [MVP, Windows Desktop Experience]

unread,
Jan 13, 2010, 3:36:59 PM1/13/10
to
This was a total guess based on the (native) call stack. I now see evidence
that my guess was wrong and that it didn't even finish sending the message
before it got stuck. I added debug logging to it, so I can find out for
sure.

Thanks,

Paul

"Paul Baker [MVP, Windows Desktop Experience]"
<paulrich...@community.nospam> wrote in message
news:%23DREI18...@TK2MSFTNGP05.phx.gbl...

Paul Baker [MVP, Windows Desktop Experience]

unread,
Jan 27, 2010, 10:46:01 AM1/27/10
to
Yes, logging confirms that it got "stuck" in the Send method of a
CDO.Message object.

The Exchange logs show that the message was delivered to both recipients.
One was local, which we can confirm we reiceved, and one was SMTP, though we
are not certain whether or not the customer received it.

In order to investigate multi-threaded CDO and scripting, I wrote a test
application that is as similar as possible to the production application. It
was able to consistently send dozens of CDO messages simultaneously.

And yes, I am talking to myself ;)

Paul

"Paul Baker [MVP, Windows Desktop Experience]"
<paulrich...@community.nospam> wrote in message

news:%238f2oAJ...@TK2MSFTNGP04.phx.gbl...

Paul Baker [MVP, Windows Desktop Experience]

unread,
Feb 9, 2010, 9:16:19 AM2/9/10
to
I am now able to reproduce this, which helps troubleshoot it.

When this happens, the main UI thread, in an STA, is in a call to the Send
method of a CDO.Message. It seems to be related to the unexpected
dispatching of messages. For example, if I click a menu item, it
unexpectedly responds. Then it is stuck in
CDOEX.DLL!CSMTPCallback::WaitForCompletion until I close the program
"breaking" the message loop, then it suddenly finishes.

If I put a breakpoint in the handling of the menu item message, I see this
in the middle of the call stack:

user32.dll!DispatchMessageA+0xf
CDOEX.DLL!CSMTPCallback::WaitForCompletion+0x4c

So it seems CDOEX.DLL!CSMTPCallback::WaitForCompletion is dispatching
messages that I was not expecting.

I realise that COM must dispatch messages in an STA, but it is not going
through COM here. It is only sporadically calling the
IMessageFilter:MessagePending method of the message filter I registered. It
is still looking to me like a CDO problem, but perhaps it is a COM problem.

Paul

"Paul Baker [MVP, Windows Desktop Experience]"
<paulrich...@community.nospam> wrote in message

news:%238SmUf2...@TK2MSFTNGP02.phx.gbl...

Paul Baker [MVP, Windows Desktop Experience]

unread,
Feb 15, 2010, 11:45:34 AM2/15/10
to
(originally posted on 2/10, but I don't think it ever got published)

I have resolved my own issue, and I wanted to post here for anyone who is
following it or researching their own similar issue. I'm not going to quote
previous messages, in the interest of being concise.

This is what is happening, at least in one of the scenarios I can reproduce.

1) The main UI thread, which is in an STA, has a message loop #1A.

2) In response to user input, the main UI thread calls the Send method of a
CDO.Message object.

3) COM may enter a message loop #1B to dispatch COM calls.

INFO: OLE Threads Must Dispatch Messages:
http://support.microsoft.com/kb/136885

Since the thread is in a synchronous blocking call and cannot dispatch
messages, COM does it for you.

4) The Send method is called. After sending the message, CDO waits for a
thread message (see PostThreadMessage) that notifies it that the send
completed. There is a message loop #2 that dispatches all messages, not just
the one it is looking for. THIS IS A CDO BUG! It should use the
wMsgFilterMin and wMsgFilterMax parameters of GetMessage to filter out
anything but the thread message it needs.

PostThreadMessage Function:
http://msdn.microsoft.com/en-us/library/ms644946(VS.85).aspx

GetMessage:
http://msdn.microsoft.com/en-us/library/ms644936(VS.85).aspx

4) The user clicks on a menu (see WM_ENTERMENULOOP) or there is a modal
dialog, either of which enters a message loop #3.

WM_ENTERMENULOOP Notification:
http://msdn.microsoft.com/en-us/library/ms647595(VS.85).aspx

5) Message loop #3 sees the thread message, but message loop #2 does not. If
it is still in message loop #3 when the send completes, message loop #2 does
not exit when expected. Therefore, it appears as if the send never
completes. Raymond Chen explains this in more detail:

Thread messages are eaten by modal loops:
http://blogs.msdn.com/oldnewthing/archive/2005/04/26/412116.aspx

There are ways around this, but it gets ugly.

Rescuing thread messages from modal loops via message filters:
http://blogs.msdn.com/oldnewthing/archive/2005/04/28/412574.aspx

This is the problem I was originally describing.

6) When the menu or modal dialog is closed, message loop #3 exits and
message loop #2 is running the main UI thread. This may be a problem if
special processing is needed and implemented in message loop #1 or for some
other reason. Message loop #1 is supposed to be running the show long term.

7) Message loop #2 does not exit until an attempt is made to close the
program and WM_QUIT breaks it. It returns control to message loop #1,
resuming normal message processing but the program does not close as
expected. THIS IS A CDO BUG! It is caused by a failure to re-post the
WM_QUIT message, which is Raymond Chen explains in more detail:

Modality, part 3: The WM_QUIT message:
http://blogs.msdn.com/oldnewthing/archive/2005/02/22/378018.aspx

8) Since the program did not close, the steps can be repeated and you can
get into unbounded nested message loops. That can't be good.

The solution I chose to implement is to do all in a thread independant of
the main UI thread. I don't need to handle any messages or create any
windows, so I don't need to care what someone else does with them. The main
UI thread can still initiate the call and wait for the other thread to be
done under my control. So I can handle messages however I want.

So, what have we learned boys and girls?

The code that created the thread should be in control of its message queue.
Other code it calls upon should avoid calling any message functions. If it
does, it should document it and try to give the caller the option of
avoiding it. If it must call message functions, it should leave messages it
is not interested in alone. This can cause reentrancy issues, deadlocks,
etc...

Message loops are even worse are to be avoided even more strongly. This can
give the user opportunity to do stuff that the caller assumed would be
delayed until the call completes, like closing a window upon which the call
depends.

Paul


0 new messages