Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frequent crashing especially when dealing with function contexts #53

Closed
hyperswine opened this issue Feb 6, 2024 · 12 comments
Closed
Labels
bug Something isn't working

Comments

@hyperswine
Copy link

hyperswine commented Feb 6, 2024

Environment

  • Millet version: 0.14.2
  • Editor version: VS Code - Insiders, version 1.87
  • OS version: Darwin 23.3.0 / macOS

Issue and Steps to Reproduce

Setup a basic project structure with millet.toml, sources.mlb with a main.sml.
Start writing code, especially with functions fun ....

Get an error that says "extension crashed <3-5> times in the last 5 minutes, shutting down".

@hyperswine hyperswine added the bug Something isn't working label Feb 6, 2024
@azdavis
Copy link
Owner

azdavis commented Feb 8, 2024

sorry to hear that. can you provide more details? like the exact files you set up? it should also be possible to pull up the vs code output panel and select millet to see details about the crashes. could you share the backtrack?

@hyperswine
Copy link
Author

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "<unknown>", line 0, in core::panicking::panic
  File "<unknown>", line 0, in lang_srv::notification::go
  File "<unknown>", line 0, in lang_srv::run_inner
  File "<unknown>", line 0, in lang_srv::run_stdio
  File "<unknown>", line 0, in millet_ls::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in _main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1758
thread: main
[Error - 22:12:42] Server process exited with code 101.
[Info  - 22:12:42] Connection to server got closed. Server will restart.
true

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "<unknown>", line 0, in core::panicking::panic
  File "<unknown>", line 0, in lang_srv::notification::go
  File "<unknown>", line 0, in lang_srv::run_inner
  File "<unknown>", line 0, in lang_srv::run_stdio
  File "<unknown>", line 0, in millet_ls::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in _main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1758
thread: main
[Error - 22:12:42] Server process exited with code 101.
[Info  - 22:12:42] Connection to server got closed. Server will restart.
true

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "<unknown>", line 0, in core::panicking::panic
  File "<unknown>", line 0, in lang_srv::notification::go
  File "<unknown>", line 0, in lang_srv::run_inner
  File "<unknown>", line 0, in lang_srv::run_stdio
  File "<unknown>", line 0, in millet_ls::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in _main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1758
thread: main

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
[Info  - 22:12:43] Connection to server got closed. Server will restart.
true
[Error - 22:12:43] Server process exited with code 101.

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "<unknown>", line 0, in core::panicking::panic
  File "<unknown>", line 0, in lang_srv::notification::go
  File "<unknown>", line 0, in lang_srv::run_inner
  File "<unknown>", line 0, in lang_srv::run_stdio
  File "<unknown>", line 0, in millet_ls::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in _main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1758
thread: main
[Error - 22:12:43] Server process exited with code 101.
[Info  - 22:12:43] Connection to server got closed. Server will restart.
true

Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "<unknown>", line 0, in core::panicking::panic
  File "<unknown>", line 0, in lang_srv::notification::go
  File "<unknown>", line 0, in lang_srv::run_inner
  File "<unknown>", line 0, in lang_srv::run_stdio
  File "<unknown>", line 0, in millet_ls::main
  File "<unknown>", line 0, in std::sys_common::backtrace::__rust_begin_short_backtrace
  File "<unknown>", line 0, in _main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1758
thread: main
[Error - 22:12:44] Server process exited with code 101.
[Error - 22:12:44] The millet server crashed 5 times in the last 3 minutes. The server will not be restarted. See the output for more information.

@hyperswine
Copy link
Author

I set up a very basic structure with

main.mlb

main.sml

millet.toml

version = 1
workspace.root = "all.mlb"

main.sml

<start typing "fun x" and so on ...>
Screenshot 2024-02-08 at 10 15 19 pm

@azdavis
Copy link
Owner

azdavis commented Feb 11, 2024

ok i think im able to reproduce. it seems easier to reproduce with autosave on.

with the following basic project structure:

.vscode/settings.json

{
  "millet.server.diagnostics.moreInfoHint.enable": false,
  "millet.format.engine": "none",
  "files.autoSave": "afterDelay",
  "files.autoSaveDelay": 100
}

