Skip to content

Commit

Permalink
internal/frontend/search: don't return 500 on timeout
Browse files Browse the repository at this point in the history
If searches time out, return 408 (request timed out) instead of 500.

We previously did this for symbol searches, but sometimes package
searches can also time out.

Fixes #64422.

Change-Id: I423845e0663866f9743e19ed768f68e6abde6250
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/638596
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
kokoro-CI: kokoro <[email protected]>
  • Loading branch information
jba committed Dec 26, 2024
1 parent 06c6edf commit 8bad909
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 6 deletions.
16 changes: 15 additions & 1 deletion internal/frontend/fetchserver/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type serverTestCase struct {
name string
// path to use in an HTTP GET request
urlPath string
// make the request time out
timeOut bool
// statusCode we expect to see in the headers.
wantStatusCode int
// if non-empty, contents of Location header. For testing redirects.
Expand Down Expand Up @@ -1056,6 +1058,12 @@ var searchGroupingTestCases = []serverTestCase{
wantStatusCode: http.StatusOK,
want: notIn(".Pagination-nav"),
},
{
name: "search timeout",
urlPath: "/search?q=-http",
timeOut: true,
wantStatusCode: http.StatusRequestTimeout,
},
}

// TestServer checks the contents of served pages by looking for
Expand Down Expand Up @@ -1103,7 +1111,13 @@ func testServer(t *testing.T, testCases []serverTestCase) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) { // remove initial '/' for name
w := httptest.NewRecorder()
handler.ServeHTTP(w, httptest.NewRequest("GET", test.urlPath, nil))
rctx := ctx
if test.timeOut {
var cancel func()
rctx, cancel = context.WithTimeout(ctx, time.Microsecond)
defer cancel()
}
handler.ServeHTTP(w, httptest.NewRequestWithContext(rctx, "GET", test.urlPath, nil))
res := w.Result()
if res.StatusCode != test.wantStatusCode {
t.Fatalf("GET %q = %d, want %d", test.urlPath, res.StatusCode, test.wantStatusCode)
Expand Down
8 changes: 4 additions & 4 deletions internal/frontend/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ func determineSearchAction(r *http.Request, ds internal.DataSource, vulnClient *
}
page, err := fetchSearchPage(ctx, ds, cq, symbol, pageParams, mode == searchModeSymbol, vulnClient)
if err != nil {
// Instead of returning a 500, return a 408, since symbol searches may
// timeout for very popular symbols.
if mode == searchModeSymbol && strings.Contains(err.Error(), "i/o timeout") {
// Instead of returning a 500, return a 408, since symbol searches may time
// out for very popular symbols, and package searches can also time out.
if errors.Is(err, context.DeadlineExceeded) || strings.Contains(err.Error(), "i/o timeout") {
return nil, &serrors.ServerError{
Status: http.StatusRequestTimeout,
Epage: &pagepkg.ErrorPage{
MessageTemplate: template.MakeTrustedTemplate(
`<h3 class="Error-message">Request timed out. Please try again!</h3>`),
`<h3 class="Error-message">Request timed out.</h3>`),
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/postgres/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (db *DB) hedgedSearch(ctx context.Context, q string, limit int, opts Search
// error ratio. That didn't behave well if Postgres was overloaded.
resp := <-responses
if resp.err != nil {
return nil, fmt.Errorf("%q search failed: %v", resp.source, resp.err)
return nil, fmt.Errorf("%q search failed: %w", resp.source, resp.err)
}
// cancel proactively here: we've got the search result we need.
cancel()
Expand Down

0 comments on commit 8bad909

Please sign in to comment.