Heap corruption during signal processing

I have a multi-threaded Windows server that I'm working on, and found that after a certain set of criteria, when I close the program via control-c, it fails. If my server accepts packets from the client and then I use control-c, it fails. If I started my server, let it wait for packets for a period of time, and then use control-c, it will exit correctly.

What is strange about this is that all my threads report that they exit with status 0, even when the program throws an exception (unless that is normal).

First-chance exception at 0x75A16DA7 (kernel32.dll) in server.exe: 0x40010005: Control-C. HEAP[server.exe]: HEAP: Free Heap block 96a818 modified at 96a908 after it was freed server.exe has triggered a breakpoint. The thread 0xc34 has exited with code 0 (0x0). The thread 0x1c64 has exited with code 0 (0x0). The thread 0xdbc has exited with code 0 (0x0). The thread 0x117c has exited with code 0 (0x0). The thread 0x1444 has exited with code 0 (0x0). The thread 0x1d60 has exited with code 0 (0x0). The thread 0x798 has exited with code 0 (0x0). The thread 0x700 has exited with code 0 (0x0). The thread 0x1bbc has exited with code 0 (0x0). The thread 0x1b74 has exited with code 0 (0x0). The program '[7528] server.exe' has exited with code 0 (0x0). 

The piece of code that seems to be causing this problem:

 void handleSignal(int sig) { std::unique_lock<std::mutex> lock(signalMutex); // <-- comment out and it doesn't crash signaled = true; _receivedSignal = sig; signalHandlerCondition.notify_one(); // <-- comment out and it doesn't crash } 

Mutex and condition variables are global:

 std::mutex signalMutex; std::condition_variable signalHandlerCondition; 

I have a dedicated signal processing thread that tries to gracefully shut down the server when it is notified of this event.

 void run() { while (gContinueRunning && _continueRunning) { std::unique_lock<std::mutex> lock(signalMutex); signalHandlerCondition.wait(lock); if (signaled) { gContinueRunning = false; signaled = false; Server::stop(); } } } 

Of course, when I comment on breaking lines, the program does not respond to signals at all. I could wait, because I do not need to notify the signal processing cycle that it has a new signal, but I do not think this is the best way.

I read something from MSDN about signals:

When a CTRL + C interrupt occurs, Win32 operating systems generate a new one to specifically handle this interrupt.

Because signal handler routines are usually called asynchronously when an interrupt occurs, your signal handler function can take control when the run-time job is incomplete and in an unknown state.

I'm honestly not sure if this applies. If so, does this mean that my mutex may or may not exist when the signal handler is called?

So what is the best way to approach the signals? What problem did I encounter right here?


Edit: just clear a few things:

 void start() { _receivedSignal = 0; _continueRunning = true; // start thread std::thread signalHandlerThread(run); _signalHandlerThread = std::move(signalHandlerThread); // register signals signal(SIGABRT, SignalHandler::handleSignal); signal(SIGTERM, SignalHandler::handleSignal); signal(SIGINT, SignalHandler::handleSignal); } 

Even after removing the mutex, it seems that the program is moving a little further - although only until the main end.

 msvcr110d.dll!operator delete(void * pUserData) Line 52 C++ server.exe!std::_Ref_count<User>::_Destroy() Line 161 C++ server.exe!std::_Ref_count_base::_Decref() Line 120 C++ server.exe!std::_Ptr_base<User>::_Decref() Line 347 C++ server.exe!std::shared_ptr<User>::~shared_ptr<User>() Line 624 C++ server.exe!std::pair<unsigned int const ,std::shared_ptr<User> >::~pair<unsigned int const ,std::shared_ptr<User> >() C++ server.exe!std::pair<unsigned int const ,std::shared_ptr<User> >::`scalar deleting destructor'(unsigned int) C++ server.exe!std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 624 C++ server.exe!std::allocator_traits<std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > & _Al, std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 758 C++ server.exe!std::_Wrap_alloc<std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 909 C++ server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::_Erase(std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> * _Rootnode) Line 2069 C++ server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::clear() Line 1538 C++ server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::erase(std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<unsigned int const ,std::shared_ptr<User> > > > > _First, std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<unsigned int const ,std::shared_ptr<User> > > > > _Last) Line 1512 C++ server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::_Tidy() Line 2216 C++ server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::~_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >() Line 1190 C++ server.exe!std::map<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > > >::~map<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > > >() C++ server.exe!`dynamic atexit destructor for 'User::_usersListBySession''() C++ msvcr110d.dll!doexit(int code, int quick, int retcaller) Line 584 C msvcr110d.dll!exit(int code) Line 394 C server.exe!__tmainCRTStartup() Line 549 C server.exe!mainCRTStartup() Line 377 C 

It seems that all other topics have disappeared. I guess I probably made a mistake elsewhere.

Thanks for clearing the security of the alarm function.


Edit 2: It seems like an unrelated shared pointer is causing me problems! I am glad to see something good of it, though.

Edit 3: A completely related issue caused the crash. All is well in the world now.

+4
source share
1 answer

I suspect this is because your debugger is handling the Ctrl-C event.

This MSDN article contains the following:

If the console process is being debugged and the CTRL + C signals have not been disabled, the system throws a DBG_CONTROL_C exception. This exception occurs only in the interests of the debugger, and the application should never use an exception handler to handle it. If the debugger handles the exception, the application will not notice CTRL + C, with one exception: the alarm wait will end. If the debugger throws an exception when it is unhandled, CTRL + C is passed to the console process and treated as a signal, as discussed earlier.

You can configure event filters to "output - not handled" so your application can handle this. I have added screenshots on how to install this in WinDbg. Visual Studio lists this in the "Win32 Exceptions" section.

WinDBG Event Filters

Edit: Also, I have to add that trying to lock a mutex in an event handler is considered bad practice. If the mutex is already received when the signal handler is called, it causes a deadlock, because the application cannot resume until the signal handler terminates, and the signal handler cannot exit until the mutex is received. Although this is unlikely in your use case, CTRL-C during false awakening or 2 CTRL-C back to back can cause a dead end.

+2
source

All Articles