Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2016-02-19

SIGPIPE and multithreading

While working on my socket utilities I ran into a problem: "Thread 1: SIGPIPE"

Hmm.

Wel, lets see what Thread 1 is up to. Turns out it is stopped at a 'select' socket call. But 'select' cannot raise a SIGPIPE can it? No amount of googling did help. SIGPIPE can only occur on 'recv' and 'send'.

So what is going on?

After I added some debug prints I finally found the culprit: It was in thread 4 and it was a call to 'recv'.

So why would Xcode give the message for Thread 1?

This is what I think: the SIGPIPE error -when not handled- leads to termination of the program. This is quite likely a highest level event en thus occurs on the first thread. Hence Thread 1. Thread 1 was not the cause of the problem, but it was the one that is handling the problem.

A bit confusing, and it did cost me a few hours. But that's software development...

Oh, btw, how to get rid of this?

The error is a signal that "the other end" of a connection has closed its side. When a 'recv' or 'send' is called on a socket of which the other side is closed will raise the GIGPIPE exception, which will terminate the application. This can be good if the app is used as part of a series of 'piped' apps. Like most Unix utilities.

However if the app is a server, you want to app to continue working after a SIGPIPE. This can be achieved by setting the socket option SO_NOSIGPIPE to active (1).

A little code segment showing this:

   let receiveSocket = accept(serverSocket, &connectedAddrInfo, &connectedAddrInfoLength)
            
   // Evalute the result of the accept call
            
   if receiveSocket == -1 { // Error
                
      let strerr = String(UTF8String: strerror(errno)) ?? "Unknown error code"
      telemetry?.endTime = NSDate()
      telemetry?.result = .ERROR(message: strerr)
      return .ERROR(message: strerr)
                
   } else// Success, return the accepted socket
                
                
      // ================================================
      // Set the socket option: prevent SIGPIPE exception
      // ================================================
                
      var optval = 1;
                
      let status = setsockopt(
         receiveSocket,
         SOL_SOCKET,
         SO_NOSIGPIPE,
         &optval,
         socklen_t(sizeof(Int)))
                
      if status == -1 {
         let strError = String(UTF8String: strerror(errno)) ?? "Unknown error code"
         close(receiveSocket)
         return .ERROR(message: strError)

      }

To be fair: I am not sure if the 'accept' call will copy the SO_NOSIGPIPE to the newly created receive socket. If it does do that, then setting this option for the server socket would be enough.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment