Skip to content

Commit f67a45e

Browse files
committed
rune: Make languageserver reading re-entrant
1 parent 62f0a51 commit f67a45e

File tree

5 files changed

+86
-55
lines changed

5 files changed

+86
-55
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
/flamegraph.svg
1010
/perf.data
1111
/perf.data.*
12+
/*.log

crates/rune/src/languageserver/connection.rs

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
use core::fmt;
2+
use core::str;
23

3-
use anyhow::{anyhow, bail, Result};
4+
use anyhow::{anyhow, bail, Context as _, Result};
45
use tokio::io::{AsyncBufRead, AsyncBufReadExt as _, AsyncRead, AsyncReadExt as _, BufReader};
56

67
use crate::alloc::prelude::*;
78
use crate::languageserver::envelope;
89

9-
/// An input frame.
10-
#[derive(Debug)]
11-
pub(super) struct Frame<'a> {
12-
pub(super) content: &'a [u8],
10+
enum State {
11+
/// Initial state, before header has been received.
12+
Initial,
13+
/// Reading state, when a header has been received.
14+
Reading(usize),
1315
}
1416

1517
/// Input connection.
1618
pub(super) struct Input<I> {
17-
buf: rust_alloc::vec::Vec<u8>,
1819
reader: BufReader<I>,
20+
state: State,
1921
}
2022

2123
impl<I> Input<I>
@@ -25,28 +27,46 @@ where
2527
/// Create a new input connection.
2628
pub(super) fn new(reader: I) -> Self {
2729
Self {
28-
buf: rust_alloc::vec::Vec::new(),
2930
reader: BufReader::new(reader),
31+
state: State::Initial,
3032
}
3133
}
3234

3335
/// Get the next input frame.
34-
pub(super) async fn next(&mut self) -> Result<Option<Frame<'_>>> {
35-
let headers = match Headers::read(&mut self.buf, &mut self.reader).await? {
36-
Some(headers) => headers,
37-
None => return Ok(None),
38-
};
36+
pub(super) async fn next(&mut self, buf: &mut rust_alloc::vec::Vec<u8>) -> Result<bool> {
37+
loop {
38+
match self.state {
39+
State::Initial => {
40+
let Some(headers) = Headers::read(buf, &mut self.reader).await? else {
41+
return Ok(false);
42+
};
3943

40-
tracing::trace!("headers: {:?}", headers);
44+
tracing::trace!(?headers, "Received headers");
4145

42-
let length = match headers.content_length {
43-
Some(length) => length as usize,
44-
None => bail!("missing content-length"),
45-
};
46+
let len = match headers.content_length {
47+
Some(length) => length as usize,
48+
None => bail!("Missing content-length in header"),
49+
};
50+
51+
buf.resize(len, 0u8);
52+
self.state = State::Reading(0);
53+
}
54+
State::Reading(ref mut at) => {
55+
let n = self.reader.read(&mut buf[*at..]).await?;
4656

47-
self.buf.resize(length, 0u8);
48-
self.reader.read_exact(&mut self.buf[..]).await?;
49-
Ok(Some(Frame { content: &self.buf }))
57+
*at += n;
58+
59+
if *at == buf.len() {
60+
self.state = State::Initial;
61+
return Ok(true);
62+
}
63+
64+
if n == 0 {
65+
return Ok(false);
66+
}
67+
}
68+
}
69+
}
5070
}
5171
}
5272

@@ -195,12 +215,12 @@ impl Outbound {
195215
}
196216
}
197217

198-
#[derive(Debug)]
218+
#[derive(Debug, Clone, Copy)]
199219
pub(super) enum ContentType {
200220
JsonRPC,
201221
}
202222

203-
#[derive(Default, Debug)]
223+
#[derive(Default, Debug, Clone, Copy)]
204224
pub(super) struct Headers {
205225
pub(super) content_length: Option<u32>,
206226
pub(super) content_type: Option<ContentType>,
@@ -216,10 +236,9 @@ impl Headers {
216236
S: ?Sized + Unpin + AsyncBufRead,
217237
{
218238
let mut headers = Headers::default();
239+
let mut any = false;
219240

220241
loop {
221-
buf.clear();
222-
223242
let len = match reader.read_until(b'\n', buf).await {
224243
Ok(len) => len,
225244
Err(error) => return Err(error.into()),
@@ -229,11 +248,11 @@ impl Headers {
229248
return Ok(None);
230249
}
231250

232-
debug_assert!(len == buf.len());
233-
let buf = &buf[..len];
251+
debug_assert_eq!(len, buf.len());
234252

235-
let buf = std::str::from_utf8(buf)?;
236-
let line = buf.trim();
253+
let line = buf.get(..len).unwrap_or_default();
254+
let line = str::from_utf8(line).context("decoding line")?;
255+
let line = line.trim();
237256

238257
if line.is_empty() {
239258
break;
@@ -245,28 +264,40 @@ impl Headers {
245264

246265
let key = key.trim();
247266
let value = value.trim();
248-
let key = key.to_lowercase();
249267

250-
match key.as_str() {
251-
"content-type" => match value {
252-
"application/vscode-jsonrpc; charset=utf-8" => {
253-
headers.content_type = Some(ContentType::JsonRPC);
268+
'done: {
269+
if key.eq_ignore_ascii_case("content-type") {
270+
match value {
271+
"application/vscode-jsonrpc; charset=utf-8" => {
272+
headers.content_type = Some(ContentType::JsonRPC);
273+
}
274+
value => {
275+
return Err(anyhow!("Unsupported content-type `{value}`"));
276+
}
254277
}
255-
value => {
256-
return Err(anyhow!("bad value: {:?}", value));
257-
}
258-
},
259-
"content-length" => {
278+
279+
any = true;
280+
break 'done;
281+
}
282+
283+
if key.eq_ignore_ascii_case("content-length") {
260284
let value = value
261285
.parse::<u32>()
262286
.map_err(|e| anyhow!("bad content-length: {}: {}", value, e))?;
263287

264288
headers.content_length = Some(value);
265-
}
266-
key => {
267-
return Err(anyhow!("header not supported: {:?}", key));
268-
}
289+
any = true;
290+
break 'done;
291+
};
292+
293+
bail!("Unsupported header `{key}`");
269294
}
295+
296+
buf.clear();
297+
}
298+
299+
if !any {
300+
return Ok(None);
270301
}
271302

272303
Ok(Some(headers))

crates/rune/src/languageserver/envelope.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ pub(super) enum RequestId {
1515
}
1616

1717
#[derive(Debug, TryClone, Deserialize)]
18-
pub(super) struct IncomingMessage {
18+
pub(super) struct IncomingMessage<'a> {
1919
#[allow(unused)]
2020
pub(super) jsonrpc: V2,
2121
pub(super) id: Option<RequestId>,
22-
pub(super) method: String,
22+
pub(super) method: &'a str,
2323
#[serde(default)]
2424
#[try_clone(with = Clone::clone)]
2525
pub(super) params: serde_json::Value,

crates/rune/src/languageserver/mod.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,12 @@ where
195195
tracing::info!("Starting server");
196196
state.rebuild()?;
197197

198+
let mut content = rust_alloc::vec::Vec::new();
199+
198200
while !state.is_stopped() {
199201
tokio::select! {
200202
_ = rebuild.as_mut() => {
201-
tracing::info!("rebuilding project");
203+
tracing::info!("Rebuilding project");
202204
state.rebuild()?;
203205
rebuild.set(rebuild_notify.notified());
204206
},
@@ -210,13 +212,12 @@ where
210212
self.output.flush().await.context("flushing output")?;
211213
}
212214
},
213-
frame = input.next() => {
214-
let frame = match frame? {
215-
Some(frame) => frame,
216-
None => break,
215+
frame = input.next(&mut content) => {
216+
if !frame? {
217+
break;
217218
};
218219

219-
let incoming: envelope::IncomingMessage = serde_json::from_slice(frame.content)?;
220+
let incoming: envelope::IncomingMessage<'_> = serde_json::from_slice(&content)?;
220221
tracing::trace!(?incoming);
221222

222223
// If server is not initialized, reject incoming requests.
@@ -234,7 +235,7 @@ where
234235

235236
macro_rules! handle {
236237
($(req($req_ty:ty, $req_handle:ident)),* $(, notif($notif_ty:ty, $notif_handle:ident))* $(,)?) => {
237-
match incoming.method.as_str() {
238+
match incoming.method {
238239
$(<$req_ty>::METHOD => {
239240
let params = <$req_ty as Request>::Params::deserialize(incoming.params)?;
240241
let result = $req_handle(&mut state, params)?;
@@ -268,6 +269,8 @@ where
268269
notif(lsp::notification::DidSaveTextDocument, did_save_text_document),
269270
notif(lsp::notification::Initialized, initialized),
270271
}
272+
273+
content.clear();
271274
},
272275
}
273276
}

crates/rune/src/languageserver/state.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,6 @@ impl<'a> State<'a> {
560560
}
561561

562562
for (url, diagnostics) in reporter.by_url {
563-
if diagnostics.is_empty() {
564-
continue;
565-
}
566-
567563
tracing::info!(
568564
url = ?url.try_to_string()?,
569565
diagnostics = diagnostics.len(),

0 commit comments

Comments
 (0)