Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic read-only replica handling #470

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/hound-search/hound/codesearch/index"
"github.com/hound-search/hound/codesearch/regexp"
"github.com/hound-search/hound/vcs"
)

const (
Expand Down Expand Up @@ -115,13 +116,24 @@ func (n *Index) Close() error {
return n.idx.Close()
}

// Destroy the index and if possible, destroy the index directory
func (n *Index) Destroy() error {
n.lck.Lock()
defer n.lck.Unlock()
if err := n.idx.Close(); err != nil {
return err
}
return n.Ref.Remove()
err := n.Ref.Remove()
if err != nil {
parent := filepath.Dir(n.GetDir())
if writeable, _ := vcs.IsWriteable(parent); writeable {
// if it seems like we can write to the parent directory, but we can't remove the
// vcs directory, continue to return the error
return err
}
return nil
}
return nil
}

func (n *Index) GetDir() string {
Expand Down
49 changes: 46 additions & 3 deletions searcher/searcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,40 @@ func buildAndOpenIndex(
return index.Open(idxDir)
}

// finds an already built index for a new revision. Used for a read only
// replica instance to update the index associated with a repository when
// a new repository revision is pulled
func findNewExistingIndex(
s *Searcher,
dbpath string,
name string,
url string,
rev string,
newRev string,
) (string, bool) {
refs, err := findExistingRefs(dbpath)
if err != nil {
log.Printf("failed to update existing indices (%s): %s", name, err)
return rev, false
}
ref := refs.find(url, newRev)
if ref == nil {
log.Printf("not able to find existing index (%s)", name)
return rev, false
}
idx, err := index.Open(ref.Dir())
if err != nil {
log.Printf("failed to open existing index (%s): %s", name, err)
return rev, false
}
if err := s.swapIndexes(idx); err != nil {
log.Printf("failed index swap (%s): %s", name, err)
return rev, false
}

return newRev, true
}

// Simply prints out statistics about the heap. When hound rebuilds a new
// index it will expand the heap with a decent amount of garbage. This is
// helpful to ensure the heap growth looks sane.
Expand All @@ -264,7 +298,7 @@ func reportOnMemory() {
// Utility function for producing a hex encoded sha1 hash for a string.
func hashFor(name string) string {
h := sha1.New()
h.Write([]byte(name)) //nolint
h.Write([]byte(name)) //nolint
return hex.EncodeToString(h.Sum(nil))
}

Expand Down Expand Up @@ -365,6 +399,15 @@ func updateAndReindex(
if newRev == rev {
return rev, false
}
if writeable, _ := vcs.IsWriteable(dbpath); !writeable {
// not writeable, let's update our ref list and look for it.
log.Printf(
"dbpath (%s) not writeable for update to (%s), updating existing indices",
dbpath,
name,
)
return findNewExistingIndex(s, dbpath, name, repo.Url, rev, newRev)
}

log.Printf("Rebuilding %s for %s", name, newRev)
idx, err := buildAndOpenIndex(
Expand Down Expand Up @@ -407,7 +450,6 @@ func newSearcher(
return nil, err
}


rev, err := wd.PullOrClone(vcsDir, repo.Url)
if err != nil {
return nil, err
Expand Down Expand Up @@ -441,7 +483,8 @@ func newSearcher(
vcsDir,
idxDir,
repo.Url,
rev)
rev,
)
if err != nil {
return nil, err
}
Expand Down
29 changes: 27 additions & 2 deletions vcs/vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vcs
import (
"fmt"
"log"
"math/rand"
"os"
)

Expand All @@ -29,7 +30,6 @@ type Driver interface {

// Return a list of filenames that are marked as auto-generated.
AutoGeneratedFiles(dir string) []string

}

// An API to interact with a vcs working directory. This is
Expand Down Expand Up @@ -71,11 +71,36 @@ func exists(path string) bool {
return true
}

// Tests if a path is writeable by creating and then removing a
// temporary file in the directory
func IsWriteable(path string) (bool, error) {

r := uint64(rand.Uint32())<<32 | uint64(rand.Uint32())
tmpFile := fmt.Sprintf("tmp-%08x", r)

file, err := os.CreateTemp(path, tmpFile)
if err != nil {
return false, err
}

defer os.Remove(file.Name())
defer file.Close()

return true, nil
}

// A utility method that carries out the common operation of cloning
// if the working directory is absent and pulling otherwise.
func (w *WorkDir) PullOrClone(dir, url string) (string, error) {
if exists(dir) {
return w.Pull(dir)
writeable, _ := IsWriteable(dir)
// if we can write to the directory, pull
if writeable {
return w.Pull(dir)
}

// not writeable, just get current revision
return w.HeadRev(dir)
}
return w.Clone(dir, url)
}
21 changes: 20 additions & 1 deletion vcs/vcs_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package vcs

import (
"os"
"runtime"
"testing"
)

// TODO(knorton): Write tests for the vcs interactions

// Just make sure all drivers are tolerant of nil
func TestNilConfigs(t *testing.T) {
for name, _ := range drivers { //nolint
for name, _ := range drivers { //nolint
d, err := New(name, nil)
if err != nil {
t.Fatal(err)
Expand All @@ -19,3 +21,20 @@ func TestNilConfigs(t *testing.T) {
}
}
}

func TestIsWriteable(t *testing.T) {
dir := t.TempDir()
if writeable, err := IsWriteable(dir); !writeable {
t.Fatalf("%s is not writeable but should be: %s", dir, err)
}
if runtime.GOOS == "windows" {
t.Skip("Skipping testing RO directory in Windows environment")
}
if err := os.Chmod(dir, 0555); err != nil {
t.Fatal(err)
}

if writeable, err := IsWriteable(dir); writeable {
t.Fatalf("%s is writeable but should not be: %s", dir, err)
}
}