www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - TIL: writing to a socket and dying

reply Andy Valencia <dont spam.me> writes:
This has been touched upon way back in forum history, but I 
thought it was worth a fresh mention.  When writing to a 
socket--especially as a server--you can receive SIGPIPE.  Phobos 
appears to try and inhibit this on some BSD systems, but on Linux 
if the recipient has closed the socket and you write--SIGPIPE.

And if you thought you had your bases covered with 
try/catch--nope!  This is a _signal_, not an exception.  The 
default for SIGPIPE is for your process to terminate.  Why was my 
server dying?  The key clue was the exit code, which led me back 
to SIGPIPE and then I could guess what had happened.  I added a 
SIG_IGN of SIGPIPE and that made the problem stop.  (There's also 
a flag MSG_NOSIGNAL to send() which might address this as well.)

With that, my web service middleware is running quite nicely.  
SSL, authentication, files/seeking/streaming, and parallel 
request dispatch.

Andy
Apr 24
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 25/04/2025 2:04 AM, Andy Valencia wrote:
 This has been touched upon way back in forum history, but I thought it 
 was worth a fresh mention.  When writing to a socket--especially as a 
 server--you can receive SIGPIPE.  Phobos appears to try and inhibit this 
 on some BSD systems, but on Linux if the recipient has closed the socket 
 and you write--SIGPIPE.
This was not a nice post to read first thing in the morning! I haven't handled this for my eventloop, oops. Some interesting articles on the subject: https://www.doof.me.uk/2020/09/23/sigpipe-and-how-to-ignore-it/ https://makedist.com/posts/2014/01/16/porting-msg_more-and-msg_nosigpipe-to-os-x/ https://riptutorial.com/posix/example/17424/handle-sigpipe-generated-by-write---in-a-thread-safe-manner Anyway, here is the solution I put together (although untested), it should cover pretty much all Posix systems: ```d module sidero.base.internal.posix; import core.stdc.stdio; version(Posix) { import core.sys.posix.sys.types; } else { alias ssize_t = size_t; alias off_t = ulong; } export nothrow nogc: /** Apply any socket options required for the write abstraction here. Returns: if it succeded in applying the required options. */ void applyPerSocketFlag(FILE* socket) { applyPerSocketFlag(fileno(socket)); } /// Ditto bool applyPerSocketFlag(int s) { version(Posix) { import core.sys.posix.sys.socket; static if(__traits(compiles, SO_NOSIGPIPE)) { int enable = 1; if (setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &enable, int.sizeof) != 0) return false; } } return true; } /** Write to a socket, handling SIGPIPE error via blocking and consuming the signal. If a pending SIGPIPE is queued due to blocking, it will not be consumed, and should be left unaffected (max should be one queued). May call ``writeAppend``, instead. You must call ``applyPerSocketFlag`` prior to calling this. */ ssize_t writeOnSocket(FILE* socket, const scope void* buffer, size_t length, int flags = 0) { return writeOnSocket(fileno(socket), buffer, length, flags); } /// Ditto ssize_t writeOnSocket(int socket, const scope void* buffer, size_t length, int flags = 0) { version(Posix) { import core.sys.posix.sys.socket; static if(__traits(compiles, SO_NOSIGPIPE)) { return send(socket, buffer, length, flags); } else static if(__traits(compiles, MSG_NOSIGNAL)) { return send(socket, buffer, length, flags | MSG_NOSIGNAL); } else { return writeAppend(socket, buffer, length); } } else assert(0, "Unimplemented, use platform specific functions for writing on a socket"); } /** Write to a file, handling SIGPIPE error via blocking and consuming the signal. If a pending SIGPIPE is queued due to blocking, it will not be consumed, and should be left unaffected (max should be one queued). */ ssize_t writeAppend(FILE* file, const scope void* buffer, size_t length) { return writeAppend(fileno(file), buffer, length); } /// Ditto ssize_t writeAppend(int file, const scope void* buffer, size_t length) { version(Posix) { import core.sys.posix.signal; import core.sys.posix.unistd; import core.sys.posix.time; import core.stdc.errno; sigset_t block, old, pending; sigemptyset(&block); sigaddset(&block, SIGPIPE); if(pthread_sigmask(SIG_BLOCK, &block, &old) != 0) return -1; int pendingtype = -1; if(sigpending(&block) != -1) pendingtype = sigismember(&pending, SIGPIPE); ssize_t ret; while((ret = write(file, buffer, length)) == -1 && errno == EINTR) { } if(ret == -1 && errno == EPIPE && pendingtype == 0) { static if(__traits(compiles, sigtimedwait)) { timespec ts; int sig; while((sig = sigtimedwait(&block, null, &ts)) == -1 && errno == EINTR) { } } else { pendingtype = -1; if(sigpending(&block) != -1) pendingtype = sigismember(&pending, SIGPIPE); if(pendingtype == 1) sigwait(&block, null); } } pthread_sigmask(SIG_SETMASK, &old, null); return ret; } else assert(0, "Unimplemented, use platform specific functions for writing to a fd"); } /** Write to a file at offset, handling SIGPIPE error via blocking and consuming the signal. If a pending SIGPIPE is queued due to blocking, it will not be consumed, and should be left unaffected (max should be one queued). */ ssize_t writeToOffset(FILE* file, const scope void* buffer, size_t length, off_t offset) { return writeToOffset(fileno(file), buffer, length, offset); } /// Ditto ssize_t writeToOffset(int file, const scope void* buffer, size_t length, off_t offset) { version(Posix) { import core.sys.posix.signal; import core.sys.posix.unistd; import core.sys.posix.time; import core.stdc.errno; sigset_t block, old, pending; sigemptyset(&block); sigaddset(&block, SIGPIPE); if(pthread_sigmask(SIG_BLOCK, &block, &old) != 0) return -1; int pendingtype = -1; if(sigpending(&block) != -1) pendingtype = sigismember(&pending, SIGPIPE); ssize_t ret; while((ret = pwrite(file, buffer, length, offset)) == -1 && errno == EINTR) { } if(ret == -1 && errno == EPIPE && pendingtype == 0) { static if(__traits(compiles, sigtimedwait)) { timespec ts; int sig; while((sig = sigtimedwait(&block, null, &ts)) == -1 && errno == EINTR) { } } else { pendingtype = -1; if(sigpending(&block) != -1) pendingtype = sigismember(&pending, SIGPIPE); if(pendingtype == 1) sigwait(&block, null); } } pthread_sigmask(SIG_SETMASK, &old, null); return ret; } else assert(0, "Unimplemented, use platform specific functions for writing to a fd"); } ```
Apr 24
prev sibling parent reply kdevel <kdevel vogtner.de> writes:
On Thursday, 24 April 2025 at 14:04:03 UTC, Andy Valencia wrote:
 [...]
 Phobos appears to try and inhibit this on some BSD systems,
