Skip to content

Commit b709a3e

Browse files
rscgopherbot
authored andcommitted
cmd/go/internal/vcweb: cache hg servers
Cuts TestScript/reuse_hg from 73s to 47s. (Python startup is slow! What's left is all Python too!) Change-Id: Ia7124d4819286b3820355e4f427ffcfdc125491b Reviewed-on: https://go-review.googlesource.com/c/go/+/718501 Reviewed-by: Michael Matloob <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Auto-Submit: Russ Cox <[email protected]>
1 parent 426ef30 commit b709a3e

File tree

1 file changed

+65
-17
lines changed
  • src/cmd/go/internal/vcweb

1 file changed

+65
-17
lines changed

src/cmd/go/internal/vcweb/hg.go

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ type hgHandler struct {
2525
once sync.Once
2626
hgPath string
2727
hgPathErr error
28+
29+
mu sync.Mutex
30+
wg sync.WaitGroup
31+
ctx context.Context
32+
cancel func()
33+
cmds []*exec.Cmd
34+
url map[string]*url.URL
2835
}
2936

3037
func (h *hgHandler) Available() bool {
@@ -34,6 +41,30 @@ func (h *hgHandler) Available() bool {
3441
return h.hgPathErr == nil
3542
}
3643

44+
func (h *hgHandler) Close() error {
45+
h.mu.Lock()
46+
defer h.mu.Unlock()
47+
48+
if h.cancel == nil {
49+
return nil
50+
}
51+
52+
h.cancel()
53+
for _, cmd := range h.cmds {
54+
h.wg.Add(1)
55+
go func() {
56+
cmd.Wait()
57+
h.wg.Done()
58+
}()
59+
}
60+
h.wg.Wait()
61+
h.url = nil
62+
h.cmds = nil
63+
h.ctx = nil
64+
h.cancel = nil
65+
return nil
66+
}
67+
3768
func (h *hgHandler) Handler(dir string, env []string, logger *log.Logger) (http.Handler, error) {
3869
if !h.Available() {
3970
return nil, ServerNotInstalledError{name: "hg"}
@@ -50,10 +81,25 @@ func (h *hgHandler) Handler(dir string, env []string, logger *log.Logger) (http.
5081
// if "hg" works at all then "hg serve" works too, and we'll execute that as
5182
// a subprocess, using a reverse proxy to forward the request and response.
5283

53-
ctx, cancel := context.WithCancel(req.Context())
54-
defer cancel()
84+
h.mu.Lock()
85+
86+
if h.ctx == nil {
87+
h.ctx, h.cancel = context.WithCancel(context.Background())
88+
}
5589

56-
cmd := exec.CommandContext(ctx, h.hgPath, "serve", "--port", "0", "--address", "localhost", "--accesslog", os.DevNull, "--name", "vcweb", "--print-url")
90+
// Cache the hg server subprocess globally, because hg is too slow
91+
// to start a new one for each request. There are under a dozen different
92+
// repos we serve, so leaving a dozen processes around is not a big deal.
93+
u := h.url[dir]
94+
if u != nil {
95+
h.mu.Unlock()
96+
logger.Printf("proxying hg request to %s", u)
97+
httputil.NewSingleHostReverseProxy(u).ServeHTTP(w, req)
98+
return
99+
}
100+
101+
logger.Printf("starting hg serve for %s", dir)
102+
cmd := exec.CommandContext(h.ctx, h.hgPath, "serve", "--port", "0", "--address", "localhost", "--accesslog", os.DevNull, "--name", "vcweb", "--print-url")
57103
cmd.Dir = dir
58104
cmd.Env = append(slices.Clip(env), "PWD="+dir)
59105

@@ -74,39 +120,32 @@ func (h *hgHandler) Handler(dir string, env []string, logger *log.Logger) (http.
74120

75121
stdout, err := cmd.StdoutPipe()
76122
if err != nil {
123+
h.mu.Unlock()
77124
http.Error(w, err.Error(), http.StatusInternalServerError)
78125
return
79126
}
80127

81128
if err := cmd.Start(); err != nil {
129+
h.mu.Unlock()
82130
http.Error(w, err.Error(), http.StatusInternalServerError)
83131
return
84132
}
85-
var wg sync.WaitGroup
86-
defer func() {
87-
cancel()
88-
err := cmd.Wait()
89-
if out := strings.TrimSuffix(stderr.String(), "interrupted!\n"); out != "" {
90-
logger.Printf("%v: %v\n%s", cmd, err, out)
91-
} else {
92-
logger.Printf("%v", cmd)
93-
}
94-
wg.Wait()
95-
}()
96133

97134
r := bufio.NewReader(stdout)
98135
line, err := r.ReadString('\n')
99136
if err != nil {
137+
h.mu.Unlock()
138+
http.Error(w, err.Error(), http.StatusInternalServerError)
100139
return
101140
}
102141
// We have read what should be the server URL. 'hg serve' shouldn't need to
103142
// write anything else to stdout, but it's not a big deal if it does anyway.
104143
// Keep the stdout pipe open so that 'hg serve' won't get a SIGPIPE, but
105144
// actively discard its output so that it won't hang on a blocking write.
106-
wg.Add(1)
145+
h.wg.Add(1)
107146
go func() {
108147
io.Copy(io.Discard, r)
109-
wg.Done()
148+
h.wg.Done()
110149
}()
111150

112151
// On some systems,
@@ -116,12 +155,21 @@ func (h *hgHandler) Handler(dir string, env []string, logger *log.Logger) (http.
116155
line = strings.ReplaceAll(line, "//1.0.0.127.in-addr.arpa", "//127.0.0.1")
117156
line = strings.ReplaceAll(line, "//1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa", "//[::1]")
118157

119-
u, err := url.Parse(strings.TrimSpace(line))
158+
u, err = url.Parse(strings.TrimSpace(line))
120159
if err != nil {
160+
h.mu.Unlock()
121161
logger.Printf("%v: %v", cmd, err)
122162
http.Error(w, err.Error(), http.StatusBadGateway)
123163
return
124164
}
165+
166+
if h.url == nil {
167+
h.url = make(map[string]*url.URL)
168+
}
169+
h.url[dir] = u
170+
h.cmds = append(h.cmds, cmd)
171+
h.mu.Unlock()
172+
125173
logger.Printf("proxying hg request to %s", u)
126174
httputil.NewSingleHostReverseProxy(u).ServeHTTP(w, req)
127175
})

0 commit comments

Comments
 (0)