Skip to content
This repository was archived by the owner on Oct 27, 2021. It is now read-only.

Commit 22f3bf7

Browse files
authored
Merge pull request #71 from mdempsky/cache
cache packages to improve speed
2 parents 4d2079a + d9dd64a commit 22f3bf7

10 files changed

+346
-66
lines changed

client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
"runtime/debug"
1616

17-
"github.com/mdempsky/gocode/internal/gbimporter"
17+
"github.com/mdempsky/gocode/internal/cache"
1818
"github.com/mdempsky/gocode/internal/suggest"
1919
)
2020

@@ -126,7 +126,7 @@ func tryToConnect(network, address string) (*rpc.Client, error) {
126126
func cmdAutoComplete(c *rpc.Client) {
127127
var req AutoCompleteRequest
128128
req.Filename, req.Data, req.Cursor = prepareFilenameDataCursor()
129-
req.Context = gbimporter.PackContext(&build.Default)
129+
req.Context = cache.PackContext(&build.Default)
130130
req.Source = *g_source
131131
req.Builtin = *g_builtin
132132
req.IgnoreCase = *g_ignore_case

internal/gbimporter/context.go internal/cache/context.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package gbimporter
1+
package cache
22

33
import "go/build"
44

internal/cache/importer.go

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package cache
2+
3+
import (
4+
"fmt"
5+
"go/build"
6+
goimporter "go/importer"
7+
"go/token"
8+
"go/types"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"sync"
13+
"time"
14+
15+
"golang.org/x/tools/go/gcexportdata"
16+
)
17+
18+
// We need to mangle go/build.Default to make gcimporter work as
19+
// intended, so use a lock to protect against concurrent accesses.
20+
var buildDefaultLock sync.Mutex
21+
22+
// Mu must be held while using the cache importer.
23+
var Mu sync.Mutex
24+
25+
var importCache = importerCache{
26+
fset: token.NewFileSet(),
27+
imports: make(map[string]importCacheEntry),
28+
}
29+
30+
func NewImporter(ctx *PackedContext, filename string) types.ImporterFrom {
31+
importCache.clean()
32+
33+
imp := &importer{
34+
ctx: ctx,
35+
importerCache: &importCache,
36+
}
37+
38+
slashed := filepath.ToSlash(filename)
39+
i := strings.LastIndex(slashed, "/vendor/src/")
40+
if i < 0 {
41+
i = strings.LastIndex(slashed, "/src/")
42+
}
43+
if i > 0 {
44+
paths := filepath.SplitList(imp.ctx.GOPATH)
45+
46+
gbroot := filepath.FromSlash(slashed[:i])
47+
gbvendor := filepath.Join(gbroot, "vendor")
48+
if SamePath(gbroot, imp.ctx.GOROOT) {
49+
goto Found
50+
}
51+
for _, path := range paths {
52+
if SamePath(path, gbroot) || SamePath(path, gbvendor) {
53+
goto Found
54+
}
55+
}
56+
57+
imp.gbroot = gbroot
58+
imp.gbvendor = gbvendor
59+
Found:
60+
}
61+
62+
return imp
63+
}
64+
65+
type importer struct {
66+
*importerCache
67+
gbroot, gbvendor string
68+
ctx *PackedContext
69+
}
70+
71+
type importerCache struct {
72+
fset *token.FileSet
73+
imports map[string]importCacheEntry
74+
}
75+
76+
type importCacheEntry struct {
77+
pkg *types.Package
78+
mtime time.Time
79+
}
80+
81+
func (i *importer) Import(importPath string) (*types.Package, error) {
82+
return i.ImportFrom(importPath, "", 0)
83+
}
84+
85+
func (i *importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (*types.Package, error) {
86+
buildDefaultLock.Lock()
87+
defer buildDefaultLock.Unlock()
88+
89+
origDef := build.Default
90+
defer func() { build.Default = origDef }()
91+
92+
def := &build.Default
93+
// The gb root of a project can be used as a $GOPATH because it contains pkg/.
94+
def.GOPATH = i.ctx.GOPATH
95+
if i.gbroot != "" {
96+
def.GOPATH = i.gbroot
97+
}
98+
def.GOARCH = i.ctx.GOARCH
99+
def.GOOS = i.ctx.GOOS
100+
def.GOROOT = i.ctx.GOROOT
101+
def.CgoEnabled = i.ctx.CgoEnabled
102+
def.UseAllFiles = i.ctx.UseAllFiles
103+
def.Compiler = i.ctx.Compiler
104+
def.BuildTags = i.ctx.BuildTags
105+
def.ReleaseTags = i.ctx.ReleaseTags
106+
def.InstallSuffix = i.ctx.InstallSuffix
107+
def.SplitPathList = i.splitPathList
108+
def.JoinPath = i.joinPath
109+
110+
filename, path := gcexportdata.Find(importPath, srcDir)
111+
entry, ok := i.imports[path]
112+
if filename == "" {
113+
// If there is no export data, check the cache.
114+
// TODO(rstambler): Develop a better heuristic for entry eviction.
115+
if ok && time.Since(entry.mtime) <= time.Minute*20 {
116+
return entry.pkg, nil
117+
}
118+
// If there is no cache entry, import and cache using the source importer.
119+
pkg, err := goimporter.For("source", nil).Import(path)
120+
if pkg != nil {
121+
entry = importCacheEntry{pkg, time.Now()}
122+
i.imports[path] = entry
123+
}
124+
return pkg, err
125+
}
126+
127+
// If there is export data for the package.
128+
fi, err := os.Stat(filename)
129+
if err != nil {
130+
return nil, err
131+
}
132+
if entry.mtime != fi.ModTime() {
133+
f, err := os.Open(filename)
134+
if err != nil {
135+
return nil, err
136+
}
137+
in, err := gcexportdata.NewReader(f)
138+
if err != nil {
139+
return nil, err
140+
}
141+
pkg, err := gcexportdata.Read(in, i.fset, make(map[string]*types.Package), path)
142+
if err != nil {
143+
return nil, err
144+
}
145+
entry = importCacheEntry{pkg, fi.ModTime()}
146+
i.imports[path] = entry
147+
}
148+
149+
return entry.pkg, nil
150+
}
151+
152+
// Delete random files to keep the cache at most 100 entries.
153+
// Only call while holding the importer's mutex.
154+
func (i *importerCache) clean() {
155+
for k := range i.imports {
156+
if len(i.imports) <= 100 {
157+
break
158+
}
159+
delete(i.imports, k)
160+
}
161+
}
162+
163+
func (i *importer) splitPathList(list string) []string {
164+
res := filepath.SplitList(list)
165+
if i.gbroot != "" {
166+
res = append(res, i.gbroot, i.gbvendor)
167+
}
168+
return res
169+
}
170+
171+
func (i *importer) joinPath(elem ...string) string {
172+
res := filepath.Join(elem...)
173+
174+
if i.gbroot != "" {
175+
// Want to rewrite "$GBROOT/(vendor/)?pkg/$GOOS_$GOARCH(_)?"
176+
// into "$GBROOT/pkg/$GOOS-$GOARCH(-)?".
177+
// Note: gb doesn't use vendor/pkg.
178+
if gbrel, err := filepath.Rel(i.gbroot, res); err == nil {
179+
gbrel = filepath.ToSlash(gbrel)
180+
gbrel, _ = match(gbrel, "vendor/")
181+
if gbrel, ok := match(gbrel, fmt.Sprintf("pkg/%s_%s", i.ctx.GOOS, i.ctx.GOARCH)); ok {
182+
gbrel, hasSuffix := match(gbrel, "_")
183+
184+
// Reassemble into result.
185+
if hasSuffix {
186+
gbrel = "-" + gbrel
187+
}
188+
gbrel = fmt.Sprintf("pkg/%s-%s/", i.ctx.GOOS, i.ctx.GOARCH) + gbrel
189+
gbrel = filepath.FromSlash(gbrel)
190+
res = filepath.Join(i.gbroot, gbrel)
191+
}
192+
}
193+
}
194+
return res
195+
}
196+
197+
func match(s, prefix string) (string, bool) {
198+
rest := strings.TrimPrefix(s, prefix)
199+
return rest, len(rest) < len(s)
200+
}

internal/cache/samepath_unix.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// +build !windows
2+
3+
package cache
4+
5+
// SamePath checks two file paths for their equality based on the current filesystem
6+
func SamePath(a, b string) bool {
7+
return a == b
8+
}

internal/cache/samepath_windows.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// +build windows
2+
3+
package cache
4+
5+
import (
6+
"strings"
7+
)
8+
9+
// SamePath checks two file paths for their equality based on the current filesystem
10+
func SamePath(a, b string) bool {
11+
return strings.EqualFold(a, b)
12+
}

internal/gbimporter/gbimporter.go

+10-16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"path/filepath"
99
"strings"
1010
"sync"
11+
12+
"github.com/mdempsky/gocode/internal/cache"
1113
)
1214

1315
// We need to mangle go/build.Default to make gcimporter work as
@@ -17,18 +19,15 @@ var buildDefaultLock sync.Mutex
1719
// importer implements types.ImporterFrom and provides transparent
1820
// support for gb-based projects.
1921
type importer struct {
20-
underlying types.ImporterFrom
21-
ctx *PackedContext
22-
gbroot string
23-
gbpaths []string
22+
ctx *cache.PackedContext
23+
gbroot string
24+
gbpaths []string
2425
}
2526

26-
func New(ctx *PackedContext, filename string, underlying types.ImporterFrom) types.ImporterFrom {
27+
func New(ctx *cache.PackedContext, filename string) types.ImporterFrom {
2728
imp := &importer{
28-
ctx: ctx,
29-
underlying: underlying,
29+
ctx: ctx,
3030
}
31-
3231
slashed := filepath.ToSlash(filename)
3332
i := strings.LastIndex(slashed, "/vendor/src/")
3433
if i < 0 {
@@ -39,11 +38,11 @@ func New(ctx *PackedContext, filename string, underlying types.ImporterFrom) typ
3938

4039
gbroot := filepath.FromSlash(slashed[:i])
4140
gbvendor := filepath.Join(gbroot, "vendor")
42-
if samePath(gbroot, imp.ctx.GOROOT) {
41+
if cache.SamePath(gbroot, imp.ctx.GOROOT) {
4342
goto Found
4443
}
4544
for _, path := range paths {
46-
if samePath(path, gbroot) || samePath(path, gbvendor) {
45+
if cache.SamePath(path, gbroot) || cache.SamePath(path, gbvendor) {
4746
goto Found
4847
}
4948
}
@@ -82,12 +81,7 @@ func (i *importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*type
8281
def.SplitPathList = i.splitPathList
8382
def.JoinPath = i.joinPath
8483

85-
pkg, err := i.underlying.ImportFrom(path, srcDir, mode)
86-
if pkg == nil {
87-
// If importing fails, try importing with source importer.
88-
pkg, _ = goimporter.For("source", nil).(types.ImporterFrom).ImportFrom(path, srcDir, mode)
89-
}
90-
return pkg, err
84+
return goimporter.For("source", nil).Import(path)
9185
}
9286

9387
func (i *importer) splitPathList(list string) []string {

internal/gbimporter/samepath_unix.go

-8
This file was deleted.

internal/gbimporter/samepath_windows.go

-12
This file was deleted.

0 commit comments

Comments
 (0)