fork of https://github.com/sourcegraph/zoekt
1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ctags
16
17import (
18 "fmt"
19 "log"
20 "os"
21 "strings"
22 "sync"
23 "time"
24
25 goctags "github.com/sourcegraph/go-ctags"
26)
27
28const debug = false
29
30type Parser = goctags.Parser
31type Entry = goctags.Entry
32
33type parseReq struct {
34 Name string
35 Content []byte
36}
37
38type parseResp struct {
39 Entries []*Entry
40 Err error
41}
42
43type lockedParser struct {
44 mu sync.Mutex
45 opts goctags.Options
46 p Parser
47 send chan<- parseReq
48 recv <-chan parseResp
49}
50
51// parseTimeout is how long we wait for a response for parsing a single file
52// in ctags. 1 minute is a very conservative timeout which we should only hit
53// if ctags hangs.
54const parseTimeout = time.Minute
55
56// Parse wraps go-ctags Parse. It lazily starts the process and adds a timeout
57// around parse requests. Additionally it serializes access to the parsing
58// process. The timeout is important since we occasionally come across
59// documents which hang universal-ctags.
60func (lp *lockedParser) Parse(name string, content []byte) ([]*Entry, error) {
61 lp.mu.Lock()
62 defer lp.mu.Unlock()
63
64 if lp.p == nil {
65 p, err := goctags.New(lp.opts)
66 if err != nil {
67 return nil, err
68 }
69 send := make(chan parseReq)
70 // buf of 1 so we avoid blocking sends in the parser if we exit early.
71 recv := make(chan parseResp, 1)
72
73 go func() {
74 defer close(recv)
75 for req := range send {
76 entries, err := p.Parse(req.Name, req.Content)
77 recv <- parseResp{Entries: entries, Err: err}
78 }
79 }()
80
81 lp.p = p
82 lp.send = send
83 lp.recv = recv
84 }
85
86 lp.send <- parseReq{Name: name, Content: content}
87
88 deadline := time.NewTimer(parseTimeout)
89 defer deadline.Stop()
90
91 select {
92 case resp := <-lp.recv:
93 return resp.Entries, resp.Err
94 case <-deadline.C:
95 // Error out since ctags hanging is a sign something bad is happening.
96 lp.close()
97 return nil, fmt.Errorf("ctags timedout after %s parsing %s", parseTimeout, name)
98 }
99}
100
101func (lp *lockedParser) Close() {
102 lp.mu.Lock()
103 defer lp.mu.Unlock()
104 lp.close()
105}
106
107// close assumes lp.mu is held.
108func (lp *lockedParser) close() {
109 if lp.p == nil {
110 return
111 }
112
113 lp.p.Close()
114 lp.p = nil
115 close(lp.send)
116 lp.send = nil
117 lp.recv = nil
118}
119
120// NewParser creates a parser that is implemented by the given
121// universal-ctags binary. The parser is safe for concurrent use.
122func NewParser(bin string) (Parser, error) {
123 if strings.Contains(bin, "universal-ctags") {
124 opts := goctags.Options{
125 Bin: bin,
126 }
127 if debug {
128 opts.Info = log.New(os.Stderr, "CTAGS INF: ", log.LstdFlags)
129 opts.Debug = log.New(os.Stderr, "CTAGS DBG: ", log.LstdFlags)
130 }
131 return &lockedParser{
132 opts: opts,
133 }, nil
134 }
135
136 log.Fatal("not implemented")
137 return nil, nil
138}