web: add feature flag PropagateCancels (#7778)
This allow client-initiated cancels to propagate through gRPC. IN-10803 tracks the SRE-side changes to enable this flag.
This commit is contained in:
parent
21bc647fa5
commit
02685602a2
|
|
@ -103,6 +103,15 @@ type Config struct {
|
||||||
// This flag should only be used in conjunction with UseKvLimitsForNewOrder.
|
// This flag should only be used in conjunction with UseKvLimitsForNewOrder.
|
||||||
DisableLegacyLimitWrites bool
|
DisableLegacyLimitWrites bool
|
||||||
|
|
||||||
|
// PropagateCancels controls whether the WFE and ocsp-responder allows
|
||||||
|
// cancellation of an inbound request to cancel downstream gRPC and other
|
||||||
|
// queries. In practice, cancellation of an inbound request is achieved by
|
||||||
|
// Nginx closing the connection on which the request was happening. This may
|
||||||
|
// help shed load in overcapacity situations. However, note that in-progress
|
||||||
|
// database queries (for instance, in the SA) are not cancelled. Database
|
||||||
|
// queries waiting for an available connection may be cancelled.
|
||||||
|
PropagateCancels bool
|
||||||
|
|
||||||
// InsertAuthzsIndividually causes the SA's NewOrderAndAuthzs method to
|
// InsertAuthzsIndividually causes the SA's NewOrderAndAuthzs method to
|
||||||
// create each new authz one at a time, rather than using MultiInserter.
|
// create each new authz one at a time, rather than using MultiInserter.
|
||||||
// Although this is expected to be a performance penalty, it is necessary to
|
// Although this is expected to be a performance penalty, it is necessary to
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@
|
||||||
"Overrides": "test/config-next/wfe2-ratelimit-overrides.yml"
|
"Overrides": "test/config-next/wfe2-ratelimit-overrides.yml"
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
|
"PropagateCancels": true,
|
||||||
"ServeRenewalInfo": true,
|
"ServeRenewalInfo": true,
|
||||||
"CheckIdentifiersPaused": true,
|
"CheckIdentifiersPaused": true,
|
||||||
"UseKvLimitsForNewOrder": true,
|
"UseKvLimitsForNewOrder": true,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/boulder/features"
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -127,11 +128,13 @@ func (th *TopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
Origin: r.Header.Get("Origin"),
|
Origin: r.Header.Get("Origin"),
|
||||||
Extra: make(map[string]interface{}),
|
Extra: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
// We specifically override the default r.Context() because we would prefer
|
if !features.Get().PropagateCancels {
|
||||||
// for clients to not be able to cancel our operations in arbitrary places.
|
// We specifically override the default r.Context() because we would prefer
|
||||||
// Instead we start a new context, and apply timeouts in our various RPCs.
|
// for clients to not be able to cancel our operations in arbitrary places.
|
||||||
ctx := context.WithoutCancel(r.Context())
|
// Instead we start a new context, and apply timeouts in our various RPCs.
|
||||||
r = r.WithContext(ctx)
|
ctx := context.WithoutCancel(r.Context())
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Some clients will send a HTTP Host header that includes the default port
|
// Some clients will send a HTTP Host header that includes the default port
|
||||||
// for the scheme that they are using. Previously when we were fronted by
|
// for the scheme that they are using. Previously when we were fronted by
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@ package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/boulder/features"
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
"github.com/letsencrypt/boulder/test"
|
"github.com/letsencrypt/boulder/test"
|
||||||
)
|
)
|
||||||
|
|
@ -117,3 +120,36 @@ func TestHostHeaderRewrite(t *testing.T) {
|
||||||
req.Host = "localhost:123"
|
req.Host = "localhost:123"
|
||||||
th.ServeHTTP(httptest.NewRecorder(), req)
|
th.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cancelHandler struct {
|
||||||
|
res chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch cancelHandler) ServeHTTP(e *RequestEvent, w http.ResponseWriter, r *http.Request) {
|
||||||
|
select {
|
||||||
|
case <-r.Context().Done():
|
||||||
|
ch.res <- r.Context().Err().Error()
|
||||||
|
case <-time.After(300 * time.Millisecond):
|
||||||
|
ch.res <- "300 ms passed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropagateCancel(t *testing.T) {
|
||||||
|
mockLog := blog.UseMock()
|
||||||
|
res := make(chan string)
|
||||||
|
features.Set(features.Config{PropagateCancels: true})
|
||||||
|
th := NewTopHandler(mockLog, cancelHandler{res})
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", "/thisisignored", &bytes.Reader{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
th.ServeHTTP(httptest.NewRecorder(), req)
|
||||||
|
}()
|
||||||
|
cancel()
|
||||||
|
result := <-res
|
||||||
|
if result != "context canceled" {
|
||||||
|
t.Errorf("expected 'context canceled', got %q", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue