7

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

Momergil
  • 2,213
  • 5
  • 29
  • 59
  • This is not a duplicate. The question which it was marked as a duplicate of did not even refer to `SIGPIPE` or anything about signals; it was just about handling unrelated network error conditions. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 23:00
  • It was about handling ECONNRESET, which is *what you get when you handle SIGPIPE*. Nothing 'unrelated' about it whatsoever. The answer to the one is the answer to the other. – user207421 Jul 23 '14 at 23:11
  • @EJP: `ECONNRESET` is an error that can occur while reading, not while writing, and it has nothing to do with `SIGPIPE`. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 23:28

2 Answers2

9

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.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • And how do you report the failure to the application? Given that single handles should not have side effects? – user207421 Jul 23 '14 at 23:12
  • @EJP: When `SIGPIPE` is blocked at the time it would be generated, the `write`/`send` returns an error with `errno` set to `EPIPE`. You then handle this the same way you would handle any other write error. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 23:26
  • Few if any libraries do that when invoking `send`. Pedantically correct solution to a problem that does not exist. – Maxim Egorushkin Jul 24 '14 at 10:09
  • 1
    What do they do then? Make a non-thread-safe call to `sigaction` or rely on the caller to have blocked or ignored `SIGPIPE`? Just because few libraries do something correctly is not an argument against doing so. Few libraries handle out-of-memory correctly and this makes the majority that don't junk that you can't use in a serious program, not valid. – R.. GitHub STOP HELPING ICE Jul 24 '14 at 14:06
  • BTW if the caller provides the socket fd, then unless the library has to guarantee consistency of some on-disk structure or similar, I think it's pretty reasonable for a library to "let `SIGPIPE` happen" unless the caller blocked/ignored it. But if opening and using the socket is internal to the library (part of the implementation, not the interface) it definitely needs to prevent `SIGPIPE` from crashing the program or else document it. – R.. GitHub STOP HELPING ICE Jul 24 '14 at 14:30
  • I can only think of one use case when `SIGPIPE` needs to terminate an application: for command line applications that are expected to be used with shell pipes. In all other cases `SIGPIPE` should be ignored, no code should ever raise this signal. – Maxim Egorushkin Jul 25 '14 at 11:20
  • @R.. First of all, thanks for the reply (and for all others who helped in the interesting debate that was made). I just read the hole debate between you and the other two contributors and I became convinced you provided the best answer - not because of the implementation itself, but because of the theory behind it. I mean, I actually didn't understand your resolution in terms of "how do I implement this in a code". Could you please complete your answer with a implementation example (or providing a link for that)? Thanks! – Momergil Jul 28 '14 at 20:25
  • @Momergil: I added some sample code and further elaborated on related possibilities for dealing with `SIGPIPE`. – R.. GitHub STOP HELPING ICE Jul 29 '14 at 14:17
3

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.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Since the signal disposition for `SIGPIPE` is global, this is not appropriate; another part of the program might want the signal to be generated and might have a signal handler. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 21:03
  • @R. True. I have not seen libraries that raise `SIGPIPE`, but would avoid or patch them if I do. – Maxim Egorushkin Jul 23 '14 at 21:04
  • I provided an alternate approach that avoids changing any global process state in my answer. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 21:12
  • I think while R's suggestion handles library pathologies well, in 95% of cases this solution is how you fix things. – Dietrich Epp Jul 23 '14 at 21:12
  • 1
    @DietrichEpp: Maxim's solution is appropriate if the code doing the ignoring is the main program and it knows it wants to ignore `SIGPIPE` all the time, but it's not code that you can safely put in a library. My approach works in either. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 21:13
  • That's a good point, but the question is clearly about application development. – Dietrich Epp Jul 23 '14 at 21:14
  • Also `MSG_NOSIGNAL` is the best way, but it's not always an option, e.g. if you can't use `send` but have to use `write`. Also, prior to POSIX 2008, it was not a mandatory feature, so some implementations that aren't up to date with POSIX 2008 (maybe Mac OSX?) don't necessarily support it. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 21:15
  • 1
    @R. Ignoring SIG_PIPE is how 99% of programs do it. Stop complaining that someone provided a different answer from yours. – user207421 Jul 23 '14 at 22:53
  • 1
    @EJP: I'm not complaining. I'm providing useful information about the pros and cons of different options. SO questions and answers are supposed to be useful in the future, not just for the OP, so expanding on them is part of providing a useful resource. As you can see I agree with Maxim that `MSG_NOSIGNAL` is the best way when it's possible. – R.. GitHub STOP HELPING ICE Jul 23 '14 at 22:56