Skip to content

Unix-socket adapters are killed by SIGPIPE when Redis closes an idle connection #115

Open

Description

@bigsamich

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_Options apart from cxn.path

Suggested fix

Any one of:

  • Have RedisConnection (or the adapter constructor) set SIGPIPE to SIG_IGN when its disposition is still SIG_DFL, and document that it does.
  • Document prominently that applications using cxn.path must ignore SIGPIPE.
  • Keep the process-wide disposition untouched and block SIGPIPE around writes in the library's own threads (pthread_sigmask, then sigtimedwait to 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmust fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions