Search code examples
c++csocketssend

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


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()?


Solution

  • 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.