Discussion:
Mixing asynchronous / synchronous requests
(too old to reply)
Walt Welton-Lair
2005-04-26 04:22:02 UTC
Permalink
I'm running into a problem which I believe involves mixing of asynchronous /
synchronous requests. Here's the scenario. Using IE I make a server
request which responds with some DHTML containing a number of HREFs, plus an
object tag referencing my ActiveX control. The control downloads content
from a location specified by a parameter in the object tag, and it uses
synchronous calls to WinInet to do this.

The problem I'm having is that the control's request times out. This is
what I see happening:

- the DHTML loads in IE, including the reference to the control
- IE begins resolving HREFs
=> there are lots of them, so all available connections get used
(MaxConnectionsPerServer is set to its default of 2)
- the control initializes itself and makes its synchronous request via
WinInet (same process as IE)
=> at this point all loading in IE halts
- eventually the control's request times out
- immediately IE continues loading the remaining HREFs

If I set MaxConnectionsPerServer to a large enough value (e.g. > the number
of HREFs) then everything works fine.

I read the Microsoft support article at
http://support.microsoft.com/kb/q183110/. It states:

"WinInet limits the number of simultaneous connections that it makes to a
single HTTP server. If you exceed this limit, the requests block until one
of the current connections has completed."

So the control's request is definitely getting blocked - no big deal. But
as I mentioned above, all activity in the current connections is temporarily
suspended once the control's (synchronous) request is made. Unfortunately
that means none of the current connections ever completes so that the
control's request can become unblocked. The control will always timeout in
this case.

These observations suggest that a synchronous WinInet request blocks all
asynchronous requests made from the same process. Can someone confirm this?

As far as how to address my problem, I know I can call InternetSetOption and
programmatically increase MaxConnectionsPerServer, but I don't like that for
two reasons:
- I'll never know in advance how many connections I'll need
- it generally doesn't seem like a good idea to stray from the HTTP
specification

Does anyone know of other simple workarounds for this? Or do I have no
choice but to update the control to make an asynchronous request?

Thanks in advance for any feedback.
Walt
Stephen Sulzer
2005-04-26 21:57:34 UTC
Permalink
In WinInet, synchronous HTTP requests do not block asynchronous requests (or
vice versa). The problem you are encountering is more complex and involves
more than just WinInet. It sounds like there is a deadlock involving the IE
thread that calls into your ActiveX control: the deadlock being caused by
the blocking operation (in this case, a synchronous WinInet request) that
your control performs on that IE thread. You could probably reproduce the
hang by replacing the synchronous WinInet request in your control by a call
to, for example, Sleep(15000).

IE uses the URLMON component to download resources (to resolve the HREFs).
URLMON provides a COM API and asynchronous programming model on top of the
WinInet API. I am not an expert on the interaction between IE and URLMON,
but I think that IE uses the same thread (that calls into your ActiveX
control) to issue the download requests to URLMON.

URLMON will download the resources using asynchronous WinInet requests.
WinInet will use separate worker threads to carry out the async downloads.
However, in order for URLMON to notify IE of the current request progress
(and to let IE consume the data being downloaded), URLMON must notify IE on
the same thread that IE used to call into URLMON. Which is the same thread
that IE is using to call into your ActiveX control. When your control causes
that thread to block, the notifications from URLMON to IE's thread are also
blocked, stalling the progress of the async requests that URLMON is
handling. Your control blocks because the requests from URLMON are currently
using the available WinInet socket connections (per the
MaxConnectionsPerServer limit), so there is no available socket to service
your WinInet request.

Your ActiveX control has a common defect that single-threaded apartment
(STA) ActiveX & COM objects must avoid: STA COM objects cannot perform
blocking operations on the STA thread, unless the COM object also pumps
Windows messages. Therefore, if your control needs to perform a synchronous
blocking operation, it needs to implement a Windows message pump while
waiting for the blocking operation to complete.

Switching to using an async WinInet request won't really help that much:
your control will still have to implement a Windows message pump while
waiting for the async request to complete. An alternative solution that may
be easier to implement would be to spin up a worker thread that handles the
synchronous WinInet request. The worker thread would signal a Win32 event
handle to indicate to the main thread (the thread that IE called your
control on) when the request completes. While waiting for that event
notification, the code in the main thread would run a windows message pump.
To wait, it could use the MsgWaitForMultipleObjects Win32 function, which
can be used to wait on both an event handle and the thread's message queue.
If MsgWaitForMultipleObjects indicates that there is a Windows message
waiting, then run a simple message pump to dispatch the message(s). Some
pseudo-code:

