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

Reloading file in LS after desynchronization. #6752

Merged
merged 9 commits into from
May 23, 2023

Conversation

farmaazon
Copy link
Contributor

@farmaazon farmaazon commented May 18, 2023

Pull Request Description

Fixes #5203

This PR changes behavior when text/applyChange returned error.

Before we always assumed that the change was not applied, and tried to send full synchronization still assuming old file content. But this was not the case on some errors (timeouts for example). Now we instead reopen the file (getting its actual content) and then make a full invalidation.

Also added a shortcut allowing manual file reloading, what may be useful in some kinds of error (and also allowed me testing of reopening file in the application).

Important Notes

The unit tests of sending text updates were improved: now we actually check if all expected messages are emitted from the IDE.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed, the GUI was tested when built using ./run ide build.

@farmaazon farmaazon self-assigned this May 18, 2023
@farmaazon farmaazon marked this pull request as draft May 18, 2023 09:44
@farmaazon
Copy link
Contributor Author

Turned to draft after realizing some FIXMEs were left in code.

@farmaazon farmaazon marked this pull request as ready for review May 18, 2023 10:17
Copy link
Contributor

@vitvakatu vitvakatu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor nit-picks.

app/gui/src/model/module/synchronized.rs Outdated Show resolved Hide resolved
Comment on lines 126 to 130
impl Default for LanguageServerContent {
fn default() -> Self {
Self::Unknown
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's possible to derive Default and mark the needed variant with the #[default] attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. For some reason, I thought it's possible only when all variants have no fields.

Comment on lines 395 to 396
/// Send to LanguageServer update about received notification about module. Returns the new
/// content summery of Language Server state.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second sentence is no longer accurate.

app/gui/src/model/module/synchronized.rs Show resolved Hide resolved
new_file: &SourceFile,
edits: Vec<TextEdit>,
execute: bool,
) -> impl Future<Output = FallibleResult<ParsedContentSummary>> + 'static {
) -> impl Future<Output = ()> + 'static {
println!("---\n{}---\n", new_file.content);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug left over?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all printlns.

Comment on lines 650 to 661
debug!("Actual digest: {actual_old} => {actual_new}");
debug!("Declared digest: {} => {}", edits.old_version, edits.new_version);
debug!("New content:\n===\n{new_content}\n===");
println!("Actual digest: {actual_old} => {actual_new}");
println!("Declared digest: {} => {}", edits.old_version, edits.new_version);
println!("New content:\n===\n{new_content}\n===");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I believe debug! (and other) messages are still visible in regular tests, so this change should be unnecessary.

farmaazon and others added 2 commits May 19, 2023 11:22
@@ -77,6 +77,8 @@ pub struct FileToUpload<DataProvider> {
#[derive(Clone, Debug)]
pub struct FileUploadProcess<DataProvider> {
bin_connection: Rc<binary::Connection>,
// See FIXME in upload_chunk method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any FIXME in upload_chunk method. Is this still necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a change committed to a wrong branch.

#6689 also contains the change, once it will be merged, here it should disappear from diff.

@Frizi
Copy link
Contributor

Frizi commented May 19, 2023

QA report 🥦

No issues spotted. I tried opening large projects and doing many edits in quick succession, enabling CPU throttling and even toggling offline mode. The IDE was able to recover from all of those.

Copy link
Contributor

@hubertp hubertp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on my pet project. I can no longer reproduce Invalid Version errors, so I think it's a step forward. Ideally, it would be cool to somehow notify the user about the reload because I guess they can be surprised about lost of last edit?

Comment on lines +310 to 314
fn reopen_file_in_language_server(&self) -> BoxFuture<FallibleResult> {
info!("Ignoring request for reopening file in the Language Server, because it's not connected");
future::ready_boxed(Ok(()))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100 chars

Comment on lines +117 to +124
#[derive(Clone, Debug, Default)]
enum LanguageServerContent {
/// The content is synchronized with our module state after last fully handled notification.
Synchronized(ParsedContentSummary),
/// The content is not synchronized with our module state after last fully handled
/// notification, probably due to connection error when sending update.
Desynchronized(ContentSummary),
}

impl LanguageServerContent {
fn summary(&self) -> &ContentSummary {
match self {
LanguageServerContent::Synchronized(content) => &content.summary,
LanguageServerContent::Desynchronized(content) => content,
}
}
/// The content is not known due to an unrecognized error received from the Language Server
/// after applying the last update. We don't know if, and to what extent it was applied.
#[default]
Unknown,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it better than Option<ParsedContentSummary>?

self.language_server.client.close_text_file(file_path).await?;
info!("Reopening file {file_path}.");
if let Err(error) = self.language_server.client.close_text_file(file_path).await {
error!("Error while reopening file {file_path}: Closing operation failed: {error} Trying to open the file anyway.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100 chars

Comment on lines +368 to +369
/// Listen for all module changes and sends proper updates to the Language Server.
///
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sends -> send

) -> impl Future<Output = ()> {
/// Listen for all module changes and sends proper updates to the Language Server.
///
/// This function returns once the notifications from underlying [module model](plain::Module)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double space after the

new_file: SourceFile,
) -> impl Future<Output = FallibleResult<ParsedContentSummary>> + 'static {
) -> impl Future<Output = ()> + 'static {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Output not default to () ? If not, this is an IMO surprising design of this lib.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Associated type defaults are not in stable Rust yet and unstable feature is incomplete, especially around trait objects.
rust-lang/rust#29661 ...since 2015!

Comment on lines +545 to +549
debug!("Updating the LS content digest to: {:?}", summary);
ls_content.replace(LanguageServerContent::Synchronized(summary));
}
Err(err) => {
error!("Error during sending text change to Language Server: {err}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor inconsistency - some errors use LS, some use Language Server. I think that abbreviations are always worse than full names, so I'd be thankful for moving them to full names instead.

@mergify mergify bot merged commit a27f19f into develop May 23, 2023
@mergify mergify bot deleted the wip/ao/reloading_after_desynchronization branch May 23, 2023 08:19
Procrat added a commit that referenced this pull request May 23, 2023
* develop:
  Use state sent with `GET /directories` instead of querying state separately (#6794)
  Run TypeScript typechecking and eslint on Lint CI (#6603)
  Disable cloud backend for unverified users; use local backend as default backend; minor bugfixes (#6779)
  Reloading file in LS after desynchronization. (#6752)
  Missing conversion of hash key in EqualsNode (#6803)
  feat: set constructor args tag values (#6801)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reopen file in engine after synchronization failure
6 participants