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.
This commit is contained in:
Yorhel 2026-01-05 08:57:48 +01:00
parent d300f4d791
commit 48fe393d5f
4 changed files with 34 additions and 15 deletions

11
FU.pm
View file

@ -292,7 +292,8 @@ sub _read_req($c) {
: $r == -2 ? "I/O error while reading from FastCGI socket\n" : $r == -2 ? "I/O error while reading from FastCGI socket\n"
: $r == -3 ? "FastCGI protocol error\n" : $r == -3 ? "FastCGI protocol error\n"
: $r == -4 ? "Too long FastCGI parameter\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}; delete $c->{fcgi_obj};
fu->error(-1); fu->error(-1);
} }
@ -400,7 +401,13 @@ sub _do_req($c) {
} }
$REQ->{trace_end} = clock_gettime(CLOCK_MONOTONIC); $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}) { if (debug && $REQ->{trace_id} && $debug_info->{history} && $debug_info->{storage}) {
require FU::DebugImpl; require FU::DebugImpl;

6
FU.xs
View file

@ -3,7 +3,7 @@
#include <time.h> /* struct timespec & clock_gettime() */ #include <time.h> /* struct timespec & clock_gettime() */
#include <string.h> /* strerror() */ #include <string.h> /* strerror() */
#include <arpa/inet.h> /* inet_ntop(), inet_ntoa() */ #include <arpa/inet.h> /* inet_ntop(), inet_ntoa() */
#include <sys/socket.h> /* fd passing */ #include <sys/socket.h> /* send(), fd passing */
#include <sys/un.h> /* fd passing */ #include <sys/un.h> /* fd passing */
#include <dlfcn.h> /* dlopen() etc */ #include <dlfcn.h> /* dlopen() etc */
@ -170,11 +170,11 @@ void print(fufcgi *ctx, SV *sv)
CODE: CODE:
STRLEN len; STRLEN len;
const char *buf = SvPVbyte(sv, len); const char *buf = SvPVbyte(sv, len);
fufcgi_print(ctx, buf, len); fufcgi_print(aTHX_ ctx, buf, len);
void flush(fufcgi *ctx) void flush(fufcgi *ctx)
CODE: CODE:
fufcgi_done(ctx); fufcgi_done(aTHX_ ctx);
void DESTROY(fufcgi *ctx) void DESTROY(fufcgi *ctx)
CODE: CODE:

View file

@ -18,6 +18,7 @@
#define FUFE_CLEN -5 #define FUFE_CLEN -5
#define FUFE_ABORT -6 /* explicit abort or client-level EOF */ #define FUFE_ABORT -6 /* explicit abort or client-level EOF */
#define FUFE_NOREQ -7 /* protocol-level EOF before we received anything */ #define FUFE_NOREQ -7 /* protocol-level EOF before we received anything */
#define FUFE_SEND -8 /* error in send() */
#define FUFCGI_MAX_DATA 65535 #define FUFCGI_MAX_DATA 65535
@ -177,8 +178,8 @@ static int fufcgi_write_record(fufcgi *ctx, fufcgi_rec *hdr, char *buf) {
buf[7] = 0; buf[7] = 0;
int len = hdr->len + 8; int len = hdr->len + 8;
while (len > 0) { while (len > 0) {
int r = write(ctx->fd, buf, len); int r = send(ctx->fd, buf, len, MSG_NOSIGNAL);
if (r <= 0) return r == 0 ? FUFE_EOF : FUFE_IO; if (r <= 0) return FUFE_SEND;
buf += r; buf += r;
len -= 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; fufcgi_rec hdr;
if (ctx->len > 0) { if (ctx->len > 0) {
hdr.len = ctx->len; hdr.len = ctx->len;
hdr.type = FCGI_STDOUT; hdr.type = FCGI_STDOUT;
hdr.id = ctx->reqid; 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; 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; int r;
while (len > 0) { while (len > 0) {
r = len > FUFCGI_MAX_DATA - ctx->len ? FUFCGI_MAX_DATA - ctx->len : len; 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; ctx->len += r;
len -= r; len -= r;
buf += 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_rec hdr;
fufcgi_flush(ctx); fufcgi_flush(aTHX_ ctx);
hdr.len = 0; hdr.len = 0;
hdr.type = FCGI_STDOUT; hdr.type = FCGI_STDOUT;
hdr.id = ctx->reqid; 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 */ memcpy(ctx->buf+8, "\0\0\0\0\0\0\0\0", 8); /* FCGI_REQUEST_COMPLETE */
hdr.type = FCGI_END_REQUEST; hdr.type = FCGI_END_REQUEST;
hdr.len = 8; 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; ctx->reqid = ctx->len = ctx->off = 0;
} }

View file

@ -54,6 +54,11 @@ start;
begin 1, 2; begin 1, 2;
record 1, 4, ""; record 1, 4, "";
start;
begin 3, 2, 1;
$remote->close;
iserr -8;
start; start;
begin 3, 2, 1; begin 3, 2, 1;
begin 1, 1, 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, 4, "";
record 1, 5, ""; record 1, 5, "";
isrec {'content-length','0'}, {body => ''}; isrec {'content-length','0'}, {body => ''};
$remote->close;
ok !eval { $f->flush; 1 };
like $@, qr/write error/;
start; start;
begin; begin;