16

I'm writing a cross-platform server program in C++ using Boost.Asio. Following the HTTP Server example on this page, I'd like to handle a user termination request without using implementation-specific APIs. I've initially attempted to use the standard C signal library, but have been unable to find a design pattern suitable for Asio. The Windows example's design seems to resemble the signal library closest, but there's a race condition where the console ctrl handler could be called after the server object has been destroyed. I'm trying to avoid undefined behavior as specified by the C++ standard.

Is there a standard (and correct) way to stop the server?

To illustrate problems with using the C signal library:

#include <csignal>
#include <functional>
#include <boost/asio.hpp>

using std::signal;
using boost::asio::io_service;

namespace
{
    std::function<void ()> sighandler;
}

extern "C"
{
    static void handle_signal(int);
}

void handle_signal(int)
{
    // error - undefined behavior
    sighandler();
}

int main()
{
    io_service s;
    sighandler = std::bind(&io_service::stop, &s);
    auto old_sigint = signal(SIGINT, &handle_signal);
    if (old_sigint == SIG_IGN)
        // race condition?  raise SIGINT before I can set ignore back
        signal(SIGINT, SIG_IGN);
    auto old_sigterm = signal(SIGTERM, &handle_signal);
    if (old_sigterm == SIG_IGN)
        // race condition?  raise SIGTERM before I can set ignore back
        signal(SIGTERM, SIG_IGN);
    s.run();
    // reset signals so I can clear reference to io_service
    if (old_sigterm != SIG_IGN)
        signal(SIGTERM, SIG_DFL);
    if (old_sigint != SIG_IGN)
        signal(SIGINT, SIG_DFL);
    // clear reference to io_service, but can I be sure that handle_signal
    // isn't being executed?
    sighandler = nullptr;
    // io_service is destroyed
}
Deanie
  • 2,316
  • 2
  • 19
  • 35
Timothy003
  • 2,348
  • 5
  • 28
  • 33
  • @Timothy are you asking how to install a signal handler for a signal like SIGINT? Or, what to do in that signal handler to shutdown your HTTP server? – Sam Miller Jan 09 '11 at 15:21
  • @Sam added a csignal design example – Timothy003 Jan 09 '11 at 15:53
  • @Timothy you cannot safely invoke `io_service::stop` from within your signal handler, even if it is wrapped in a `boost::function`. Doing so results in undefined behavior. Look at [my answer](http://stackoverflow.com/questions/4639909/standard-way-to-perform-a-clean-shutdown-with-boost-asio/4639977#4639977) for a list of functions you can call from within a signal handler. – Sam Miller Jan 09 '11 at 16:02
  • @Timothy I retagged your question as `c++0x` since it looks like you are using the `auto` keyword. – Sam Miller Jan 09 '11 at 16:08
  • @Sam added comment to show that UB, thanks – Timothy003 Jan 09 '11 at 16:26
  • @Timothy undefined behavior means don't do what your doing. Even if it appears to work, it will randomly behave differently. You should use sigwait or only invoke async-signal-safe functions from within your signal handler. – Sam Miller Jan 09 '11 at 16:42
  • Although my example is C++0x code, the question doesn't really ask about C++0x. Perhaps the `c` tag would be better, since `csignal` is defined in the C standard. – Timothy003 Jan 09 '11 at 21:19

2 Answers2

20

Version 1.5.3 of Boost.Asio (to be integrated in upcoming boost 1.47 release?) has the signal_set class:

#include <boost/asio/signal_set.hpp>

// Register signal handlers so that the daemon may be shut down. You may
// also want to register for other signals, such as SIGHUP to trigger a
// re-read of a configuration file.
boost::asio::signal_set signals(io_service, SIGINT, SIGTERM);
signals.async_wait(
    boost::bind(&boost::asio::io_service::stop, &io_service));

EDIT

Now included in Boost version 1.47

CharlesB
  • 86,532
  • 28
  • 194
  • 218
  • Interesting; Asio uses static variables to access data from a signal handler. Is that not UB? – Timothy003 Jul 18 '11 at 07:39
  • 2
    By just stopping io_service and leaving work undone, your application is subjected to random segfaults once the destructor of io_service is called. – Kenji Jul 30 '17 at 20:42
5

The posix example HTTP server is a good way to cleanly shutdown. One thread invokes io_service::run while another waits for a signal with sigwait.

Alternatively, you can install a signal handler but that it slightly trickier. There's a very small list of async-signal-safe functions you can invoke from within a signal handler.

The routine handler must be very careful, since processing elsewhere was interrupted at some arbitrary point. POSIX has the concept of "safe function". If a signal interrupts an unsafe function, and handler calls an unsafe function, then the behavior is undefined. Safe functions are listed explicitly in the various standards.

The POSIX.1-2003 list is

_Exit() _exit() abort() accept() access() aio_error() aio_return() aio_suspend() alarm() bind() cfgetispeed() cfgetospeed() cfsetispeed() cfsetospeed() chdir() chmod() chown() clock_gettime() close() connect() creat() dup() dup2() execle() execve() fchmod() fchown() fcntl() fdatasync() fork() fpathconf() fstat() fsync() ftruncate() getegid() geteuid() getgid() getgroups() getpeername() getpgrp() getpid() getppid() getsockname() getsockopt() getuid() kill() link() listen() lseek() lstat() mkdir() mkfifo() open() pathconf() pause() pipe() poll() posix_trace_event() pselect() raise() read() readlink() recv() recvfrom() recvmsg() rename() rmdir() select() sem_post() send() sendmsg() sendto() setgid() setpgid() setsid() setsockopt() setuid() shutdown() sigaction() sigaddset() sigdelset() sigemptyset() sigfillset() sigismember() signal() sigpause() sigpending() sigprocmask() sigqueue() sigset() sigsuspend() sleep() socket() socketpair() stat() symlink() sysconf() tcdrain() tcflow() tcflush() tcgetattr() tcgetpgrp() tcsendbreak() tcsetattr() tcsetpgrp() time() timer_getoverrun() timer_gettime() timer_settime() times() umask() uname() unlink() utime() wait() waitpid() write().

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • 2
    I've found a list of functions on [this page.](http://linux.die.net/man/2/signal) However, it is POSIX-specific. Does the C standard say anything different about this stuff? – Timothy003 Jan 09 '11 at 17:53
  • @Timothy that is the correct link, I've edited my answer. I'm not overly familiar with the C standard, though I've never had trouble using only the functions listed in the signal man page from within a signal handler. – Sam Miller Jan 09 '11 at 18:19