Discussion:
[asio-users] Problem with DLL causing program to hang upon exit when io_service contains a handler to async_read
Incognito
2011-05-01 08:07:34 UTC
Permalink
Okay, unless you couldn't tell by the subject, my problem is somewhat of a
complicated one. I have a DLL that acts as a TCP client using Asio.
Everything works great while the program is running, but unfortunately,
sometimes the program hangs upon exit. I've found that this only happens if
the server does not close the socket before the program exits. If the socket
is still open when the user tries to exit, the program hangs.

Upon further inspection, the program _only_ hangs if the io_service contains
a handler to async_read (or async_read_some). I don't know why this is the
case, but it happens every time.

Here's a very small portion of my code:

void run()
{
boost::this_thread::sleep(boost::posix_time::seconds(1));
// Additional code here to give io_service some work to do
io_service.run();
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
boost::thread thread(run);
break;
}
}

Some remarks:

Yes, I know that creating threads in DllMain is bad form. Yes, I know that
synchronizing threads in DllMain is hard or nearly impossible. However, I
don't have access to the source of the executable, so my only choice is to
create a separate thread when the DLL loads so that the io_service does not
block the main thread.

I've tried doing a number of things under DLL_PROCESS_DETACH. I've tried
using io_service.stop(), but that does nothing. I've tried putting the
io_service in a scoped_ptr and resetting it, but that causes the io_service
destructor to hang as well. I've tried interrupting and terminating the
thread in every way imaginable, but it seems that I simply can't communicate
with the thread once the process ends.

The only thing that has worked is using ExitThread under DLL_PROCESS_DETACH
if the socket is still open. This has been my workaround for a while, but it
forces the whole program to end immediately, so I don't like it. Surely
there's some way that's more elegant.

If anyone has any suggestions (or any insights at all as to why this only
happens when there is an ongoing asynchronous read operation), please let me
know.

Thanks.
Simeon Maxein
2011-05-01 11:40:09 UTC
Permalink
This problem is probably solved already, but that change was made after
the 1.5.3 release. You can try using the latest version from the
repository at asio.git.sourceforge.net.
Post by Incognito
Okay, unless you couldn't tell by the subject, my problem is somewhat
of a complicated one. I have a DLL that acts as a TCP client using
Asio. Everything works great while the program is running, but
unfortunately, sometimes the program hangs upon exit. I've found that
this only happens if the server does not close the socket before the
program exits. If the socket is still open when the user tries to
exit, the program hangs.
Upon further inspection, the program _only_ hangs if the io_service
contains a handler to async_read (or async_read_some). I don't know
why this is the case, but it happens every time.
void run()
{
boost::this_thread::sleep(boost::posix_time::seconds(1));
// Additional code here to give io_service some work to do
io_service.run();
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
boost::thread thread(run);
break;
}
}
Yes, I know that creating threads in DllMain is bad form. Yes, I know
that synchronizing threads in DllMain is hard or nearly impossible.
However, I don't have access to the source of the executable, so my
only choice is to create a separate thread when the DLL loads so that
the io_service does not block the main thread.
I've tried doing a number of things under DLL_PROCESS_DETACH. I've
tried using io_service.stop(), but that does nothing. I've tried
putting the io_service in a scoped_ptr and resetting it, but that
causes the io_service destructor to hang as well. I've tried
interrupting and terminating the thread in every way imaginable, but
it seems that I simply can't communicate with the thread once the
process ends.
The only thing that has worked is using ExitThread under
DLL_PROCESS_DETACH if the socket is still open. This has been my
workaround for a while, but it forces the whole program to end
immediately, so I don't like it. Surely there's some way that's more
elegant.
If anyone has any suggestions (or any insights at all as to why this
only happens when there is an ongoing asynchronous read operation),
please let me know.
Thanks.
------------------------------------------------------------------------------
WhatsUp Gold - Download Free Network Management Software
The most intuitive, comprehensive, and cost-effective network
management toolset available today. Delivers lowest initial
acquisition cost and overall TCO of any competing solution.
http://p.sf.net/sfu/whatsupgold-sd
_______________________________________________
asio-users mailing list
https://lists.sourceforge.net/lists/listinfo/asio-users
_______________________________________________
Using Asio? List your project at
http://think-async.com/Asio/WhoIsUsingAsio
Incognito
2011-05-05 21:00:26 UTC
Permalink
Post by Simeon Maxein
This problem is probably solved already, but that change was made after
the 1.5.3 release. You can try using the latest version from the repository
at asio.git.sourceforge.net.

