From 48fe393d5f05e9cec8ddb0c502d1a8e8c47d4c13 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Mon, 5 Jan 2026 08:57:48 +0100 Subject: [PATCH] FastCGI: Improve handling of EPIPE while writing response That would previously result in the worker getting killed with SIGPIPE. Which works, but we can also recover from that error without restarting the process. --- FU.pm | 11 +++++++++-- FU.xs | 6 +++--- c/fcgi.c | 24 ++++++++++++++---------- t/fcgi.t | 8 ++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/FU.pm b/FU.pm index 8e47c1d..7d5df3b 100644 --- a/FU.pm +++ b/FU.pm @@ -292,7 +292,8 @@ sub _read_req($c) { : $r == -2 ? "I/O error while reading from FastCGI socket\n" : $r == -3 ? "FastCGI protocol error\n" : $r == -4 ? "Too long FastCGI parameter\n" - : $r == -5 ? "Too long request body\n" : undef if $r != -7; + : $r == -5 ? "Too long request body\n" + : $r == -8 ? "I/O error while writing to FastCGI socket\n" : undef if $r != -7; delete $c->{fcgi_obj}; fu->error(-1); } @@ -400,7 +401,13 @@ sub _do_req($c) { } $REQ->{trace_end} = clock_gettime(CLOCK_MONOTONIC); - fu->_flush($c->{fcgi_obj} || $c->{client_sock}); + eval { + fu->_flush($c->{fcgi_obj} || $c->{client_sock}); + 1; + } || do { + log_write "Error writing response: $@\n"; + $c->{client_sock} = $c->{fcgi_obj} = undef; + }; if (debug && $REQ->{trace_id} && $debug_info->{history} && $debug_info->{storage}) { require FU::DebugImpl; diff --git a/FU.xs b/FU.xs index 60ef7a6..1477a0a 100644 --- a/FU.xs +++ b/FU.xs @@ -3,7 +3,7 @@ #include /* struct timespec & clock_gettime() */ #include /* strerror() */ #include /* inet_ntop(), inet_ntoa() */ -#include /* fd passing */ +#include /* send(), fd passing */ #include /* fd passing */ #include /* dlopen() etc */ @@ -170,11 +170,11 @@ void print(fufcgi *ctx, SV *sv) CODE: STRLEN len; const char *buf = SvPVbyte(sv, len); - fufcgi_print(ctx, buf, len); + fufcgi_print(aTHX_ ctx, buf, len); void flush(fufcgi *ctx) CODE: - fufcgi_done(ctx); + fufcgi_done(aTHX_ ctx); void DESTROY(fufcgi *ctx) CODE: diff --git a/c/fcgi.c b/c/fcgi.c index 4f886dd..efcce6e 100644 --- a/c/fcgi.c +++ b/c/fcgi.c @@ -18,6 +18,7 @@ #define FUFE_CLEN -5 #define FUFE_ABORT -6 /* explicit abort or client-level EOF */ #define FUFE_NOREQ -7 /* protocol-level EOF before we received anything */ +#define FUFE_SEND -8 /* error in send() */ #define FUFCGI_MAX_DATA 65535 @@ -177,8 +178,8 @@ static int fufcgi_write_record(fufcgi *ctx, fufcgi_rec *hdr, char *buf) { buf[7] = 0; int len = hdr->len + 8; while (len > 0) { - int r = write(ctx->fd, buf, len); - if (r <= 0) return r == 0 ? FUFE_EOF : FUFE_IO; + int r = send(ctx->fd, buf, len, MSG_NOSIGNAL); + if (r <= 0) return FUFE_SEND; buf += r; len -= r; } @@ -409,18 +410,19 @@ static int fufcgi_read_req(pTHX_ fufcgi *ctx, SV *headers, SV *params) { } } -static void fufcgi_flush(fufcgi *ctx) { +static void fufcgi_flush(pTHX_ fufcgi *ctx) { fufcgi_rec hdr; if (ctx->len > 0) { hdr.len = ctx->len; hdr.type = FCGI_STDOUT; hdr.id = ctx->reqid; - fufcgi_write_record(ctx, &hdr, ctx->buf); + if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK) + croak("write error: %s", strerror(errno)); ctx->len = 0; } } -static void fufcgi_print(fufcgi *ctx, const char *buf, int len) { +static void fufcgi_print(pTHX_ fufcgi *ctx, const char *buf, int len) { int r; while (len > 0) { r = len > FUFCGI_MAX_DATA - ctx->len ? FUFCGI_MAX_DATA - ctx->len : len; @@ -428,23 +430,25 @@ static void fufcgi_print(fufcgi *ctx, const char *buf, int len) { ctx->len += r; len -= r; buf += r; - if (ctx->len >= FUFCGI_MAX_DATA) fufcgi_flush(ctx); + if (ctx->len >= FUFCGI_MAX_DATA) fufcgi_flush(aTHX_ ctx); } } -static void fufcgi_done(fufcgi *ctx) { +static void fufcgi_done(pTHX_ fufcgi *ctx) { fufcgi_rec hdr; - fufcgi_flush(ctx); + fufcgi_flush(aTHX_ ctx); hdr.len = 0; hdr.type = FCGI_STDOUT; hdr.id = ctx->reqid; - fufcgi_write_record(ctx, &hdr, ctx->buf); + if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK) + croak("write error: %s", strerror(errno)); memcpy(ctx->buf+8, "\0\0\0\0\0\0\0\0", 8); /* FCGI_REQUEST_COMPLETE */ hdr.type = FCGI_END_REQUEST; hdr.len = 8; - fufcgi_write_record(ctx, &hdr, ctx->buf); + if (fufcgi_write_record(ctx, &hdr, ctx->buf) != FUFE_OK) + croak("write error: %s", strerror(errno)); ctx->reqid = ctx->len = ctx->off = 0; } diff --git a/t/fcgi.t b/t/fcgi.t index d7860dc..85636e3 100644 --- a/t/fcgi.t +++ b/t/fcgi.t @@ -54,6 +54,11 @@ start; begin 1, 2; record 1, 4, ""; +start; +begin 3, 2, 1; +$remote->close; +iserr -8; + start; begin 3, 2, 1; begin 1, 1, 1; @@ -173,6 +178,9 @@ record 1, 4, "\x13\x01HTTP_CONTENT_LENGTH3\x0e\x01CONTENT_LENGTH0\x13\x01HTTP_CO record 1, 4, ""; record 1, 5, ""; isrec {'content-length','0'}, {body => ''}; +$remote->close; +ok !eval { $f->flush; 1 }; +like $@, qr/write error/; start; begin;