How does it do that?
 but on Linux if the recipient has closed the socket and [the 
 OPs process running his progam] write[s]--SIGPIPE.
"the whole point of the signal is to notify [the process writing to the closed socket] asynchronously that the write is no longer possible. This is part of what makes the whole elegant co-routine structure of pipes work in UNIX." [1] The truth is probably that there are lots of programs which don't check write's (printf's etc.) return value so that they will continue running.
 [...] I added a SIG_IGN of SIGPIPE and that made the problem 
 stop.
You know that it will now throw? [0] [0] [Issue 21649] Make D runtime ignore or handle SIGPIPE or document the behavior, https://forum.dlang.org/thread/bug-21649-3 https.issues.dlang.org%2F [1] https://stackoverflow.com/questions/8369506/why-does-sigpipe-exist
Apr 24
next sibling parent Andy Valencia <dont spam.me> writes:
On Thursday, 24 April 2025 at 19:36:03 UTC, kdevel wrote:
 [...] I added a SIG_IGN of SIGPIPE and that made the problem 
 stop.
You know that it will now throw? [0]
Yes; my server was written to handle an exception, and it also is prepared for the send() to return failure. Adding a third vector of error notification was not helpful. Andy
Apr 24
prev sibling parent Andy Valencia <dont spam.me> writes:
On Thursday, 24 April 2025 at 19:36:03 UTC, kdevel wrote:
 Phobos appears to try and inhibit this on some BSD systems,
How does it do that?
Sorry, I forgot to answer this question. There's a setsockopt SO_NOSIGPIPE which is used if available during initial socket setup (in Phobos). Some comments I spotted made it sound like a BSD innovation. Andy
Apr 25