Thanks. The newest version (as of a few days ago) doesn't seem to fix it,
unfortunately. I assume you're talking about this change, by the way:
http://asio.git.sourceforge.net/git/gitweb.cgi?p=asio/asio;a=commit;h=81a6a51c0cb66de6bc77e1fa5dcd46b2794995e4
Simeon Maxein
2011-05-05 21:07:03 UTC
Permalink
Post by Simeon Maxein
This problem is probably solved already, but that change was made
after the 1.5.3 release. You can try using the latest version from the
repository at asio.git.sourceforge.net <http://asio.git.sourceforge.net>.
Thanks. The newest version (as of a few days ago) doesn't seem to fix
it, unfortunately. I assume you're talking about this change, by the
http://asio.git.sourceforge.net/git/gitweb.cgi?p=asio/asio;a=commit;h=81a6a51c0cb66de6bc77e1fa5dcd46b2794995e4
Yes, this is the change that solved the hang in the destructor for me.
Your problem appears to have a different source then.
Marsh Ray
2011-05-01 16:25:38 UTC
Permalink
Post by Incognito
void run()
{
boost::this_thread::sleep(boost::posix_time::seconds(1));
// Additional code here to give io_service some work to do
io_service.run();
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
boost::thread thread(run);
break;
}
}
Yes, I know that creating threads in DllMain is bad form. Yes, I know
that synchronizing threads in DllMain is hard or nearly impossible.
However, I don't have access to the source of the executable, so my only
choice is to create a separate thread when the DLL loads so that the
io_service does not block the main thread.
Creating a thread is actually one of the few things _not_ prohibited by
the loader lock and sometimes it's the only way.

Some suggestions:

* Grab a reference to your own DLL (with LoadLibrary) on yourself and
don't release it until after the thread exits. This is to keep the
thread from having his code unloaded out from under him.

* You must put a try/catch(...) around your C++ code. Not having that
may completely break stack unwinding depending on your compiler flags.

* Step through the boost::thread code to make sure it's not doing
anything disallowed. Which, as you know, is really just about anything.

This is really a place for a bit of plain old C code.

- Marsh
Incognito
2011-05-05 21:10:15 UTC
Permalink
Post by Marsh Ray
Creating a thread is actually one of the few things _not_ prohibited by
the loader lock and sometimes it's the only way.
Post by Marsh Ray
* Grab a reference to your own DLL (with LoadLibrary) on yourself and
don't release it until after the thread exits. This is to keep the thread
from having his code unloaded out from under him.
Post by Marsh Ray
* You must put a try/catch(...) around your C++ code. Not having that may
completely break stack unwinding depending on your compiler flags.
Post by Marsh Ray
* Step through the boost::thread code to make sure it's not doing anything
disallowed. Which, as you know, is really just about anything.
Post by Marsh Ray
This is really a place for a bit of plain old C code.
- Marsh
Thanks. I've tried doing tricks with LoadLibrary to increment the reference
count so that the DLL won't be freed, but I haven't had much success. The
last parameter in DllMain (fdwReason) is non-NULL when the program hangs,
meaning that the process is terminating (according to MSDN). I believe this
also means that all of the threads have already been terminated, which would
explain why I can't communicate with the thread running the io_service. I
think what you're suggesting will only work if FreeLibrary is called before
the process terminates, though I could be wrong.

I've tried putting try-catch blocks around all of my code, but it doesn't
seem to do anything. My DLL isn't throwing exceptions; it's just hanging
when the io_service is destructed.

I'm not really sure if boost::thread is the culprit, since this problem only
cropped up once I started using Asio for all of my networking.
Marsh Ray
2011-05-05 21:39:47 UTC
Permalink
Post by Marsh Ray
Creating a thread is actually one of the few things _not_ prohibited
by the loader lock and sometimes it's the only way.
Post by Marsh Ray
* Grab a reference to your own DLL (with LoadLibrary) on yourself and
don't release it until after the thread exits. This is to keep the
thread from having his code unloaded out from under him.
Post by Marsh Ray
* You must put a try/catch(...) around your C++ code. Not having that
may completely break stack unwinding depending on your compiler flags.
Post by Marsh Ray
* Step through the boost::thread code to make sure it's not doing
anything disallowed. Which, as you know, is really just about anything.
Post by Marsh Ray
This is really a place for a bit of plain old C code.
- Marsh
Thanks. I've tried doing tricks with LoadLibrary to increment the
reference count so that the DLL won't be freed, but I haven't had much
success. The last parameter in DllMain (fdwReason) is non-NULL when the
program hangs, meaning that the process is terminating (according to
MSDN). I believe this also means that all of the threads have already
been terminated, which would explain why I can't communicate with the
thread running the io_service.
Yeah, you might not be able to pass messages between threads as you're
getting unloaded.

Also, Windows async IO operations get canceled when the thread that
queued them exits. Are you using your own threads for this or initiating
IO when called from someone else's thread pool?
I think what you're suggesting will only
work if FreeLibrary is called before the process terminates, though I
could be wrong.
I don't know how your DLL is getting loaded, but it can get unloaded
when it's handle count drops to 0. Better not have any code executing in
it when that happens.
I've tried putting try-catch blocks around all of my code, but it
doesn't seem to do anything.
It's still required.
My DLL isn't throwing exceptions; it's just
hanging when the io_service is destructed.
What's calling the destructor? DllMain(PROCESS_DETACH)?

What's the call stack when it hangs?

WinDbg (available from Microsoft) is the tool for this sort of thing.
I'm not really sure if boost::thread is the culprit, since this problem
only cropped up once I started using Asio for all of my networking.
You should not run significant code under the loader lock. This means no
global/static objects with nontrivial destructors in your DLL.

Can you add an entry point to your DLL and arrange to get called before
the process starts getting torn down? That way you can disconnect
gracefully.

- Marsh
Incognito
2011-05-06 04:59:34 UTC
Permalink
Also, Windows async IO operations get canceled when the thread that queued
them exits.
If this is true, then my thread must have not completely exited yet, because
I found something very interesting:

