1
0
mirror of https://github.com/golang/go synced 2024-11-25 02:57:57 -07:00

godoc: compute search index for all file systems under godoc's observation

R=rsc
CC=golang-dev
https://golang.org/cl/3209041
This commit is contained in:
Robert Griesemer 2010-11-18 19:55:38 -08:00
parent b3dd22fecb
commit ad21c42f05
3 changed files with 92 additions and 31 deletions

View File

@ -43,6 +43,7 @@ func (dt *delayTime) backoff(max int) {
v = max v = max
} }
dt.value = v dt.value = v
// don't change dt.timestamp - calling backoff indicates an error condition
dt.mutex.Unlock() dt.mutex.Unlock()
} }
@ -66,6 +67,7 @@ var (
fsMap Mapping // user-defined mapping fsMap Mapping // user-defined mapping
fsTree RWValue // *Directory tree of packages, updated with each sync fsTree RWValue // *Directory tree of packages, updated with each sync
pathFilter RWValue // filter used when building fsMap directory trees pathFilter RWValue // filter used when building fsMap directory trees
fsModified RWValue // timestamp of last call to invalidateIndex
// http handlers // http handlers
fileServer http.Handler // default file server fileServer http.Handler // default file server
@ -179,13 +181,21 @@ func readDirList(filename string) ([]string, os.Error) {
} }
func updateFilterFile() { // updateMappedDirs computes the directory tree for
// for each user-defined file system mapping, compute // each user-defined file system mapping. If a filter
// respective directory tree w/o filter for accuracy // is provided, it is used to filter directories.
//
func updateMappedDirs(filter func(string) bool) {
fsMap.Iterate(func(path string, value *RWValue) bool { fsMap.Iterate(func(path string, value *RWValue) bool {
value.set(newDirectory(path, nil, -1)) value.set(newDirectory(path, filter, -1))
return true return true
}) })
invalidateIndex()
}
func updateFilterFile() {
updateMappedDirs(nil) // no filter for accuracy
// collect directory tree leaf node paths // collect directory tree leaf node paths
var buf bytes.Buffer var buf bytes.Buffer
@ -219,12 +229,7 @@ func initDirTrees() {
setPathFilter(list) setPathFilter(list)
} }
// for each user-defined file system mapping, compute go updateMappedDirs(getPathFilter()) // use filter for speed
// respective directory tree quickly using pathFilter
go fsMap.Iterate(func(path string, value *RWValue) bool {
value.set(newDirectory(path, getPathFilter(), -1))
return true
})
// start filter update goroutine, if enabled. // start filter update goroutine, if enabled.
if *filter != "" && *filterMin > 0 { if *filter != "" && *filterMin > 0 {
@ -1350,16 +1355,62 @@ func search(w http.ResponseWriter, r *http.Request) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Indexer // Indexer
// invalidateIndex should be called whenever any of the file systems
// under godoc's observation change so that the indexer is kicked on.
//
func invalidateIndex() {
fsModified.set(nil)
}
// indexUpToDate() returns true if the search index is not older
// than any of the file systems under godoc's observation.
//
func indexUpToDate() bool {
_, fsTime := fsModified.get()
_, siTime := searchIndex.get()
return fsTime <= siTime
}
// feedDirnames feeds the directory names of all directories
// under the file system given by root to channel c.
//
func feedDirnames(root *RWValue, c chan<- string) {
if dir, _ := root.get(); dir != nil {
for d := range dir.(*Directory).iter(false) {
c <- d.Path
}
}
}
// fsDirnames() returns a channel sending all directory names
// of all the file systems under godoc's observation.
//
func fsDirnames() <-chan string {
c := make(chan string, 256) // asynchronous for fewer context switches
go func() {
feedDirnames(&fsTree, c)
fsMap.Iterate(func(_ string, root *RWValue) bool {
feedDirnames(root, c)
return true
})
close(c)
}()
return c
}
func indexer() { func indexer() {
for { for {
_, ts := fsTree.get() if !indexUpToDate() {
if _, timestamp := searchIndex.get(); timestamp < ts {
// index possibly out of date - make a new one // index possibly out of date - make a new one
// (could use a channel to send an explicit signal if *verbose {
// from the sync goroutine, but this solution is log.Printf("updating index...")
// more decoupled, trivial, and works well enough) }
start := time.Nanoseconds() start := time.Nanoseconds()
index := NewIndex(*goroot) index := NewIndex(fsDirnames())
stop := time.Nanoseconds() stop := time.Nanoseconds()
searchIndex.set(index) searchIndex.set(index)
if *verbose { if *verbose {

View File

@ -30,6 +30,7 @@ import (
"go/parser" "go/parser"
"go/token" "go/token"
"go/scanner" "go/scanner"
"io/ioutil"
"os" "os"
pathutil "path" pathutil "path"
"sort" "sort"
@ -578,11 +579,6 @@ func (x *Indexer) Visit(node interface{}) ast.Visitor {
} }
func (x *Indexer) VisitDir(path string, f *os.FileInfo) bool {
return true
}
func pkgName(filename string) string { func pkgName(filename string) string {
file, err := parser.ParseFile(filename, nil, parser.PackageClauseOnly) file, err := parser.ParseFile(filename, nil, parser.PackageClauseOnly)
if err != nil || file == nil { if err != nil || file == nil {
@ -592,11 +588,12 @@ func pkgName(filename string) string {
} }
func (x *Indexer) VisitFile(path string, f *os.FileInfo) { func (x *Indexer) visitFile(dirname string, f *os.FileInfo) {
if !isGoFile(f) { if !isGoFile(f) {
return return
} }
path := pathutil.Join(dirname, f.Name)
if excludeTestFiles && (!isPkgFile(f) || strings.HasPrefix(path, "test/")) { if excludeTestFiles && (!isPkgFile(f) || strings.HasPrefix(path, "test/")) {
return return
} }
@ -637,15 +634,27 @@ type Index struct {
func canonical(w string) string { return strings.ToLower(w) } func canonical(w string) string { return strings.ToLower(w) }
// NewIndex creates a new index for the file tree rooted at root. // NewIndex creates a new index for the .go files
func NewIndex(root string) *Index { // in the directories given by dirnames.
//
func NewIndex(dirnames <-chan string) *Index {
var x Indexer var x Indexer
// initialize Indexer // initialize Indexer
x.words = make(map[string]*IndexResult) x.words = make(map[string]*IndexResult)
// collect all Spots // index all files in the directories given by dirnames
pathutil.Walk(root, &x, nil) for dirname := range dirnames {
list, err := ioutil.ReadDir(dirname)
if err != nil {
continue // ignore this directory
}
for _, f := range list {
if !f.IsDirectory() {
x.visitFile(dirname, f)
}
}
}
// for each word, reduce the RunLists into a LookupResult; // for each word, reduce the RunLists into a LookupResult;
// also collect the word with its canonical spelling in a // also collect the word with its canonical spelling in a

View File

@ -128,6 +128,7 @@ func dosync(w http.ResponseWriter, r *http.Request) {
// Consider keeping separate time stamps so the web- // Consider keeping separate time stamps so the web-
// page can indicate this discrepancy. // page can indicate this discrepancy.
fsTree.set(newDirectory(*goroot, nil, -1)) fsTree.set(newDirectory(*goroot, nil, -1))
invalidateIndex()
fallthrough fallthrough
case 1: case 1:
// sync failed because no files changed; // sync failed because no files changed;
@ -255,11 +256,11 @@ func main() {
} }
// Initialize default directory tree with corresponding timestamp. // Initialize default directory tree with corresponding timestamp.
// Do it in two steps: // (Do it in a goroutine so that launch is quick.)
// 1) set timestamp right away so that the indexer is kicked on go func() {
fsTree.set(nil) fsTree.set(newDirectory(*goroot, nil, -1))
// 2) compute initial directory tree in a goroutine so that launch is quick invalidateIndex()
go func() { fsTree.set(newDirectory(*goroot, nil, -1)) }() }()
// Initialize directory trees for user-defined file systems (-path flag). // Initialize directory trees for user-defined file systems (-path flag).
initDirTrees() initDirTrees()