diff --git a/cmd/zoekt-sourcegraph-indexserver/main.go b/cmd/zoekt-sourcegraph-indexserver/main.go index 7d3591b50..2fe32d478 100644 --- a/cmd/zoekt-sourcegraph-indexserver/main.go +++ b/cmd/zoekt-sourcegraph-indexserver/main.go @@ -9,6 +9,7 @@ import ( "flag" "fmt" "html/template" + "io" "io/ioutil" "log" "math" @@ -95,6 +96,8 @@ func (s *Server) loggedRun(tr trace.Trace, cmd *exec.Cmd) error { // Run the sync loop. This blocks forever. func (s *Server) Run() { + waitForFrontend(s.Root) + queue := &Queue{} // Start a goroutine which updates the queue with commits to index. @@ -408,6 +411,50 @@ func resolveRevision(root *url.URL, repo, spec string) (string, error) { return b.String(), nil } +func ping(root *url.URL) error { + u := root.ResolveReference(&url.URL{Path: "/.internal/ping"}) + resp, err := http.Get(u.String()) + if err != nil { + return err + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("ping: bad HTTP response status %d", resp.StatusCode) + } + body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 16)) + if err != nil { + return err + } + if want := []byte("pong"); !bytes.Equal(body, want) { + return fmt.Errorf("ping: bad HTTP response body %q (want %q)", string(body), string(want)) + } + return nil +} + +func waitForFrontend(root *url.URL) { + warned := false + lastWarn := time.Now() + for { + err := ping(root) + if err == nil { + break + } + + if time.Since(lastWarn) > 15*time.Second { + warned = true + lastWarn = time.Now() + log.Printf("frontend API not reachable, will try again: %s", err) + } + + time.Sleep(250 * time.Millisecond) + } + + if warned { + log.Println("frontend API is now reachable. Starting indexing...") + } +} + func tarballURL(root *url.URL, repo, commit string) string { return root.ResolveReference(&url.URL{Path: fmt.Sprintf("/.internal/git/%s/tar/%s", repo, commit)}).String() } diff --git a/cmd/zoekt-sourcegraph-indexserver/main_test.go b/cmd/zoekt-sourcegraph-indexserver/main_test.go index f1ad36666..8b54e2c1f 100644 --- a/cmd/zoekt-sourcegraph-indexserver/main_test.go +++ b/cmd/zoekt-sourcegraph-indexserver/main_test.go @@ -1,12 +1,15 @@ package main import ( + "fmt" "io/ioutil" "net/http" "net/http/httptest" "net/url" "reflect" + "strings" "testing" + "time" ) func TestGetIndexOptions(t *testing.T) { @@ -84,3 +87,46 @@ func TestListRepos(t *testing.T) { t.Fatalf("unexpected request path. got %q, want %q", gotURL.Path, want) } } + +func TestPing(t *testing.T) { + var response []byte + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/.internal/ping" { + http.Error(w, "not found", http.StatusNotFound) + return + } + _, _ = w.Write(response) + })) + defer server.Close() + + root, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + + // Ping fails + response = []byte("hello") + err = ping(root) + if got, want := fmt.Sprintf("%v", err), "bad HTTP response body"; !strings.Contains(got, want) { + t.Errorf("wanted ping to fail,\ngot: %q\nwant: %q", got, want) + } + + response = []byte("pong") + err = ping(root) + if err != nil { + t.Errorf("wanted ping to succeed, got: %v", err) + } + + // We expect waitForFrontend to just work now + done := make(chan struct{}) + go func() { + waitForFrontend(root) + close(done) + }() + + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("waitForFrontend blocking") + } +}