Repository navigation
Unix-socket adapters are killed by SIGPIPE when Redis closes an idle connection #115
Description
RedisAdapter version or revision
main at ff2d5b8; unchanged through the #108 → #109 → #111 stack (0c3b0e6).
Redis version and deployment mode
Redis 7.4.11 standalone, reached over a Unix-domain socket (RA_Options.cxn.path).
Observed behavior
When the adapter talks to Redis over a Unix-domain socket and the server closes one of its pooled connections (Redis restart, CLIENT KILL, or the server's timeout setting), the next command sent on that connection kills the whole process with SIGPIPE (exit status 141).
Over TCP the same event is harmless: the write fails, the adapter reports a transport error, and the next command reconnects.
hiredis writes with send(fd, …, 0) (hiredis/net.c:88, no MSG_NOSIGNAL). On an AF_UNIX stream whose peer is gone, the kernel raises SIGPIPE, and its default action terminates the process. Nothing in RedisAdapter, RedisConnection or redis-plus-plus ignores SIGPIPE; only hiredis's own tests and examples call signal(SIGPIPE, SIG_IGN).
Expected: a server-side close on a Unix socket is reported and recovered from, as it is on TCP.
Reproduction
#include "RedisAdapter.hpp"
#include <csignal>
#include <iostream>
int main(int argc, char** argv) {
const std::string transport = argc > 1 ? argv[1] : "unix";
if (argc > 2 && std::string(argv[2]) == "ignore") std::signal(SIGPIPE, SIG_IGN);
RA_Options options;
if (transport == "unix") options.cxn.path = "/tmp/redis.sock";
sw::redis::ConnectionOptions tcp; tcp.host = "127.0.0.1"; tcp.port = 6379;
sw::redis::Redis control(tcp); // control client, always TCP
RedisAdapter adapter("sigpipe", options);
std::cout << "write before kill -> " << adapter.addSingleValue<int>("x", 1).ok() << std::endl;
control.command<long long>("CLIENT", "KILL", "TYPE", "normal", "SKIPME", "yes");
std::cout << "first write after kill -> " << std::flush;
std::cout << adapter.addSingleValue<int>("x", 2).ok() << std::endl;
std::cout << "second write after kill -> " << adapter.addSingleValue<int>("x", 3).ok() << std::endl;
}Output (identical on main and at the stack head):
unix: write before kill -> 1 / first write after kill -> (process killed, exit 141)
unix, SIGPIPE ignored: first write after kill -> 0 / second write after kill -> 1
tcp: first write after kill -> 0 / second write after kill -> 1
The connection-loss step of #111's recovery_test.cpp, run with the adapter on /tmp/redis.sock, also dies with SIGPIPE (5/5). The backtrace is redisNetWrite (hiredis/net.c:88) ← sw::redis::Connection::recv ← RedisConnection::streamBounds ← RedisAdapter::inspect_readers.
The UnixDomainSocket gtest passes only because nothing closes its connection.
Build and runtime environment
- Ubuntu 24.04, gcc 13.3, CMake 3.28
- pinned redis-plus-plus 1.3.15 and hiredis 1.3.0
- a private Redis 7.4.11 in a container
- default
RA_Optionsapart fromcxn.path
Suggested fix
Any one of:
- Have RedisConnection (or the adapter constructor) set
SIGPIPEtoSIG_IGNwhen its disposition is stillSIG_DFL, and document that it does. - Document prominently that applications using
cxn.pathmust ignoreSIGPIPE. - Keep the process-wide disposition untouched and block
SIGPIPEaround writes in the library's own threads (pthread_sigmask, thensigtimedwaitto drain a pending signal).
In every case, add a Unix-socket variant of the connection-loss step to the recovery test, run with SIGPIPE left at its default.
Related: #65. Its triage proposes a single Redis instance and the Unix socket as the supported transport, which would make this the main connection path.
Activity