main.mlb

main.sml

millet.toml

version = 1

and on the latest main (d965b01), with the following extra diff applied for debugging:

diff --git a/crates/lang-srv/src/helpers.rs b/crates/lang-srv/src/helpers.rs
index 90f5b4bb..9ed066bf 100644
--- a/crates/lang-srv/src/helpers.rs
+++ b/crates/lang-srv/src/helpers.rs
@@ -41,6 +41,7 @@ pub(crate) fn apply_changes(
   contents: &mut String,
   mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
 ) {
+  eprint!("apply_changes {contents:?} {content_changes:?} -> ");
   // If at least one of the changes is a full document change, use the last of them as the starting
   // point and ignore all previous changes.
   let content_changes = match content_changes.iter().rposition(|change| change.range.is_none()) {
@@ -72,4 +73,5 @@ pub(crate) fn apply_changes(
       contents.replace_range(std::ops::Range::<usize>::from(range), &change.text);
     }
   }
+  eprintln!("{contents:?}");
 }
diff --git a/crates/lang-srv/src/notification.rs b/crates/lang-srv/src/notification.rs
index eee710c7..912b22f4 100644
--- a/crates/lang-srv/src/notification.rs
+++ b/crates/lang-srv/src/notification.rs
@@ -39,6 +39,7 @@ fn try_update_input(
       || change.typ == lsp_types::FileChangeType::CHANGED
     {
       let new_contents = cx.fs.read_to_string(path.as_path())?;
+      eprintln!("new contents {new_contents:?}");
       entry.insert(new_contents);
     } else if change.typ == lsp_types::FileChangeType::DELETED {
       entry.remove();
@@ -53,11 +54,13 @@ fn try_update_input(
 #[allow(clippy::too_many_lines)]
 fn go(st: &mut St, mut n: Notification) -> ControlFlow<Result<()>, Notification> {
   n = helpers::try_notif::<lsp_types::notification::DidChangeWatchedFiles, _>(n, |params| {
+    eprintln!("DidChangeWatchedFiles");
     match &mut st.mode {
       Mode::Root(root) => {
         match try_update_input(&mut st.cx, &mut root.input, params.changes) {
           Ok(_) => {
             // TODO use path ids
+            eprintln!("try_update_input ok sources {:?}", root.input.sources);
           }
           Err(_) => root.input = st.cx.get_input(&root.path),
         }
@@ -68,6 +71,7 @@ fn go(st: &mut St, mut n: Notification) -> ControlFlow<Result<()>, Notification>
     Ok(())
   })?;
   n = helpers::try_notif::<lsp_types::notification::DidChangeTextDocument, _>(n, |params| {
+    eprintln!("DidChangeTextDocument");
     let url = params.text_document.uri;
     let path = convert::url_to_path_id(&st.cx.fs, &mut st.cx.paths, &url)?;
     if let Mode::Root(root) = &mut st.mode {

if i pull up main.sml and basically type random nonsense into it, i get this after a few seconds:

DidChangeTextDocument
apply_changes "asehf asef ase\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 14 }, end: Position { line: 0, character: 14 } }), range_length: Some(0), text: "f" }] -> "asehf asef asef\n"
DidChangeTextDocument
apply_changes "asehf asef asef\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 15 }, end: Position { line: 0, character: 15 } }), range_length: Some(0), text: "a" }] -> "asehf asef asefa\n"
DidChangeTextDocument
apply_changes "asehf asef asefa\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 16 }, end: Position { line: 0, character: 16 } }), range_length: Some(0), text: "l" }] -> "asehf asef asefal\n"
DidChangeTextDocument
apply_changes "asehf asef asefal\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 17 }, end: Position { line: 0, character: 17 } }), range_length: Some(0), text: "k" }] -> "asehf asef asefalk\n"
DidChangeTextDocument
apply_changes "asehf asef asefalk\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 18 }, end: Position { line: 0, character: 18 } }), range_length: Some(0), text: "s" }] -> "asehf asef asefalks\n"
DidChangeWatchedFiles
new contents "asehf asef\n"
try_update_input ok sources {PathId(Idx(1)): "asehf asef\n"}
DidChangeTextDocument
apply_changes "asehf asef\n" [TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 0, character: 19 }, end: Position { line: 0, character: 19 } }), range_length: Some(0), text: "e" }] -> 
Run with RUST_BACKTRACE=full to include source snippets.
Backtrace (most recent call first):
  File "rust:library/core/src/panicking.rs", line 144, in core::panicking::panic
  File "rust:library/alloc/src/string.rs", line 1908, in alloc::string::String::replace_range
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/helpers.rs", line 73, in lang_srv::helpers::apply_changes
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/notification.rs", line 81, in lang_srv::notification::go::{{closure}}
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/helpers.rs", line 25, in lang_srv::helpers::try_notif
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/notification.rs", line 73, in lang_srv::notification::go
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/notification.rs", line 14, in lang_srv::notification::handle
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/lib.rs", line 32, in lang_srv::run_inner
  File "/Users/ariel.davis/Documents/millet/crates/lang-srv/src/lib.rs", line 46, in lang_srv::run_stdio
  File "/Users/ariel.davis/Documents/millet/crates/millet-ls/src/main.rs", line 7, in millet_ls::main

Millet (0.14.2) crashed. We would appreciate a bug report: https://github.com/azdavis/millet/issues
  assertion failed: self.is_char_boundary(n)
in rust:library/alloc/src/string.rs, line 1908
thread: main

it seems like what happens is:

  • we get a bunch of DidChangeTextDocument notifs which we respond to correctly
  • we get a DidChangeWatchedFiles notif which we respond to by getting the current file contents from the filesystem
  • the current contents on the filesystem are not up-to-date somehow??
  • we get another DidChangeTextDocument based on the more up-to-date file contents
  • trying to apply those changes to the out-of-date file contents from the fs fails

i'm not sure how to fix this. unfortunately String::replace_range does not have a non-panicking equivalent, so the only way i can catch the error is to catch_unwind. and even once i do, it seems like the whole problem is that the contents of the file on disk are out of date.

@azdavis
Copy link
Owner

azdavis commented Feb 13, 2024

i released v0.14.3 to try to address the issue by just catching the unwind and basically doing nothing afterwards. not the greatest solution to be sure but it might help

@azdavis
Copy link
Owner

azdavis commented Feb 13, 2024

can you let me know if it helps? it seems to help for me in my testing. in that, it does still panic but doesn't crash the language server anymore

@hyperswine
Copy link
Author

I tested it out on mine and it looks good, nice work on identifying the specifics and a (temporary I suppose?) fix!

@azdavis
Copy link
Owner

azdavis commented Feb 15, 2024

yea i'd like for the 'fix' to be temporary. if i get a chance i'd like to see if i can get the opinion of more experienced language server developers, since (i think?) the issue is not with millet (or at least not only).

i do know in general e.g. rust-analyzer does depend on being able to catch_unwind (so it is not compiled with panic=abort); one of the reasons they do this is to implement request cancellation (which millet does not implement), but an ancillary benefit may be that this case is already handled 'for free'.

@azdavis
Copy link
Owner

azdavis commented Feb 16, 2024

started a discussion to see if they have any insight rust-lang/rust-analyzer#16578

@azdavis
Copy link
Owner

azdavis commented Apr 3, 2024

important excerpt from that discussion:

but i don't register for DidOpenTextDocument notifications. maybe i should, and when a file is opened mark it as ignored for the purposes of responding to DidChangeWatchedFiles?

Yes you should, you can't reasonably handle DidChangeTextDocument notifications without keeping track which files the client has opened. The spec even requires the client to only send DidChangeTextDocument notifications for documents that are considedered opened at the time by DidOpenTextDocument

this seems like the better solution (as opposed to the current hack of catching the unwind).

@azdavis
Copy link
Owner

azdavis commented Apr 26, 2024

more correctly fixed in 99426ff by applying the advice to ignore open files when handling DidChangeWatchedFiles. though i guess i can leave the catch_unwind there in case for now at least

@azdavis
Copy link
Owner

azdavis commented Apr 26, 2024

the better fix is released in v0.14.4

@azdavis azdavis closed this as completed Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants