Reputation: 85
I'm trying to exit an accept()
call after a timeout occurs using an alarm()
signal. The signal registers as excepted but isn't interrupting the blocking accept()
process. How can I make the signal break the loop or interrupt the blocking process?
volatile sig_atomic_t time_out_flag = false;
void handleSig(int sig)
{
std::cout << "signal\n";
time_out_flag = true;
return;
}
void Server::start(ClientHandler &ch) // throw(const char *)
{
t = new std::thread([&] { // Main thread
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(_port);
if (bind(_socket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) // Bind socket (socket, sockaddr, addrlen)
throw("Failed to bind to port.\n");
if (listen(_socket, 2) < 0) // Passivly listen on socket (socket, backlog aka max users queued)
throw("Failed to listen on socket.\n");
auto addrlen = sizeof(sockaddr);
signal(SIGALRM, handleSig); // What to do after alarm(k)
while (!time_out_flag)
{
alarm(3);
int connection = accept(_socket, (struct sockaddr *)&sockaddr, (socklen_t *)&addrlen);
if (connection < 0) {
throw("accept() error\n");
}
alarm(0);
ch.handle(connection);
}
std::cout << "after";
});
}
I would really like to work with accept()
and not select()
if possible.
UPDATE
I've changed signal()
to sigaction()
as was recommended, but accept()
continues blocking the thread. This is the revised code:
volatile sig_atomic_t alarmed = 0;
void handle_alarm(int)
{
std::cout << "alarm\n";
alarmed = 1;
return;
}
void Server::start(ClientHandler &ch) // throw(const char *)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGALRM);
if (pthread_sigmask(SIG_BLOCK, &mask, nullptr) < 0)
throw("sigaction pthread failed\n");
#ifdef USE_SIGACTION
// Set up sigaction() with alarmer
struct sigaction sigbreak;
std::memset(&sigbreak, 0, sizeof sigbreak);
sigbreak.sa_handler = &handle_alarm;
if (sigaction(SIGALRM, &sigbreak, NULL) != 0)
throw("sigaction() failed\n");
#else
if (signal(SIGALRM, handle_alarm) == SIG_ERR)
throw("signal() failed\n");
#endif
t = new std::thread([&] { // Main thread
if (pthread_sigmask(SIG_UNBLOCK, &mask, nullptr) < 0)
std::cout << "alarmos\n";
struct sockaddr_in server_sockaddr;
struct sockaddr_in sockaddr = {0};
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(_port);
if (bind(_socket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) // Bind socket (socket, sockaddr, addrlen)
throw("Failed to bind to port.\n");
if (listen(_socket, 2) < 0) // Passivly listen on socket (socket, backlog aka max users queued)
throw("Failed to listen on socket.\n");
auto addrlen = sizeof(sockaddr);
while (1)
{
if (alarmed)
return;
try
{
alarm(3);
int connection = accept(_socket, (struct sockaddr *)&sockaddr, (socklen_t *)&addrlen);
if (alarmed)
return;
if (connection == -1)
throw("accept() error\n");
alarm(0);
ch.handle(connection);
}
catch (const char *msg)
{
throw(msg);
}
}
});
alarm(3);
}
UPDATE + SOLUTION
defining USE_SIGACTION
does the trick and interrupts accept()
as suggested in the answer.
Upvotes: 1
Views: 1297
Reputation: 52354
The issue is that signal()
installs signal handlers in such a way that syscalls like accept()
are resumed after a signal is caught*. Use sigaction()
instead to disable that behavior:
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <signal.h>
#include <unistd.h>
//#define USE_SIGACTION
volatile sig_atomic_t alarmed = 0;
void handle_alarm(int) {
alarmed = 1;
}
int main(void) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGALRM);
if (pthread_sigmask(SIG_BLOCK, &mask, nullptr) < 0) {
std::perror("pthread_sigmask");
return 1;
}
#ifdef USE_SIGACTION
struct sigaction alarmer;
std::memset(&alarmer, 0, sizeof alarmer);
alarmer.sa_handler = handle_alarm;
// For resumable syscalls you'd have
// alarmer.sa_flags = SA_RESTARTT;
// but we don't want that.
if (sigaction(SIGALRM, &alarmer, nullptr) < 0) {
std::perror("sigaction");
return 1;
}
std::cout << "Using sigaction()\n";
#else
if (signal(SIGALRM, handle_alarm) == SIG_ERR) {
std::perror("signal");
return 1;
}
std::cout << "Using signal()\n";
#endif
std::thread t{[&mask](){
char dummy[5];
if (pthread_sigmask(SIG_UNBLOCK, &mask, nullptr) < 0) {
perror("pthread_sigmask");
std::exit(1);
}
std::cout << "Waiting...\n";
auto bytes = read(0, dummy, sizeof dummy);
if (bytes < 0) {
std::perror("read");
if (alarmed) {
std::cerr << "SIGALRM was caught.\n";
return;
}
std::exit(1);
}
}};
alarm(1);
t.join();
return 0;
}
Example usage:
$ g++ -pthread -O -Wall -Wextra -DUSE_SIGACTION foo.cpp
$ ./a.out
Using sigaction()
Waiting...
read: Interrupted system call
SIGALRM was caught.
$ g++ -pthread -O -Wall -Wextra foo.cpp
$ ./a.out
Using signal()
Waiting...
^C
Notes on signal()
: Whether it turns on restartable syscalls or not isn't specified by POSIX, and different OSes can and do differ. The Linux/Glibc behavior is described in its signal(2)
man page:
By default, in glibc 2 and later, the
signal()
wrapper function does not invoke the kernel system call. Instead, it callssigaction(2)
using flags that supply BSD semantics. This default behavior is provided as long as a suitable feature test macro is defined:_BSD_SOURCE
on glibc 2.19 and earlier or_DEFAULT_SOURCE
in glibc 2.19 and later. (By default, these macros are defined; seefeature_test_macros(7)
for details.) If such a feature test macro is not defined, thensignal()
provides System V semantics.
BSD semantics include restarting syscalls, System V ones do not. The best approach, as suggested by the documentation, is to just never use signal()
and stick with sigaction()
and friends.
On threads and signals: When a multi-threaded application receives a signal that isn't directed at a particular thread, it is delivered to a random thread that doesn't have that signal blocked. So this code first blocks SIGALRM
and then unblocks it in the example blocking thread. If you don't do something like this, it might not be your accept()
that gets interrupted, but something in a different thread.
Upvotes: 2
Reputation: 16
Without select()
you can just close socket in another thread for example. In this case, the accept()
will end. But this case is not Normal.
By the way your code has issue with throw exceptions. In this situation within thread your program will be terminated. To avoid that you should write try-catch inside thread. For example:
t = new std::thread([&] { // Main thread
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(_port);
try
{
....
}
catch(const char* msg)
{
std::cout << msg << std::endl;
}});
It is good practice to use exception classes from namespace std instead of strings.
Upvotes: 1