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

Reworked Bloop connection and Tree View #1677

Merged
merged 1 commit into from
May 4, 2020

Conversation

tgodzik
Copy link
Contributor

@tgodzik tgodzik commented Apr 29, 2020

Previously, we would only reconnect if the user asked for it and often this would not show up quickly.
Now, we make the reconnection work better by:

  • reconnecting as soon as the build server is disconnected (fixes Missing bloop disconnection alert when exiting bloop #1354)
  • making reconnection automatic unless the user specifically asks for it not to be via system property metals.ask-to-reconnect=true
  • moving compilation outside the initial build server connection - longer running compilations would then produce invalid exceptions and break the next connection
  • reindexing and compiling the workspace on reconnection (this is needed since workspaces would not have all the classpath updated)

Also in this PR:

  • a new clean-compile command was introduced, which together with the restart bloop command was added to tree view
  • tree view now has 4 parts - one for help, one for project view, one for commands and one for running compilations

The reworked tree view would look like this:

image

I am also thinking of:

  • adding icons to compiling projects, I've seen it break in some cases
  • partly reindexing the workspace - not sure if that is sensible since Bloop has a different directory for each client and when reconnecting we are effectively a different client

@tgodzik
Copy link
Contributor Author

tgodzik commented Apr 29, 2020

@nilskp I changed the TreeView to make it more explicit that the commands are clickable and add two more useful commands. Will this be helpful?

Copy link
Contributor Author

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Left some notes on what I changed and why. Overall, I think we need the improvements here to help with the issues we are having in Bloop. In the meantime, I am also trying to help with those issues in Bloop.

@ckipp01 Could you maybe take a look at this from the coc-metals perspective? Not sure if a lot of changes will be need or whether I should do something differently.

@@ -96,13 +98,15 @@ final class BloopServers(
serverStarted
)

val finished = Promise[Unit]()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This promise is used to determine whether the launcher is finished.

Copy link
Contributor

Choose a reason for hiding this comment

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

then maybe it could be called: launched instead of finished?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

finished is updated when the launcher finishes and not when it launches, so I think this is better.

@@ -70,14 +72,21 @@ final class BspServers(
s"${details.getName} input stream"
)

val finished = Promise[Unit]()
Future {
process.waitFor()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not 100% sure about this solution, need to test it with another BSP server.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a test for it in Bill Suite, seems to work pretty well.

)(implicit ec: ExecutionContextExecutorService)
extends Cancelable {

@volatile private var connection = Future.successful(initialConnection)
connection.foreach { conn => conn.onConnectionFinished(reconnect) }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This uses the promise from the launcher to reconnect when the connection is finished.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand the logic here, what is the difference between this and the logic below?

Suggested change
connection.foreach { conn => conn.onConnectionFinished(reconnect) }
initialConnection.onConnectionFinished(reconnect)

Copy link
Contributor Author

@tgodzik tgodzik May 1, 2020

Choose a reason for hiding this comment

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

onConnectionFinished is coming from the process or the launcher to say that the connection is finished. onReconnection is used to later reindex the workspace.

The alternative is to do it in the MetalsLanguageServer and just do a full reconnect, but then it will be less seamless for the user.

LauncherConnection stoped -> BuildServerConnection.reconnect -> MetalsLanguageServer.indexWorkspace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a bit better than a full reconnect, because we will not lose any requests to the build server.

def onReconnection(
index: BuildServerConnection => Future[Unit]
): Unit = {
onReconnection.set(index)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to reindex the workspace and this is to make sure that we will only try to reindex once we finished the indexing at least once.

case response if response == Messages.DisconnectedServer.notNow =>
notification.dismiss(5, TimeUnit.MINUTES)
connection
if (config.askToReconnect) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Depends now on the user configuration.

conn.onConnectionFinished(reconnect)
conn
}
connection.foreach(_ => onReconnection.get()(this))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we actually specify that we want to reindex.

@@ -66,6 +66,26 @@ final class Compilations(
cascadeBatch.cancelCurrentRequest()
}

def recompile(): Future[Unit] = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used to fully recompile a workspace.

Copy link
Contributor

Choose a reason for hiding this comment

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

if the fully part is more important, then maybe it should be called compileAll ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to recompileAll

)
scribe.error(message, e)
BuildChange.Failed
.flatMap(compileAllOpen)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved compiling after recover, since this caused to disconnect the build server if anything happened during the compilation.

val result = connectToNewBuildServer(build)
build.onReconnection { reconnected =>
connectToNewBuildServer(reconnected)
.flatMap(compileAllOpen)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We also compile, which will effectively just move all the old artifacts to the new BSP directory.

TreeViewNode(
viewId = "commands",
nodeUri = s"metals://command/${command.id}",
label = command.title,
command = MetalsCommand(
command.title,
command.id,
"metals." + command.id,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Somehow this didn't work with the new command, since the ones declared all have the metals. prefix

This might not be needed, but I haven't been able to make the new command work otherwise.

I think we could also just use an echo command for all server commands here.

@tgodzik tgodzik force-pushed the rearrange-tree-view branch 2 times, most recently from 2c870e8 to aabc8e3 Compare April 29, 2020 18:17
@ckipp01
Copy link
Member

ckipp01 commented Apr 29, 2020

@ckipp01 Could you maybe take a look at this from the coc-metals perspective? Not sure if a lot of changes will be need or whether I should do something differently.

First try shows the MetalsBuild section just fine, but the MetalsCompile and is empty and MetalsProject doesn't show up :/. Not sure if it's just a small thing or a larger thing that needs to get fixed on the coc-metals side without diving further in.

@tgodzik
Copy link
Contributor Author

tgodzik commented Apr 29, 2020

Seems to be an issue with shutting down currently. The connection might get automatically reestablised 😅 Will fix it tomorrow.

@tgodzik
Copy link
Contributor Author

tgodzik commented Apr 29, 2020

@ckipp01 Could you maybe take a look at this from the coc-metals perspective? Not sure if a lot of changes will be need or whether I should do something differently.

First try shows the MetalsBuild section just fine, but the MetalsCompile and is empty and MetalsProject doesn't show up :/. Not sure if it's just a small thing or a larger thing that needs to get fixed on the coc-metals side without diving further in.

Project just changed the name, so maybe we can adjust the names of views too? MetalsCompile should be empty unless we are compiling anything.

@tgodzik tgodzik force-pushed the rearrange-tree-view branch 3 times, most recently from caa9aac to e2c4358 Compare April 30, 2020 08:41
Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

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

I'm really excited to see this PR! I believe it will be a big improvements in troubleshooting errors when Metals is behaving weirdly. A few notes on the names, I haven't reviewed the code.

Should "Recompile workspace" be called "Clean compile workspace" instead?

Should we use the sync icon 🔄 for "Import build" for consistency with IntelliJ "Refresh project"? We can use something like a stop icon ⏹️ for "Restart build server" instead.

Can we move "Restart build server" to the bottom of the command list in the tree view?

(brainstorming, maybe for a separate PR) I wonder if we should have some form of "Clean import build" which removes .bloop/ before importing the build 🤔 I worry however that we end up having too many troubleshooting commands and users blindly run them all.

@tgodzik
Copy link
Contributor Author

tgodzik commented May 1, 2020

Should "Recompile workspace" be called "Clean compile workspace" instead?

I was thinking about it, but recompile seemed to be clear that we are redoing the whole compilation. I can change don't feel strongly about it

Should we use the sync icon for "Import build" for consistency with IntelliJ "Refresh project"? We can use something like a stop icon for "Restart build server" instead.

Good idea! I wasn't really sure about those two.

Can we move "Restart build server" to the bottom of the command list in the tree view?

Sure!

(brainstorming, maybe for a separate PR) I wonder if we should have some form of "Clean import build" which removes .bloop/ before importing the build I worry however that we end up having too many troubleshooting commands and users blindly run them all.

Honestly, I have never needed to do that myself. Either clean compile or restart server should help with most situations. There is no need to remove the .json files in .bloop ever, so the only thing we need to do is remove the compile artifacts, which the clean compile should do.

@tgodzik
Copy link
Contributor Author

tgodzik commented May 1, 2020

@olafurpg I updated the icons for import and restart server. I used debug-stop for restart, since stop gives us the same icon as for cancel. I also updated the VSCode PR so that if we don't find an icon we try to use the default ones from VS Code.

The image in the description got updated too.

Btw. you can actually easily look for icons in https://microsoft.github.io/vscode-codicons/dist/codicon.html

And they are quite easy to modify in https://www.figma.com/, so it shouldn't take me too long to update anything if anyone has better ideas.

@@ -75,6 +76,12 @@ class MetalsTreeViewProvider(
languageClient.metalsTreeViewDidChange(
TreeViewDidChangeParams(
Array(
TreeViewNode(
Copy link
Member

@ckipp01 ckipp01 May 3, 2020

Choose a reason for hiding this comment

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

Sort of nitpicky, but I'm trying to debug what is going on with coc-metals with the branch, and one things I noticed is that this is technically against the spec that we have published. In the TreeViewNode class, viewId and lable aren't marked as @Nullable, and in the spec it's the same thing. I'm getting an undefined that I'm trying to chase down, and I'm sure it's coming from here, but this is the sort of thing that could cause it and confuse someone since there should be somewhat of a guarantee that there is a label here. The same for the other two TreeViewNode's below this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added TreeViewNode.empty that sets both the id and label.

@ckipp01 ckipp01 added the improvement Not a bug or a feature, but something general we can improve label May 4, 2020
TreeViewNode(
Compile,
"metals://ongoing-compilations",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ckipp01 Maybe this part is a problem with the undefined ? I hoped the node is discovered by id, but maybe we check something else in vim? This is the only big change, the rest is just moving things around

Copy link
Member

@ckipp01 ckipp01 May 4, 2020

Choose a reason for hiding this comment

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

No, I found the issue. The TVP panel in coc-metals was sort of built with the assumption that apart from the initial view it was never empty. Now that things have been shifted around the the compilations view will return an empty array just isn't accounted for. So I need to adjust the logic for this since I have logic built-in to find the offset of node to update, which is wrong now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, cool. Thanks for taking a look at it!

@@ -96,13 +98,15 @@ final class BloopServers(
serverStarted
)

val finished = Promise[Unit]()
Copy link
Contributor

Choose a reason for hiding this comment

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

then maybe it could be called: launched instead of finished?

/** Run build/shutdown procedure */
def shutdown(): Future[Unit] = connection.map { conn =>
try {
isShuttingDown.set(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

by using compareAndSet(false, true you could guarantee here that the method will be called exactly once, hence making it idempotent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! Fixed it!

@@ -66,6 +66,26 @@ final class Compilations(
cascadeBatch.cancelCurrentRequest()
}

def recompile(): Future[Unit] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

if the fully part is more important, then maybe it should be called compileAll ?

}
_ = {
treeView.init()
def compileAllOpen: BuildChange => Future[BuildChange] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

could be compileOpenFiles at first I was not 100% if it meant open files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to compileAllOpenFiles

…estart-build commands to it.

Also, make reconnection work better by:
- reconnecting as soon as the build server is disconnected
- making reconnection automatic unless the user specifically asks for it not to be
- moving compilation outside the initial build server connection - longer running compilations would then produce invalid exceptions
@tgodzik tgodzik merged commit a39ac72 into scalameta:master May 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improvement Not a bug or a feature, but something general we can improve
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Missing bloop disconnection alert when exiting bloop
4 participants