Skip to content

Commit b8dab7b

Browse files
authored
Set http server read/write timeout from --idle-timeout (minio#228) (minio#20715)
Golang http.Server will call SetReadDeadline overwriting the previous deadline configuration set after a new connection Accept in the custom listener code. Therefore, --idle-timeout was not correctly respected. Make http.Server read/write timeout similar to --idle-timeout.
1 parent abd6bf0 commit b8dab7b

File tree

5 files changed

+148
-6
lines changed

5 files changed

+148
-6
lines changed

.github/workflows/go.yml

+1
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ jobs:
3939
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
4040
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
4141
make verify
42+
make test-timeout

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ test-multipart: install-race ## test multipart
149149
@echo "Test multipart behavior when part files are missing"
150150
@(env bash $(PWD)/buildscripts/multipart-quorum-test.sh)
151151

152+
test-timeout: install-race ## test multipart
153+
@echo "Test server timeout"
154+
@(env bash $(PWD)/buildscripts/test-timeout.sh)
155+
152156
verify: install-race ## verify minio various setups
153157
@echo "Verifying build with race"
154158
@(env bash $(PWD)/buildscripts/verify-build.sh)

buildscripts/test-timeout.sh

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/bin/bash
2+
3+
if [ -n "$TEST_DEBUG" ]; then
4+
set -x
5+
fi
6+
7+
WORK_DIR="$PWD/.verify-$RANDOM"
8+
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
9+
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
10+
11+
if [ ! -x "$PWD/minio" ]; then
12+
echo "minio executable binary not found in current directory"
13+
exit 1
14+
fi
15+
16+
if [ ! -x "$PWD/minio" ]; then
17+
echo "minio executable binary not found in current directory"
18+
exit 1
19+
fi
20+
21+
trap 'catch $LINENO' ERR
22+
23+
function purge() {
24+
rm -rf "$1"
25+
}
26+
27+
# shellcheck disable=SC2120
28+
catch() {
29+
if [ $# -ne 0 ]; then
30+
echo "error on line $1"
31+
fi
32+
33+
echo "Cleaning up instances of MinIO"
34+
pkill minio || true
35+
pkill -9 minio || true
36+
purge "$WORK_DIR"
37+
if [ $# -ne 0 ]; then
38+
exit $#
39+
fi
40+
}
41+
42+
catch
43+
44+
function gen_put_request() {
45+
hdr_sleep=$1
46+
body_sleep=$2
47+
48+
echo "PUT /testbucket/testobject HTTP/1.1"
49+
sleep $hdr_sleep
50+
echo "Host: foo-header"
51+
echo "User-Agent: curl/8.2.1"
52+
echo "Accept: */*"
53+
echo "Content-Length: 30"
54+
echo ""
55+
56+
sleep $body_sleep
57+
echo "random line 0"
58+
echo "random line 1"
59+
echo ""
60+
echo ""
61+
}
62+
63+
function send_put_object_request() {
64+
hdr_timeout=$1
65+
body_timeout=$2
66+
67+
start=$(date +%s)
68+
timeout 5m bash -c "gen_put_request $hdr_timeout $body_timeout | netcat 127.0.0.1 $start_port | read" || return -1
69+
[ $(($(date +%s) - start)) -gt $((srv_hdr_timeout + srv_idle_timeout + 1)) ] && return -1
70+
return 0
71+
}
72+
73+
function test_minio_with_timeout() {
74+
start_port=$1
75+
76+
export MINIO_ROOT_USER=minio
77+
export MINIO_ROOT_PASSWORD=minio123
78+
export MC_HOST_minio="http://minio:[email protected]:${start_port}/"
79+
export MINIO_CI_CD=1
80+
81+
mkdir ${WORK_DIR}
82+
C_PWD=${PWD}
83+
if [ ! -x "$PWD/mc" ]; then
84+
MC_BUILD_DIR="mc-$RANDOM"
85+
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
86+
echo "failed to download https://github.com/minio/mc"
87+
purge "${MC_BUILD_DIR}"
88+
exit 1
89+
fi
90+
91+
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
92+
93+
# remove mc source.
94+
purge "${MC_BUILD_DIR}"
95+
fi
96+
97+
"${MINIO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
98+
pid=$!
99+
disown $pid
100+
sleep 1
101+
102+
if ! ps -p ${pid} 1>&2 >/dev/null; then
103+
echo "server1 log:"
104+
cat "${WORK_DIR}/server1.log"
105+
echo "FAILED"
106+
purge "$WORK_DIR"
107+
exit 1
108+
fi
109+
110+
set -e
111+
112+
"${PWD}/mc" mb minio/testbucket
113+
"${PWD}/mc" anonymous set public minio/testbucket
114+
115+
# slow header writing
116+
send_put_object_request 20 0 && exit -1
117+
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
118+
119+
# quick header write and slow bodywrite
120+
send_put_object_request 0 40 && exit -1
121+
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
122+
123+
# quick header and body write
124+
send_put_object_request 1 1 || exit -1
125+
"${PWD}/mc" stat minio/testbucket/testobject || exit -1
126+
}
127+
128+
function main() {
129+
export start_port=$(shuf -i 10000-65000 -n 1)
130+
export srv_hdr_timeout=5
131+
export srv_idle_timeout=5
132+
export -f gen_put_request
133+
134+
test_minio_with_timeout ${start_port}
135+
}
136+
137+
main "$@"

cmd/server-main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -879,8 +879,8 @@ func serverMain(ctx *cli.Context) {
879879
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
880880
UseTLSConfig(newTLSConfig(getCert)).
881881
UseIdleTimeout(globalServerCtxt.IdleTimeout).
882-
UseReadTimeout(24 * time.Hour). // (overridden by listener.config.IdleTimeout on requests)
883-
UseWriteTimeout(24 * time.Hour). // (overridden by listener.config.IdleTimeout on requests)
882+
UseReadTimeout(globalServerCtxt.IdleTimeout).
883+
UseWriteTimeout(globalServerCtxt.IdleTimeout).
884884
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
885885
UseBaseContext(GlobalContext).
886886
UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib

internal/deadlineconn/deadlineconn.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ func (c *DeadlineConn) SetDeadline(t time.Time) error {
114114
c.mu.Lock()
115115
defer c.mu.Unlock()
116116

117-
c.readSetAt = time.Now()
118-
c.writeSetAt = time.Now()
117+
c.readSetAt = time.Time{}
118+
c.writeSetAt = time.Time{}
119119
c.abortReads.Store(!t.IsZero() && time.Until(t) < 0)
120120
c.abortWrites.Store(!t.IsZero() && time.Until(t) < 0)
121121
c.infReads.Store(t.IsZero())
@@ -131,7 +131,7 @@ func (c *DeadlineConn) SetReadDeadline(t time.Time) error {
131131
defer c.mu.Unlock()
132132
c.abortReads.Store(!t.IsZero() && time.Until(t) < 0)
133133
c.infReads.Store(t.IsZero())
134-
c.readSetAt = time.Now()
134+
c.readSetAt = time.Time{}
135135
return c.Conn.SetReadDeadline(t)
136136
}
137137

@@ -145,7 +145,7 @@ func (c *DeadlineConn) SetWriteDeadline(t time.Time) error {
145145
defer c.mu.Unlock()
146146
c.abortWrites.Store(!t.IsZero() && time.Until(t) < 0)
147147
c.infWrites.Store(t.IsZero())
148-
c.writeSetAt = time.Now()
148+
c.writeSetAt = time.Time{}
149149
return c.Conn.SetWriteDeadline(t)
150150
}
151151

0 commit comments

Comments
 (0)