Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle a SIGPIPE error inside the object that generated it?

Tags:

c++

c

sockets

send

I have two applications, one server and other client, both written in C++ and Qt, but both of them also uses a C library that uses C socket methods to perform a socket communication between them (and this all in Linux).

When both of them are connected and I close the client, when the server tries to send a new message to it, it gets a SIGPIPE error and closes. I did some research on the web and in SO to see how could I create a handler for the SIGPIPE so instead of closing the application, I'ld tell the timers that constantly send the information to stop.

Now I did learn how to simply handle the signal: create a method that receives a int and use signal(SIGPIPE, myMethod) inside main() or global (note: learned that from SO and yes, I know that signal() is obsolete).

But the problem is that by doing this way I'm unable to stop the sending of information to the dead client, for the method that handles the signal needs to be either outside the class which sends the message or a static method, which don't have access to my server object.

To clarify, here is the current architecture:

//main.cpp

void signal_callback_handler(int signum)
{
    qDebug() << "Caught signal SIGPIPE" << signum << "; closing the application";

    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    app.setApplicationName("ConnEmulator");
    app.setApplicationVersion("1.0.0");
    app.setOrganizationName("Embrasul");
    app.setOrganizationDomain("http://www.embrasul.com.br");

    MainWidget window;
    window.show();

    /* Catch Signal Handler SIGPIPE */
    signal(SIGPIPE, signal_callback_handler);

    return app.exec();
}

//The MainWidget class (simplified)

MainWidget::MainWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MainWidget),
    timerSendData(new QTimer(this))
{
    ui->setupUi(this);

    connect(timerSendData,SIGNAL(timeout()),this,SLOT(slotSendData()));

    timerSendData->start();
    //...
}

void MainWidget::slotSendData()
{
    //Prepares data
    //...

    //Here the sending message is called with send()
    if (hal_socket_write_to_client(&socket_descriptor, (u_int8_t *)buff_write, myBufferSize) == -1)
        qDebug() << "Error writting to client";
}

//Socket library

int hal_socket_write_to_client(socket_t *obj, u_int8_t *buffer, int size)
{
    struct s_socket_private * const socket_obj = (struct s_socket_private *)obj;
    int retval = send(socket_obj->client_fd, buffer, size, 0);

    if (retval < 0)
        perror("write_to_client");

    return retval;
}

So how can I make my MainWidget object created inside int main() handle the signal so he may call timerSendData->stop()?

like image 577
Momergil Avatar asked Jul 23 '14 20:07

Momergil


2 Answers

SIGPIPE is ugly, but possible to deal with in a way that's fully encapsulated, thread-safe, and does not affect anything but the code making the write that might cause SIGPIPE. The general method is:

  1. Block SIGPIPE with pthread_sigmask (or sigprocmask, but the latter is not guaranteed to be safe in multi-threaded programs) and save the original signal mask.

  2. Perform the operation that might raise SIGPIPE.

  3. Call sigtimedwait with a zero timeout to consume any pending SIGPIPE signal.

  4. Restore the original signal mask (unblocking SIGPIPE if it was unblocked before).

Here's a try at some sample code using this method, in the form of a pure wrapper to write that avoids SIGPIPE:

ssize_t write_nosigpipe(int fd, void *buf, size_t len)
{
    sigset_t oldset, newset;
    ssize_t result;
    siginfo_t si;
    struct timespec ts = {0};

    sigemptyset(&newset);
    sigaddset(&newset, SIGPIPE);
    pthread_sigmask(SIG_BLOCK, &newset, &oldset);

    result = write(fd, buf, len);

    while (sigtimedwait(newset, &si, &ts)>=0 || errno != EAGAIN);
    pthread_sigmask(SIG_SETMASK, &oldset, 0);

    return result;
}

It's untested (not even compiled) and may need minor fixes, but hopefully gets the point across. Obviously for efficiency you'd want to do this on a larger granularity than single write calls (for example, you could block SIGPIPE for the duration of the whole library function until it returns to the outside caller).

An alternate design would be simply blocking SIGPIPE and never unblocking it, and documenting in the function's interface that it leaves SIGPIPE blocked (note: blocking is thread-local and does not affect other threads) and possibly leaves SIGPIPE pending (in the blocked state). Then the caller would be responsible for restoring it if necessary, so the rare caller that wants SIGPIPE could get it (but after your function finishes) by unblocking the signal while the majority of callers could happily leave it blocked. The blocking code works like in the above, with the sigtimedwait/unblocking part removed. This is similar to Maxim's answer except that the impact is thread-local and thus thread-safe.

like image 120
R.. GitHub STOP HELPING ICE Avatar answered Sep 20 '22 01:09

R.. GitHub STOP HELPING ICE


Now I did learn how to simply handle the signal: create a method that receives a int and use signal(SIGPIPE, myMethod)

You just need to ignore SIGPIPE, no handler is needed:

// don't raise SIGPIPE when sending into broken TCP connections
::signal(SIGPIPE, SIG_IGN); 

But the problem is that by doing this way I'm unable to stop the sending of information to the dead client, for the method that handles the signal needs to be either outside the class which sends the message or a static method, which don't have access to my server object.

When SIGPIPE is ignored writing into a broken TCP connection returns error code EPIPE, which the socket wrappers you use should handle like the connection has been closed. Ideally, the socket wrapper should pass MSG_NOSIGNAL flag to send, so that send never raises SIGPIPE.

like image 29
Maxim Egorushkin Avatar answered Sep 21 '22 01:09

Maxim Egorushkin