Proper way to shut down console app.

Get answers to all your basic programming questions. No Ogre questions, please!
Post Reply
User avatar
mkultra333
Gold Sponsor
Gold Sponsor
Posts: 1865
Joined: Sun Mar 08, 2009 5:25 am
x 17

Proper way to shut down console app.

Post by mkultra333 » Wed Dec 10, 2014 7:53 am

Greetings all, been a while since I've been on the forum but I'm still working away at my Ogre game.

I'm just starting to work on a server. I'm building it as just a plain VC9 console application because I want it to be as threadbare as possible, and I figured a console would be nice and simple.

However I seem to have run into a very weird and fundamental property of consoles which makes me wonder how the hell anyone ever got them to do anything useful in the first place.

The problem is that at any moment someone might close the console, by for instance simply clicking the 'X' box in the top right corner, or other methods. When this happens, I need to do a proper shutdown, to unload things, to send messages to all the players that the console is closing down. But damned if I can find a reliable way to do that.

Basically there seem to be two main methods I've read.

The first is to use the atexit(SomeFunction) function. In theory this will call SomeFunction() when the console closes. But I have found it to be incredibly difficult to use and unreliable. dlls are already unloaded by the time the function is called. Other functions can mess it up (for instance, I use easylogging++, a header only logger, and if I use any easylogging++ functions in the exit function, the exit function seems to just be ignored.

I've also seen that atexit() often fails if the main loop is busy. So the more processing going on in the mainloop, the more likely atexit will simply do nothing. I tested this by printing some stuff to the console in the mainloop, at the same time as closing the app. Print too much, and atexit seems to get skipped.

The second method is to use SetConsoleCtrlHandler and receive the shutdown messages and do the shutdown in the handler. But this is just as big a minefield, since the handler is launched in it's own thread, meaning for a moment both the standard code and the shutdown code can be running simultaneously. So I might be trying to shutdown and free some memory or object at the same time the server is trying to perform normal processing on that memory.

Ugh. Seems like a ridiculous nightmare. How do I cleanup when a console gets shut down by the user in an unexpected way?

Edit: Tried something with the SetConsoleCtrlHandler method. Put a mutex around the handler and a mutex in the mainloops, so in theory only one of them can run at a time. Result is that sometimes it works, sometimes it doesn't, same as before. In otherwords, sometimes the shutdown code gets executed, sometimes it simply gets skipped.
0 x
"In theory there is no difference between practice and theory. In practice, there is." - Psychology Textbook.

User avatar
Zonder
Ogre Magi
Posts: 1129
Joined: Mon Aug 04, 2008 7:51 pm
Location: Manchester - England
x 20

Re: Proper way to shut down console app.

Post by Zonder » Wed Dec 10, 2014 10:41 am

Yes consoles are evil for this :)

When doing server development I have found it best to create the server as a dll. And export 2 C api calls for Start and Stop. Doing this you can use a windows app/console/windows service/tray applciation as the host.

I generally make servers as a windows service and have a console just for debugging I never bother to work round the shutdown issue since in production the server is handled by a windows service.

This doesn't answer your question but though some insight into how other people do it might help.
0 x
There are 10 types of people in the world: Those who understand binary, and those who don't...

User avatar
Herb
Orc
Posts: 412
Joined: Thu Jun 04, 2009 3:21 am
Location: Kalamazoo,MI
x 1

Re: Proper way to shut down console app.

Post by Herb » Wed Dec 10, 2014 3:35 pm

Here's a snippet I use. Not sure if I've tried closing a window by hitting the X in the corner, but this is really helpful for like "Ctrl-C" where it shuts down nicely. Hope it helps.

Code: Select all

bool gTerminate = false;

void Terminate(int)
{
    gTerminate = true;
}

int main(int argc, char **argv)
{
    signal(SIGINT, Terminate);
    signal(SIGTERM, Terminate);
    signal(SIGABRT, Terminate);

    printf("\nInitializing Service...");

    Service foo;
    
    foo.init();

    while(foo.getKeepRunning())
    {
        if (gTerminate)
        {
            printf("\nShutting down...");
            foo.shutdown();  // this will flip a flag in foo where the while condition will return false next call
        }
        else { foo.update(); }
    }

   printf("Done\nGood Bye!\n");
}
0 x

User avatar
mkultra333
Gold Sponsor
Gold Sponsor
Posts: 1865
Joined: Sun Mar 08, 2009 5:25 am
x 17

Re: Proper way to shut down console app.

Post by mkultra333 » Thu Dec 11, 2014 5:02 am

Thanks guys. It boggles my mind that the OS could handle things this badly.

I ended up getting the mutex method to work, but with the catch that IF the mainloop takes too long processing anything at shutdown, the handler fails. It seems like there's some sort of internal OS timer that's only willing to wait so long before coming down hard and killing everything. I also noticed it interacts very badly with the easylogging++ header.

Zonder, that dll trick sounds good but I'd rather not have to mess about with a dll. At least I'm glad I'm not imagining this problem and it's a real (and ridiculous) issue.

Herb, hadn't seen that one either. I might run a few tests on that method and see how it goes.

Worst case, I may just change the project to a win32 app instead of a console.
0 x
"In theory there is no difference between practice and theory. In practice, there is." - Psychology Textbook.

User avatar
mkultra333
Gold Sponsor
Gold Sponsor
Posts: 1865
Joined: Sun Mar 08, 2009 5:25 am
x 17

Re: Proper way to shut down console app.

Post by mkultra333 » Thu Dec 11, 2014 6:05 am

Herb, tried your method, but tests indicate it acts much the same as using a CtrlHandler. The additional shutdown code must be part of the Terminate() function or it gets ignored. And it appears that Terminate gets called in it's own thread, I can see cases where Terminate code is running at the same time as the mainloop code.

However with a couple of tweaks it seems to work.

My terminate function is like this:

Code: Select all

bool gTerminate = false;
bool gMainLoopEnded = false ;
void Terminate(int)
{
	// let mainloop know it's time to end.  
	// May happen halfway through the mainloop, 
	// in which case mainloop will keep going until it hits the top of the loop again.
	gTerminate = true;

	// since we may have hit the middle of the mainloop, wait for the mainloop to end.
	while(!gMainLoopEnded)
		LogMessage("Wait for mainloop...") ;

	// actual shutdown code goes here...
	
	// release the shared memory object
	LogMessage("Starting shutdown procedures.") ; 
	if(!boost::interprocess::shared_memory_object::remove("shared_memory"))
		LogMessage("Remove SharedMemory Success.") ;
	else
		LogMessage("Remove SharedMemory Failure.") ;
			
	// clear the timer period we set at the beginning.
	timeEndPeriod(wTimerRes) ;
	
	LogMessage("Closing down.\n\n\n") ;

}
I had to add some more signals to detect the 'x' corner press:

Code: Select all

	signal(SIGINT, Terminate);      /* interrupt */
	signal(SIGILL, Terminate);      /* illegal instruction - invalid function image */
	signal(SIGFPE, Terminate);      /* floating point exception */
	signal(SIGSEGV, Terminate);     /* segment violation */
	signal(SIGTERM, Terminate);     /* Software termination signal from kill */
	signal(SIGBREAK, Terminate);    /* Ctrl-Break sequence */
	signal(SIGABRT, Terminate);     /* abnormal termination triggered by abort call */
	signal(SIGABRT_COMPAT, Terminate);/* SIGABRT compatible with other platforms, same as SIGABRT */
And then mainloop ends up like this:

Code: Select all

	do
	{
		if(!gTerminate)
		{
			LogMessage("********** MAINLOOP TOP **********") ;
			for(int i=0 ; i<20 ; i++)
					LogMessage("Do Stuff") ;
		}
		else
			gMainLoopEnded=true ;
		...
Various experiments seem to indicate this works. Here's an example output.

Code: Select all

11/12/2014 13:03:30.773 INFO  ********** MAINLOOP TOP **********
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.773 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.789 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  ********** MAINLOOP TOP **********
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.820 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Wait for mainloop...
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Wait for mainloop...
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Wait for mainloop...
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.836 INFO  Wait for mainloop...
11/12/2014 13:03:30.836 INFO  Do Stuff
11/12/2014 13:03:30.851 INFO  Wait for mainloop...
11/12/2014 13:03:30.851 INFO  Do Stuff
11/12/2014 13:03:30.851 INFO  Wait for mainloop...
11/12/2014 13:03:30.851 INFO  Do Stuff
11/12/2014 13:03:30.851 INFO  Wait for mainloop...
11/12/2014 13:03:30.851 INFO  Do Stuff
11/12/2014 13:03:30.851 INFO  Wait for mainloop...
11/12/2014 13:03:30.851 INFO  Do Stuff
11/12/2014 13:03:30.851 INFO  Wait for mainloop...
11/12/2014 13:03:30.851 INFO  Do Stuff
11/12/2014 13:03:30.867 INFO  Wait for mainloop...
11/12/2014 13:03:30.867 INFO  Do Stuff
11/12/2014 13:03:30.867 INFO  Wait for mainloop...
11/12/2014 13:03:30.867 INFO  Do Stuff
11/12/2014 13:03:30.867 INFO  Wait for mainloop...
11/12/2014 13:03:30.867 INFO  Do Stuff
11/12/2014 13:03:30.867 INFO  Wait for mainloop...
11/12/2014 13:03:30.867 INFO  Starting shutdown procedures.
11/12/2014 13:03:30.883 INFO  Remove SharedMemory Success.
11/12/2014 13:03:30.883 INFO  Closing down.
Seems to work even if I make mainloop much busier, say with an internal print loop of 200 instead of 20.
0 x
"In theory there is no difference between practice and theory. In practice, there is." - Psychology Textbook.

User avatar
Herb
Orc
Posts: 412
Joined: Thu Jun 04, 2009 3:21 am
Location: Kalamazoo,MI
x 1

Re: Proper way to shut down console app.

Post by Herb » Thu Dec 11, 2014 4:59 pm

Well, glad to see you got it to work, and now I can use your modifications in my project too. :)
0 x

Post Reply