loop {
result = MsgWaitForMultipleObjects( 1,
array-of {worker-thread-event-handle},
FALSE,
aTimeoutValueInMilliseconds,
QS_ALLINPUT);

if (result says worker-thread-event-handle is signaled) {
// worker thread is done
exit loop
} else if (result says message is waiting) {
// run message pump:
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);

// continue main loop
} else if (result is WAIT_TIMEOUT) {
// timeout occured, abort request?
} else {
// some other error has occured waiting
}
}

You can create the worker thread using CreateThread. Once the worker thread
is done with the synchronous WinInet request it will make the response data
available in some kind of global memory buffer, then signal its event handle
and exit. Also be sure the main thread calls CloseHandle on the worker
thread handle when it is done.

Note that on Windows 2000 and later, there is an API called
CoWaitForMultipleHandles that essentially implements the loop I wrote above,
and handles the message pumping for you. You may be able to use this API
instead; however, I am not certain that it will dispatch URLMON's messages
to IE's main thread.

Hope this helps. Good luck.

- Stephen
Walt Welton-Lair
2005-04-27 20:42:12 UTC
Permalink
Thanks for your detailed response - your explanation makes completes sense.

I did what you suggested and opened the request in a separate thread
(waiting and pumping messages in the main thread), and it now works
correctly.
Post by Stephen Sulzer
In WinInet, synchronous HTTP requests do not block asynchronous requests
(or vice versa). The problem you are encountering is more complex and
involves more than just WinInet. It sounds like there is a deadlock
involving the IE thread that calls into your ActiveX control: the deadlock
being caused by the blocking operation (in this case, a synchronous
WinInet request) that your control performs on that IE thread. You could
probably reproduce the hang by replacing the synchronous WinInet request
in your control by a call to, for example, Sleep(15000).
IE uses the URLMON component to download resources (to resolve the HREFs).
URLMON provides a COM API and asynchronous programming model on top of the
WinInet API. I am not an expert on the interaction between IE and URLMON,
but I think that IE uses the same thread (that calls into your ActiveX
control) to issue the download requests to URLMON.
URLMON will download the resources using asynchronous WinInet requests.
WinInet will use separate worker threads to carry out the async downloads.
However, in order for URLMON to notify IE of the current request progress
(and to let IE consume the data being downloaded), URLMON must notify IE
on the same thread that IE used to call into URLMON. Which is the same
thread that IE is using to call into your ActiveX control. When your
control causes that thread to block, the notifications from URLMON to IE's
thread are also blocked, stalling the progress of the async requests that
URLMON is handling. Your control blocks because the requests from URLMON
are currently using the available WinInet socket connections (per the
MaxConnectionsPerServer limit), so there is no available socket to service
your WinInet request.
Your ActiveX control has a common defect that single-threaded apartment
(STA) ActiveX & COM objects must avoid: STA COM objects cannot perform
blocking operations on the STA thread, unless the COM object also pumps
Windows messages. Therefore, if your control needs to perform a
synchronous blocking operation, it needs to implement a Windows message
pump while waiting for the blocking operation to complete.
your control will still have to implement a Windows message pump while
waiting for the async request to complete. An alternative solution that
may be easier to implement would be to spin up a worker thread that
handles the synchronous WinInet request. The worker thread would signal a
Win32 event handle to indicate to the main thread (the thread that IE
called your control on) when the request completes. While waiting for that
event notification, the code in the main thread would run a windows
message pump. To wait, it could use the MsgWaitForMultipleObjects Win32
function, which can be used to wait on both an event handle and the
thread's message queue. If MsgWaitForMultipleObjects indicates that there
is a Windows message waiting, then run a simple message pump to dispatch
loop {
result = MsgWaitForMultipleObjects( 1,
array-of {worker-thread-event-handle},
FALSE,
aTimeoutValueInMilliseconds,
QS_ALLINPUT);
if (result says worker-thread-event-handle is signaled) {
// worker thread is done
exit loop
} else if (result says message is waiting) {
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
// continue main loop
} else if (result is WAIT_TIMEOUT) {
// timeout occured, abort request?
} else {
// some other error has occured waiting
}
}
You can create the worker thread using CreateThread. Once the worker
thread is done with the synchronous WinInet request it will make the
response data available in some kind of global memory buffer, then signal
its event handle and exit. Also be sure the main thread calls CloseHandle
on the worker thread handle when it is done.
Note that on Windows 2000 and later, there is an API called
above, and handles the message pumping for you. You may be able to use
this API instead; however, I am not certain that it will dispatch URLMON's
messages to IE's main thread.
Hope this helps. Good luck.
- Stephen
Continue reading on narkive:
Search results for 'Mixing asynchronous / synchronous requests' (Questions and Answers)
11
replies
what is computer memory made from?
started 2006-11-27 12:51:46 UTC
laptops & notebooks
Loading...