turbo-editors/turbo-corepublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

conn.go · 253 lines · 6.4 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1package jsonrpc
2
3import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "sync"
11)
12
13// Conn is a JSON-RPC 2.0 connection over a framed stream.
14//
15// It is safe for concurrent use: several goroutines may call and notify at
16// once, and replies are matched back to their callers by request id.
17type Conn struct {
18 stream io.ReadWriteCloser
19 reader *bufio.Reader
20 framer Framer
21
22 onNotify NotificationFunc
23 onRequest RequestFunc
24
25 writeMu sync.Mutex
26
27 mu sync.Mutex
28 nextID int64
29 pending map[int64]chan *message
30 closed bool
31}
32
33// NewConn returns a connection over stream, using framer to mark one message
34// off from the next. Call Run in a goroutine of its own to start reading.
35//
36// onNotify and onRequest may both be nil, in which case notifications are
37// dropped and the peer's requests are answered with "method not found".
38//
39// conn := jsonrpc.NewConn(stream, lsp.Framing{}, client.onNotify, client.onRequest)
40// go conn.Run()
41func NewConn(stream io.ReadWriteCloser, framer Framer, onNotify NotificationFunc, onRequest RequestFunc) *Conn {
42 return &Conn{
43 stream: stream,
44 reader: bufio.NewReader(stream),
45 framer: framer,
46 onNotify: onNotify,
47 onRequest: onRequest,
48 pending: map[int64]chan *message{},
49 }
50}
51
52// Run reads messages until the stream ends or the connection is closed, then
53// fails every call still waiting for an answer.
54//
55// It returns nil for a clean end of stream and the read error otherwise.
56func (c *Conn) Run() error {
57 err := c.readLoop()
58 c.failPending()
59 return err
60}
61
62// readLoop dispatches every message that arrives.
63func (c *Conn) readLoop() error {
64 for {
65 body, err := c.framer.ReadMessage(c.reader)
66 if err != nil {
67 if errors.Is(err, io.EOF) || c.isClosed() {
68 return nil
69 }
70 return err
71 }
72
73 var msg message
74 if err := json.Unmarshal(body, &msg); err != nil {
75 return fmt.Errorf("jsonrpc: unreadable message: %w", err)
76 }
77 c.dispatch(&msg)
78 }
79}
80
81// dispatch sends a message where it belongs: to the call that is waiting for
82// it, to the notification handler, or to the request handler.
83//
84// A notification and a request differ only by the id: the peer expects an
85// answer to exactly the messages that carry one.
86func (c *Conn) dispatch(msg *message) {
87 switch {
88 case msg.Method != "" && len(msg.ID) == 0:
89 if c.onNotify != nil {
90 c.onNotify(msg.Method, msg.Params)
91 }
92 case msg.Method != "":
93 c.answer(msg)
94 default:
95 c.deliver(msg)
96 }
97}
98
99// deliver hands a response to the call that is waiting for it.
100//
101// The id is looked up among the ids *this* connection sent. A peer numbering
102// its own requests from zero — which the Agent Client Protocol's agents do —
103// therefore cannot collide with a call in flight here.
104func (c *Conn) deliver(msg *message) {
105 var id int64
106 if err := json.Unmarshal(msg.ID, &id); err != nil {
107 return // a response to an id we never sent; nothing to do with it
108 }
109
110 c.mu.Lock()
111 waiting, ok := c.pending[id]
112 delete(c.pending, id)
113 c.mu.Unlock()
114
115 if ok {
116 waiting <- msg
117 }
118}
119
120// answer hands a request the peer made to the handler, which replies now or
121// later.
122func (c *Conn) answer(msg *message) {
123 request := &Request{Method: msg.Method, Params: msg.Params, conn: c, id: msg.ID}
124
125 if c.onRequest == nil {
126 request.Reply(nil, &ResponseError{Code: CodeMethodNotFound, Message: msg.Method})
127 return
128 }
129 c.onRequest(request)
130}
131
132// Call sends a request and waits for its answer, decoding it into result.
133//
134// Passing a nil result discards the answer. The call ends early if ctx does,
135// which is what stops a slow request from freezing the caller.
136//
137// var items CompletionList
138// err := conn.Call(ctx, "textDocument/completion", params, &items)
139func (c *Conn) Call(ctx context.Context, method string, params, result any) error {
140 id, waiting, err := c.register()
141 if err != nil {
142 return err
143 }
144 defer c.forget(id)
145
146 if err := c.sendRequest(id, method, params); err != nil {
147 return err
148 }
149
150 select {
151 case <-ctx.Done():
152 return ctx.Err()
153 case reply := <-waiting:
154 return decodeReply(reply, result)
155 }
156}
157
158// Notify sends a notification, which expects no answer.
159//
160// err := conn.Notify("session/cancel", map[string]string{"sessionId": id})
161func (c *Conn) Notify(method string, params any) error {
162 encodedParams, err := encodeParams(params)
163 if err != nil {
164 return err
165 }
166 return c.write(&message{JSONRPC: Version, Method: method, Params: encodedParams})
167}
168
169// sendRequest encodes and writes one request.
170func (c *Conn) sendRequest(id int64, method string, params any) error {
171 encodedID, err := json.Marshal(id)
172 if err != nil {
173 return fmt.Errorf("jsonrpc: encoding the request id: %w", err)
174 }
175 encodedParams, err := encodeParams(params)
176 if err != nil {
177 return err
178 }
179 return c.write(&message{JSONRPC: Version, ID: encodedID, Method: method, Params: encodedParams})
180}
181
182// register allocates a request id and the channel its answer will arrive on.
183func (c *Conn) register() (int64, chan *message, error) {
184 c.mu.Lock()
185 defer c.mu.Unlock()
186
187 if c.closed {
188 return 0, nil, ErrClosed
189 }
190 c.nextID++
191 // The channel is buffered so that the read loop never blocks handing over
192 // an answer whose caller has already given up.
193 waiting := make(chan *message, 1)
194 c.pending[c.nextID] = waiting
195 return c.nextID, waiting, nil
196}
197
198// forget drops a pending request, whether it was answered or abandoned.
199func (c *Conn) forget(id int64) {
200 c.mu.Lock()
201 delete(c.pending, id)
202 c.mu.Unlock()
203}
204
205// write frames and sends one message.
206func (c *Conn) write(msg *message) error {
207 body, err := json.Marshal(msg)
208 if err != nil {
209 return fmt.Errorf("jsonrpc: encoding a message: %w", err)
210 }
211
212 c.writeMu.Lock()
213 defer c.writeMu.Unlock()
214 if c.isClosed() {
215 return ErrClosed
216 }
217 return c.framer.WriteMessage(c.stream, body)
218}
219
220// failPending wakes every waiting call once the connection has ended.
221//
222// Requests the *peer* sent are not answered here. There is no answer to give,
223// and the peer whose connection has just ended is not waiting for one.
224func (c *Conn) failPending() {
225 c.mu.Lock()
226 defer c.mu.Unlock()
227
228 c.closed = true
229 for id, waiting := range c.pending {
230 close(waiting)
231 delete(c.pending, id)
232 }
233}
234
235// isClosed reports whether the connection has been closed.
236func (c *Conn) isClosed() bool {
237 c.mu.Lock()
238 defer c.mu.Unlock()
239 return c.closed
240}
241
242// Close shuts the connection down. Calls still waiting fail with ErrClosed.
243func (c *Conn) Close() error {
244 c.mu.Lock()
245 if c.closed {
246 c.mu.Unlock()
247 return nil
248 }
249 c.closed = true
250 c.mu.Unlock()
251
252 return c.stream.Close()
253}