Skip to content

Commit ce6e1cd

Browse files
committed
Dramatically improved progress handling
The progress requests from clangd are now cached and sent back to the IDE with a moderate pace. If a burst of progress reports is received by clangd only the latest one is proxied to the IDE.
1 parent 5f3c065 commit ce6e1cd

File tree

6 files changed

+298
-58
lines changed

6 files changed

+298
-58
lines changed

Diff for: handler/builder.go

+6-24
Original file line numberDiff line numberDiff line change
@@ -54,41 +54,23 @@ func (handler *InoHandler) rebuildEnvironmentLoop() {
5454
// Regenerate preprocessed sketch!
5555
done := make(chan bool)
5656
go func() {
57-
{
58-
// Request a new progress token
59-
req := &lsp.WorkDoneProgressCreateParams{Token: "arduinoLanguageServerRebuild"}
60-
var resp lsp.WorkDoneProgressCreateResult
61-
if err := handler.StdioConn.Call(context.Background(), "window/workDoneProgress/create", req, &resp, nil); err != nil {
62-
log.Printf(" !!! could not create report progress: %s", err)
63-
<-done
64-
return
65-
}
66-
}
6757

68-
req := &lsp.ProgressParams{Token: "arduinoLanguageServerRebuild"}
69-
req.Value = lsp.WorkDoneProgressBegin{
58+
handler.progressHandler.Create("arduinoLanguageServerRebuild")
59+
handler.progressHandler.Begin("arduinoLanguageServerRebuild", &lsp.WorkDoneProgressBegin{
7060
Title: "Building sketch",
71-
}
72-
if err := handler.StdioConn.Notify(context.Background(), "$/progress", req, nil); err != nil {
73-
log.Printf(" !!! could not report progress: %s", err)
74-
}
61+
})
62+
7563
count := 0
7664
dots := []string{".", "..", "..."}
7765
for {
7866
select {
7967
case <-time.After(time.Millisecond * 400):
8068
msg := "compiling" + dots[count%3]
8169
count++
82-
req.Value = lsp.WorkDoneProgressReport{Message: &msg}
83-
if err := handler.StdioConn.Notify(context.Background(), "$/progress", req, nil); err != nil {
84-
log.Printf(" !!! could not report progress: %s", err)
85-
}
70+
handler.progressHandler.Report("arduinoLanguageServerRebuild", &lsp.WorkDoneProgressReport{Message: &msg})
8671
case <-done:
8772
msg := "done"
88-
req.Value = lsp.WorkDoneProgressEnd{Message: &msg}
89-
if err := handler.StdioConn.Notify(context.Background(), "$/progress", req, nil); err != nil {
90-
log.Printf(" !!! could not report progress: %s", err)
91-
}
73+
handler.progressHandler.End("arduinoLanguageServerRebuild", &lsp.WorkDoneProgressEnd{Message: &msg})
9274
return
9375
}
9476
}

Diff for: handler/handler.go

+52-13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type InoHandler struct {
4747

4848
stdioNotificationCount int64
4949
clangdNotificationCount int64
50+
progressHandler *ProgressProxyHandler
5051

5152
clangdStarted *sync.Cond
5253
dataMux sync.RWMutex
@@ -83,6 +84,9 @@ func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler {
8384
stdStream := jsonrpc2.NewBufferedStream(stdio, jsonrpc2.VSCodeObjectCodec{})
8485
var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.HandleMessageFromIDE)
8586
handler.StdioConn = jsonrpc2.NewConn(context.Background(), stdStream, stdHandler)
87+
88+
handler.progressHandler = NewProgressProxy(handler.StdioConn)
89+
8690
if enableLogging {
8791
log.Println("Initial board configuration:", board)
8892
}
@@ -1443,20 +1447,55 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.
14431447
}
14441448
defer log.Printf(prefix + "(done)")
14451449

1446-
log.Printf(prefix + "(queued)")
1447-
switch req.Method {
1448-
case // No locking required
1449-
"$/progress",
1450-
"window/workDoneProgress/create":
1451-
case // Read lock
1452-
"textDocument/publishDiagnostics",
1453-
"workspace/applyEdit":
1454-
handler.dataMux.RLock()
1455-
defer handler.dataMux.RUnlock()
1456-
default: // Default to read lock
1457-
handler.dataMux.RLock()
1458-
defer handler.dataMux.RUnlock()
1450+
if req.Method == "window/workDoneProgress/create" {
1451+
params := lsp.WorkDoneProgressCreateParams{}
1452+
if err := json.Unmarshal(*req.Params, &params); err != nil {
1453+
log.Printf(prefix+"error decoding window/workDoneProgress/create: %v", err)
1454+
return nil, err
1455+
}
1456+
handler.progressHandler.Create(params.Token)
1457+
return &lsp.WorkDoneProgressCreateResult{}, nil
1458+
}
1459+
1460+
if req.Method == "$/progress" {
1461+
// data may be of many different types...
1462+
log.Printf(prefix + "decoding progress...")
1463+
params := lsp.ProgressParams{}
1464+
if err := json.Unmarshal(*req.Params, &params); err != nil {
1465+
log.Printf(prefix+"error decoding progress: %v", err)
1466+
return nil, err
1467+
}
1468+
id := params.Token
1469+
1470+
var begin lsp.WorkDoneProgressBegin
1471+
if err := json.Unmarshal(*params.Value, &begin); err == nil {
1472+
log.Printf(prefix+"begin %s %v", id, begin)
1473+
handler.progressHandler.Begin(id, &begin)
1474+
return nil, nil
1475+
}
1476+
1477+
var report lsp.WorkDoneProgressReport
1478+
if err := json.Unmarshal(*params.Value, &report); err == nil {
1479+
log.Printf(prefix+"report %s %v", id, report)
1480+
handler.progressHandler.Report(id, &report)
1481+
return nil, nil
1482+
}
1483+
1484+
var end lsp.WorkDoneProgressEnd
1485+
if err := json.Unmarshal(*params.Value, &end); err == nil {
1486+
log.Printf(prefix+"end %s %v", id, end)
1487+
handler.progressHandler.End(id, &end)
1488+
return nil, nil
1489+
}
1490+
1491+
log.Printf(prefix + "error unsupported $/progress: " + string(*params.Value))
1492+
return nil, errors.New("unsupported $/progress: " + string(*params.Value))
14591493
}
1494+
1495+
// Default to read lock
1496+
log.Printf(prefix + "(queued)")
1497+
handler.dataMux.RLock()
1498+
defer handler.dataMux.RUnlock()
14601499
log.Printf(prefix + "(running)")
14611500

14621501
params, err := lsp.ReadParams(req.Method, req.Params)

Diff for: handler/progress.go

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"log"
6+
"sync"
7+
8+
"github.com/bcmi-labs/arduino-language-server/lsp"
9+
"github.com/bcmi-labs/arduino-language-server/streams"
10+
"github.com/sourcegraph/jsonrpc2"
11+
)
12+
13+
type ProgressProxyHandler struct {
14+
conn *jsonrpc2.Conn
15+
mux sync.Mutex
16+
actionRequiredCond *sync.Cond
17+
proxies map[string]*progressProxy
18+
}
19+
20+
type progressProxyStatus int
21+
22+
const (
23+
progressProxyNew progressProxyStatus = iota
24+
progressProxyCreated
25+
progressProxyBegin
26+
progressProxyReport
27+
progressProxyEnd
28+
)
29+
30+
type progressProxy struct {
31+
currentStatus progressProxyStatus
32+
requiredStatus progressProxyStatus
33+
beginReq *lsp.WorkDoneProgressBegin
34+
reportReq *lsp.WorkDoneProgressReport
35+
endReq *lsp.WorkDoneProgressEnd
36+
}
37+
38+
func NewProgressProxy(conn *jsonrpc2.Conn) *ProgressProxyHandler {
39+
res := &ProgressProxyHandler{
40+
conn: conn,
41+
proxies: map[string]*progressProxy{},
42+
}
43+
res.actionRequiredCond = sync.NewCond(&res.mux)
44+
go res.handlerLoop()
45+
return res
46+
}
47+
48+
func (p *ProgressProxyHandler) handlerLoop() {
49+
defer streams.CatchAndLogPanic()
50+
51+
p.mux.Lock()
52+
defer p.mux.Unlock()
53+
54+
for {
55+
p.actionRequiredCond.Wait()
56+
57+
for id, proxy := range p.proxies {
58+
for proxy.currentStatus != proxy.requiredStatus {
59+
p.handleProxy(id, proxy)
60+
}
61+
}
62+
63+
// Cleanup ended proxies
64+
for id, proxy := range p.proxies {
65+
if proxy.currentStatus == progressProxyEnd {
66+
delete(p.proxies, id)
67+
}
68+
}
69+
}
70+
}
71+
72+
func (p *ProgressProxyHandler) handleProxy(id string, proxy *progressProxy) {
73+
ctx := context.Background()
74+
switch proxy.currentStatus {
75+
case progressProxyNew:
76+
p.mux.Unlock()
77+
var res lsp.WorkDoneProgressCreateResult
78+
err := p.conn.Call(ctx, "window/workDoneProgress/create", &lsp.WorkDoneProgressCreateParams{Token: id}, &res)
79+
p.mux.Lock()
80+
81+
if err != nil {
82+
log.Printf("ProgressHandler: error creating token %s: %v", id, err)
83+
} else {
84+
proxy.currentStatus = progressProxyCreated
85+
}
86+
87+
case progressProxyCreated:
88+
p.mux.Unlock()
89+
err := p.conn.Notify(ctx, "$/progress", lsp.ProgressParams{
90+
Token: id,
91+
Value: lsp.Raw(proxy.beginReq),
92+
})
93+
p.mux.Lock()
94+
95+
proxy.beginReq = nil
96+
if err != nil {
97+
log.Printf("ProgressHandler: error sending begin req token %s: %v", id, err)
98+
} else {
99+
proxy.currentStatus = progressProxyBegin
100+
}
101+
102+
case progressProxyBegin:
103+
if proxy.requiredStatus == progressProxyReport {
104+
p.mux.Unlock()
105+
err := p.conn.Notify(ctx, "$/progress", &lsp.ProgressParams{
106+
Token: id,
107+
Value: lsp.Raw(proxy.reportReq)})
108+
p.mux.Lock()
109+
110+
proxy.reportReq = nil
111+
if err != nil {
112+
log.Printf("ProgressHandler: error sending begin req token %s: %v", id, err)
113+
} else {
114+
proxy.requiredStatus = progressProxyBegin
115+
}
116+
117+
} else if proxy.requiredStatus == progressProxyEnd {
118+
p.mux.Unlock()
119+
err := p.conn.Notify(ctx, "$/progress", &lsp.ProgressParams{
120+
Token: id,
121+
Value: lsp.Raw(proxy.endReq),
122+
})
123+
p.mux.Lock()
124+
125+
proxy.endReq = nil
126+
if err != nil {
127+
log.Printf("ProgressHandler: error sending begin req token %s: %v", id, err)
128+
} else {
129+
proxy.currentStatus = progressProxyEnd
130+
}
131+
132+
}
133+
}
134+
}
135+
136+
func (p *ProgressProxyHandler) Create(id string) {
137+
p.mux.Lock()
138+
defer p.mux.Unlock()
139+
140+
if _, opened := p.proxies[id]; opened {
141+
// Already created
142+
return
143+
}
144+
145+
p.proxies[id] = &progressProxy{
146+
currentStatus: progressProxyNew,
147+
requiredStatus: progressProxyCreated,
148+
}
149+
p.actionRequiredCond.Broadcast()
150+
}
151+
152+
func (p *ProgressProxyHandler) Begin(id string, req *lsp.WorkDoneProgressBegin) {
153+
p.mux.Lock()
154+
defer p.mux.Unlock()
155+
156+
proxy, ok := p.proxies[id]
157+
if !ok {
158+
return
159+
}
160+
161+
proxy.beginReq = req
162+
proxy.requiredStatus = progressProxyBegin
163+
p.actionRequiredCond.Broadcast()
164+
}
165+
166+
func (p *ProgressProxyHandler) Report(id string, req *lsp.WorkDoneProgressReport) {
167+
p.mux.Lock()
168+
defer p.mux.Unlock()
169+
170+
proxy, ok := p.proxies[id]
171+
if !ok {
172+
return
173+
}
174+
175+
proxy.reportReq = req
176+
proxy.requiredStatus = progressProxyReport
177+
p.actionRequiredCond.Broadcast()
178+
}
179+
180+
func (p *ProgressProxyHandler) End(id string, req *lsp.WorkDoneProgressEnd) {
181+
p.mux.Lock()
182+
defer p.mux.Unlock()
183+
184+
proxy, ok := p.proxies[id]
185+
if !ok {
186+
return
187+
}
188+
189+
proxy.endReq = req
190+
proxy.requiredStatus = progressProxyEnd
191+
p.actionRequiredCond.Broadcast()
192+
}

Diff for: handler/syncer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type AsyncHandler struct {
1515
func (ah AsyncHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
1616
switch req.Method {
1717
case // Request that should not be parallelized
18-
"$/progress":
18+
"window/workDoneProgress/create", "$/progress":
1919
ah.handler.Handle(ctx, conn, req)
2020
default: // By default process all requests in parallel
2121
go ah.handler.Handle(ctx, conn, req)

Diff for: lsp/protocol_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,22 @@ func TestDocumentSymbolParse(t *testing.T) {
6868
func TestVariousMessages(t *testing.T) {
6969
x := &ProgressParams{
7070
Token: "token",
71-
Value: WorkDoneProgressBegin{
71+
Value: Raw(WorkDoneProgressBegin{
7272
Title: "some work",
73-
},
73+
}),
7474
}
7575
data, err := json.Marshal(&x)
7676
require.NoError(t, err)
7777
require.JSONEq(t, `{"token":"token", "value":{"kind":"begin","title":"some work"}}`, string(data))
7878

79+
var begin WorkDoneProgressBegin
80+
err = json.Unmarshal([]byte(`{"kind":"begin","title":"some work"}`), &begin)
81+
require.NoError(t, err)
82+
83+
var report WorkDoneProgressReport
84+
err = json.Unmarshal([]byte(`{"kind":"report","message":"28/29","percentage":96.551724137931032}`), &report)
85+
require.NoError(t, err)
86+
7987
msg := `{
8088
"capabilities":{
8189
"codeActionProvider":{

0 commit comments

Comments
 (0)