If I enable ASIO_ENABLE_CANCELIO and call cancel() on the socket object
Incognito
2011-05-06 06:37:34 UTC
Permalink
By the way, defining ASIO_DISABLE_IOCP seems to fix the problem as well. I
don't fully understand the implications of disabling the I/O completion
ports, however. Is this undesirable? Will it greatly affect performance in
my case (one io_service in a single thread with no strands)? I might just
consider distributing a Windows XP version of the client with IOCP disabled
since cancel() does not work properly on that operating system. If there is
a significant argument against doing this, however, then I suppose I'll need
to evaluate my options again.
Marat Abrarov
2011-05-06 07:12:44 UTC
Permalink
Post by Incognito
By the way, defining ASIO_DISABLE_IOCP seems to fix the problem as well.
I don't fully understand the implications of disabling the I/O completion ports, however.
Is this undesirable? Will it greatly affect performance in my case (one io_service in a single thread with no
strands)?
Post by Incognito
I might just consider distributing a Windows XP version of the client with IOCP
disabled since cancel() does not work properly on that operating system.
If there is a significant argument against doing this, however, then I suppose I'll need to evaluate my options again.
According to http://www.ozon.ru/context/detail/id/116668/ your problem is in usage of DllMain.
I think, until you doesn't use it and switch to an explicit entry point, you won't make a stable solution
(as I red - you can't do it - so there is no solution with such conditions).

Disabling IOCP usage may not guarantee that you won't have any other problems with all of the Windows OS family.
The problem is related to the usage of WinSock in DllMain.

Regards,
Marat Abrarov.
Marat Abrarov
2011-05-06 07:37:32 UTC
Permalink
Sorry for the wrong link.
Here's the right one: http://www.amazon.com/Windows-via-Pro-Jeffrey-Richter/dp/0735624240/ref=ntt_at_ep_dpt_5
Post by Marat Abrarov
According to http://www.ozon.ru/context/detail/id/116668/ your problem is in usage of DllMain.
I think, until you doesn't use it and switch to an explicit entry point, you won't make a stable
solution (as I red - you can't do it - so there is no solution with such conditions).
Disabling IOCP usage may not guarantee that you won't have any other problems with all of the Windows
OS family.
The problem is related to the usage of WinSock in DllMain.
Regards,
Marat Abrarov.
Roger Austin
2011-05-06 09:31:27 UTC
Permalink
Post by Incognito
Okay, unless you couldn't tell by the subject, my problem is somewhat of a
complicated one. I have a DLL that acts as a TCP client using Asio. Everything
works great while the program is running, but unfortunately, sometimes the
program hangs upon exit. I've found that this only happens if the server does
not close the socket before the program exits. If the socket is still open
when the user tries to exit, the program hangs.Upon further inspection, the
program _only_ hangs if the io_service contains a handler to async_read (or
async_read_some). I don't know why this is the case, but it happens every
time.Here's a very small portion of my code:void run()
Post by Incognito
{
        boost::this_thread::sleep(boost::posix_time::seconds(1));        //
Additional code here to give io_service some work to do
Post by Incognito
        io_service.run();
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{    switch (fdwReason)    {        case DLL_PROCESS_ATTACH:           
boost::thread thread(run);
Post by Incognito
            break;    }}Some remarks:Yes, I know that creating threads in
DllMain is bad form. Yes, I know that synchronizing threads in DllMain is hard
or nearly impossible. However, I don't have access to the source of the
executable, so my only choice is to create a separate thread when the DLL
loads so that the io_service does not block the main thread.I've tried doing a
number of things under DLL_PROCESS_DETACH. I've tried using io_service.stop(),
but that does nothing. I've tried putting the io_service in a scoped_ptr and
resetting it, but that causes the io_service destructor to hang as well. I've
tried interrupting and terminating the thread in every way imaginable, but it
seems that I simply can't communicate with the thread once the process
ends.The only thing that has worked is using ExitThread under
DLL_PROCESS_DETACH if the socket is still open. This has been my workaround
for a while, but it forces the whole program to end immediately, so I don't
like it. Surely there's some way that's more elegant.If anyone has any
suggestions (or any insights at all as to why this only happens when there is
an ongoing asynchronous read operation), please let me know.Thanks.
I don’t want to waste your time by suggesting the obvious, but have you tried
(in DLL_PROCESS_DETACH) simply closing the socket and joining the thread that
you created in DLL_PROCESS_ATTACH. Closing the socket should cause the
async_read operation to complete (with an error) and call your handler (from
the thread that is running the io_service). After your handler returns (and
provided it does not give the io_service any more work to do) then your thread
will return from io_service::run and terminate normally, whereupon the main
thread will resume its task of shutting down the program.

Having said that, destroying the io_service should also cause any pending
operations to complete and your thread to exit from io_service::run, so I am
not clear why your program is still hanging when you do that. One reason
(mentioned in a later reply to this topic) may be that cancelling async
operations created by other threads does not work on older versions of Windows
(by old, I mean XP). In that case, the method described above should work.

Roger
Incognito
2011-05-08 17:16:44 UTC
Permalink
I don’t want to waste your time by suggesting the obvious, but have you
tried
(in DLL_PROCESS_DETACH) simply closing the socket and joining the thread that
you created in DLL_PROCESS_ATTACH. Closing the socket should cause the
async_read operation to complete (with an error) and call your handler (from
the thread that is running the io_service). After your handler returns (and
provided it does not give the io_service any more work to do) then your thread
will return from io_service::run and terminate normally, whereupon the main
thread will resume its task of shutting down the program.
Having said that, destroying the io_service should also cause any pending
operations to complete and your thread to exit from io_service::run, so I am
not clear why your program is still hanging when you do that. One reason
(mentioned in a later reply to this topic) may be that cancelling async
operations created by other threads does not work on older versions of Windows
(by old, I mean XP). In that case, the method described above should work.
Roger
Yes, I have tried that before. I called stop() on the io_service and join()
on the boost::thread as you suggested, but join() returns immediately in
DLL_PROCESS_DETACH.

I guess I'll just go the route of disabling IOCP since it seems to be the
only elegant solution for now.
Roger Austin
2011-05-09 00:07:13 UTC
Permalink
 Yes, I have tried that before. I called stop() on the io_service and join()
on the boost::thread as you suggested, but join() returns immediately in
DLL_PROCESS_DETACH.I guess I'll just go the route of disabling IOCP since it
seems to be the only elegant solution for now.
I was actually suggesting that you call close() on the socket, not stop() on
the io_service. The fact that you don't get the hang (on Vista and above) when
you define ASIO_ENABLE_CANCELIO and call cancel() on the socket suggests to me
that the asio library is working correctly, despite the unusual context you
are calling from. Stopping the service does not remove the outstanding work
(the async_read) and this then causes the io_service destructor to enter the
code block that includes the hanging call to GetQueuedCompletionStatus.
Closing the socket (and thereby calling the handler) should result in the
outstanding work being cleared, allowing the destructor to bypass that code
block and exit normally. Of course it is possible that stop() on XP suffers
from the same problem as cancel() but I think it worth trying.

Loading...