Guard against null server responses

Convert unexpected null responses into DeliverRequestFail instead of crashing.

Adds null_response_handling test and runs it in the harness.
This commit is contained in:
Julien Letessier 2026-01-15 09:12:43 +01:00
parent 7485ebbb5a
commit 220772824c
3 changed files with 53 additions and 1 deletions

View File

@ -772,6 +772,11 @@ void Handler::directResponse(Request* req, Response::GenericCode code, ConnectCo
} }
void Handler::handleResponse(ConnectConnection* s, Request* req, Response* res) void Handler::handleResponse(ConnectConnection* s, Request* req, Response* res)
ResponsePtr fallback;
if (!res) {
fallback = ResponseAlloc::create(Response::DeliverRequestFail);
res = fallback;
}
{ {
FuncCallTimer(); FuncCallTimer();
SegmentStr<Const::MaxKeyLen> key(req->key()); SegmentStr<Const::MaxKeyLen> key(req->key());

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
#
# Exercise server write mismatch handling to ensure proxy stays alive
#
import argparse
import socket
import sys
import time
import redis
def run_test(host, port):
# Send a malformed pipelined request and close quickly.
try:
sock = socket.create_connection((host, port), timeout=1.0)
sock.sendall(b"*2\r\n$4\r\nping\r\n$4\r\nping\r\n")
sock.close()
except Exception as exc:
print("WARN: socket setup failed:", exc)
# Ensure the proxy still accepts connections.
try:
c = redis.StrictRedis(host=host, port=port)
if c.ping() is not True:
print("FAIL: ping did not return True")
return False
except Exception as exc:
print("FAIL: ping after malformed request:", exc)
return False
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser(conflict_handler='resolve', description="Null response handling test")
parser.add_argument("-h", "--host", default="127.0.0.1")
parser.add_argument("-p", "--port", type=int, default=7617)
args = parser.parse_args()
if run_test(args.host, args.port):
print("PASS: null response handling")
sys.exit(0)
print("FAIL: null response handling")
sys.exit(1)

View File

@ -62,6 +62,7 @@ PUBSUB_EXIT=0
PUBSUB_MESSAGE_EXIT=0 PUBSUB_MESSAGE_EXIT=0
PUBSUB_ORDER_EXIT=0 PUBSUB_ORDER_EXIT=0
PUBSUB_RESET_EXIT=0 PUBSUB_RESET_EXIT=0
NULL_RESPONSE_EXIT=0
uv run python3 test/basic.py || BASIC_EXIT=$? uv run python3 test/basic.py || BASIC_EXIT=$?
uv run python3 test/pubsub_minimal.py -p 7617 || PUBSUB_REDIS_EXIT=$? uv run python3 test/pubsub_minimal.py -p 7617 || PUBSUB_REDIS_EXIT=$?
@ -69,7 +70,8 @@ uv run python3 test/pubsub_minimal.py -p 6379 || PUBSUB_MINIMAL_EXIT=$?
uv run python3 test/pubsub.py || PUBSUB_EXIT=$? uv run python3 test/pubsub.py || PUBSUB_EXIT=$?
uv run python3 test/pubsub_subscription_order.py -p 7617 || PUBSUB_ORDER_EXIT=$? uv run python3 test/pubsub_subscription_order.py -p 7617 || PUBSUB_ORDER_EXIT=$?
uv run python3 test/pubsub_parser_reset.py -p 7617 || PUBSUB_RESET_EXIT=$? uv run python3 test/pubsub_parser_reset.py -p 7617 || PUBSUB_RESET_EXIT=$?
uv run python3 test/null_response_handling.py -p 7617 || NULL_RESPONSE_EXIT=$?
uv run python3 test/pubsub_message_response.py -p 7617 || PUBSUB_MESSAGE_EXIT=$? uv run python3 test/pubsub_message_response.py -p 7617 || PUBSUB_MESSAGE_EXIT=$?
TEST_EXIT=$((BASIC_EXIT + PUBSUB_REDIS_EXIT + PUBSUB_MINIMAL_EXIT + PUBSUB_EXIT + PUBSUB_MESSAGE_EXIT + PUBSUB_ORDER_EXIT + PUBSUB_RESET_EXIT)) TEST_EXIT=$((BASIC_EXIT + PUBSUB_REDIS_EXIT + PUBSUB_MINIMAL_EXIT + PUBSUB_EXIT + PUBSUB_MESSAGE_EXIT + PUBSUB_ORDER_EXIT + PUBSUB_RESET_EXIT + NULL_RESPONSE_EXIT))
exit $TEST_EXIT exit $TEST_EXIT