rproxy: leave no visitor without an answer as the response time runs out (closes #295)
check / check (push) Failing after 4m37s
check / check (push) Failing after 4m37s
A visitor could get a closed connection and no answer when SWRP_MAX_RESPONSE_TIME_MS ran out about when the answer started: the write deadline passed before the first byte was written. - An answer rproxy gives itself has at least 10 seconds to be sent. - An app's answer that arrives once the time has run out is answered 504. - Otherwise the app's status and headers are sent at once, before the body is read, with at least 10 seconds. The exact limit applies to the rest. Model: opus-5-5
This commit was merged in pull request #297.
This commit is contained in:
1 parent
db0912b335
commit
aeaa9d1c77
5 files changed
+134
-13
No files matched your search
@@ -481,6 +481,30 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnAnswerRProxyGivesItselfHasTenSecondsToBeSentAtLeast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const least = 10 * time.Second
|
||||
|
||||
standIn := startDiscriminator(t, deny)
|
||||
app := startApp(t, greet)
|
||||
_, _, server := startServer(t, standIn.URL, app.URL, map[string]string{
|
||||
maxResponseTime: "300",
|
||||
})
|
||||
|
||||
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody)
|
||||
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
|
||||
|
||||
before := time.Now()
|
||||
|
||||
server.Handler.ServeHTTP(w, req)
|
||||
|
||||
if w.writeDeadline.Before(before.Add(least)) {
|
||||
t.Errorf("the time limit on sending ends at %s, want %s from the answer at least",
|
||||
w.writeDeadline, least)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -601,6 +625,68 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVisitorGetsAnAnswerWhenTheAppAnswersAsTheTimeRunsOut(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Four visitors at a time, each with an app and rproxy of its own, send more
|
||||
// requests in the same time.
|
||||
for visitor := range 4 {
|
||||
t.Run(strconv.Itoa(visitor), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
visitAsTheTimeRunsOut(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// visitAsTheTimeRunsOut sends requests to an app that answers about as
|
||||
// SWRP_MAX_RESPONSE_TIME_MS runs out, and checks that each is answered with the
|
||||
// app's 200 or a 504.
|
||||
func visitAsTheTimeRunsOut(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
const limit = 50 * time.Millisecond
|
||||
|
||||
// answerAfter is how long the app waits before it answers.
|
||||
var answerAfter atomic.Int64
|
||||
|
||||
backend := startDiscriminatorAnsweringReports(t, allow,
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||
time.Sleep(time.Duration(answerAfter.Load()))
|
||||
|
||||
_, _ = io.WriteString(w, greeting)
|
||||
})
|
||||
addr, _ := startRProxy(t, backend, app.URL, map[string]string{
|
||||
maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10),
|
||||
})
|
||||
|
||||
// After each 200 the app answers a little later, and after each 504 a little
|
||||
// sooner, so that most of its answers arrive as the time runs out.
|
||||
delay, step := limit/2, limit/4
|
||||
|
||||
for range 60 {
|
||||
answerAfter.Store(int64(delay))
|
||||
|
||||
conn := dial(t, addr)
|
||||
send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
||||
|
||||
switch status := readStatus(t, conn); status {
|
||||
case http.StatusOK:
|
||||
delay += step
|
||||
case http.StatusGatewayTimeout:
|
||||
delay -= step
|
||||
default:
|
||||
t.Fatalf("status %d, want the app's 200 or 504", status)
|
||||
}
|
||||
|
||||
_ = conn.Close()
|
||||
step = max(step/2, 20*time.Microsecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in new issue
Block a user