Skip to content
This repository was archived by the owner on Sep 21, 2019. It is now read-only.

Conversation

ches
Copy link
Contributor

@ches ches commented Jan 6, 2017

This resolves #325, that ticket serves as a good statement of the purpose of this PR.

I still have a few known todos here and I'm working on writing some tests, but this is such a large and core change (and frankly one that I'm really eager to get out of the way) that I wanted to open it up and get some people trying it. It basically works for me now but I'd like to know if anything turns out to be fragile.

Caveats at present to highlight and spare you from trying:

  • :EnClients command (client_status Python function) is broken by the these changes. Should be easily fixable, I just didn't bother with it yet. As discussed below I'd like to change it to a :EnStatus command with more features in the future—will create a follow-up issue for this.

  • It won't start a bootstrapped server installation. Please use current sbt-ensime that installs the server.

    This stems from the Server abstraction. I don't want it to know about vim (because how does that make any sense?), but EnsimeLauncher currently needs vim as a shell runner for bootstrap installations. As discussed inline we might just drop the server bootstrap functionality now on this branch, it's now been 8+ months since sbt-ensime handled server installation for you, if other build tools don't do it yet they should probably get feature requests filed.

if not server.isrunning():
self.start_server(config)
client.connect(server)
client.connection_attempts += 1
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'm just preserving how the prior implementation behaved so far, but while we're looking at this, it seems excessive to me to check this on every CursorMoveCursorHold is probably fine, but CursorMove a bit much. The server and our connection are not so unstable. Might experiment with removing the call to this from au_cursor_moved.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think experimentation won't hurt and if it turns out to be unnecessary I am more than welcome to remove it. However, there is a tradeoff here between handlers and responsiveness. If me remove CursorHold, ensime-vim could turn less responsive and responses may be processed with delays. I hope this is not the case though, I would very much prefer to use only one -- but let's keep in mind that if we're browsing through a file and waiting for some reaction of the plugin, this won't happen until the cursor is on hold for a few secs.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think ches meant remove just the reconnection logic from CursorHold, if unqueue_and_display is left there we won't lose any responsiveness.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes indeed, the queue processing remains on CursorMoved, but checking that server is up and connected is now removed and it seems fine to me.

Copy link
Contributor

@ktonga ktonga left a comment

Choose a reason for hiding this comment

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

Nothing to say. Amazing work.

@@ -300,3 +362,13 @@ def fun_en_complete_func(self, client, findstart_and_base, base=None):
# Invoked by vim
findstart = findstart_and_base
return client.complete_func(findstart, base)

def watchdog_client_server_connection(self, client):
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't working (for me), but don't worry, I will take a look as part of #350

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, what about it isn't working?

Copy link
Contributor

Choose a reason for hiding this comment

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

Dunno if it should support this case, but I tried killing the server processes and the client goes to a bad state just logging websocket not connected continuously. I have to restart vim session.
But this is related to my PR where I have to make some changes so recover from a disconnection is possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah okay yeah, I hadn't gone that far but now I have while trying to determine if something else was a bug… the latest commit probably gets you even closer (it does some of the same things you started doing on #350 in fact) but I think that EnsimeProcess has some wrong behavior that needs to be fixed in order for Server to work right when it has a stale reference to a process. I might leave that for you to pick up in the interest of keeping the scope in check here.

self.suggestions = None
self.completion_timeout = 10 # seconds
self.completion_started = False

self.full_types_enabled = False
"""Whether fully-qualified types are displayed by inspections or not"""

self.toggle_teardown = True
self.connection_attempts = 0
self.tmp_diff_folder = tempfile.mkdtemp(prefix='ensime-vim-diffs')

# By default, don't connect to server more than once
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this comment still make sense here after removing the field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No it doesn't, thanks for pointing that out—I still want to make this stuff clearer. connection_retries was a rename from number_try_connection because it was clearer to me for how it was used, but it's still confusing that the connect method and queue_poll are based on counting down connection_retries, then what is now watchdog_client_server_connection counts up connection_attempts. I'm guessing that at one time the former was meant to be like a config value for max retries, and the count-up should use that instead of a hardcoded literal, but it perhaps got mixed up over time.

I was pretty much translating the existing code on these up until now, but I'll consolidate them.

Copy link
Contributor

@jvican jvican left a comment

Choose a reason for hiding this comment

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

I really like these changes. Big up @ches. Happy to merge at any time. I wanted to chat with you guys to speed up the merge process -- to me any small improvement to the codebase can be merged right away, we don't really need PRs implementing complete features in order to click the merge button. So long as every commit works and passes the tests, it's good to go 😄.

README.md Outdated
@@ -1,7 +1,6 @@
# Ensime for vim `ensime-vim`
# `ensime-vim` – ENSIME with Vim!
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest something like "ensime-vim -- Scala support in Vim!". I sometimes feel that people don't really get what ensime is about and don't realize what this plugin is capable of. Making it explicit (though redundant) may help attract users as well as contributors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree that makes sense, improve the SEO juju 😄 I'll change this.

bootstrap_server=False,
create_client=True):
"""Decorator that gets a client and performs an operation on it."""
def execute_with_client():
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm happy to see these args go away!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah these args and the related EnsimeClient.setup method, probably my happiest moments of making these changes 😁 🍾

return client
key = os.path.realpath(config_path)
if key in self.clients:
return self.clients[key]
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think not using return may improve readability? I usually avoid several returns because they break program logic in unexpected ways and they have a big effect on readability in big chunks of code (as readers lose the thread of the logic and can be easily missed). Though redundant, I usually prefer this style:

client = None
if key in self.clients:
  client = self.clients[key]
else:
  client = self.create_client(config_path)
return client

The previous code snippet is very common in Java code, where the idiom of "only return once" is quite used. I'm not aware of Python devs using it and I don't want to bikeshed --all this refactoring is amazing--, but I'm just curious what you think about it.


self.log = setup_logger()
self.log.debug('__init__: in')
self.editor.initialize()

self.ws = None
self.ensime = None
Copy link
Contributor

Choose a reason for hiding this comment

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

This change breaks :EnClients

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, I was aware but hadn't left a todo comment, thanks for logging it here.

:EnClients is currently undocumented, I think it should be renamed to :EnStatus to reflect that it's really as much about server statuses, and the output formatting enhanced. This could even become a special filetype window for a "console" in our server lifecycle plans, with some summarized key mappings for performing actions like restarting the server on the current line, etc.

I'll just make some minimal fix here so it isn't utterly broken, until we give EnsimeProcess some attention in forthcoming work.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that :EnStatus would be a better name and it's better to tackle this in another PR.

"""Handler for CursorHold autocommand event."""
self.watchdog_client_server_connection(client)
client.unqueue_and_display(filename)
client.editor.cursorhold()
Copy link
Contributor

Choose a reason for hiding this comment

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

This call to editor's cursorhold causes the event to be retriggered every second when no activity is happening. I don't think this is the intended behaviour.

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 was moved as-is from EnsimeClient.on_cursor_hold so it's not a behavior change in this PR. I don't exactly understand the background of how this achieves "faking a timer" as the link referenced at Editor.cursohold describes, but if there's anything wrong with that I'd propose it's brought up in a new issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've been using this PR for the last days. It works well, though I've discovered that vim is slower to respond to keystrokes and, sometimes, events are fired by no reason. For instance, when typing code, the UI gets blocked. I infer this happens because ensime-vim is querying the server and waiting for its response. I don't know if this is a consequence of what @ktonga points out.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, my issue is that I have a deoplete delay of 450ms that was fired all the time. I removed it. I'll file an issue to keep track of the "blocked UI" issue, as I've experienced it in other ensime requests that take a lot of time to come back.

Copy link
Contributor

Choose a reason for hiding this comment

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

Deoplete while using omnifunc does wait for the response in a synchronous way, and more often than I'd like ensime server gets really really slow to respond having to wait several seconds after each keystroke what makes a terrible typing experience, that's why I want to implemente a deoplete source so the integration with autocomplete will be asynchronous avoiding those horrible blocks. But we have to work on ensime-vim integration API first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for testing @jvican. Would be curious about reproducible cases of blocking the UI if it's not completions, I think they're probably not results of this PR, but still.

In theory these changes could improve responsiveness: there's a subtle change where I moved the time.sleep in the polling thread's loop, it was running on every normal iteration and upon inspection this is needless, socket recv is efficient blocking I/O that's exactly what we want to happen with no artificial pause for this threading model. I moved the sleep to instead prevent a total busy-loop in the condition that we've lost the connection. This reconnection stuff isn't perfect yet but we'll focus on that soon; I'm not positive that there aren't race conditions (i.e. the running and connected attributes shared between threads without a queue), but these things haven't really changed here—a deep refactoring of this logic wasn't my intention, yet. I haven't actually been able to observe what I think is a race, the GIL may be solving it. If we're noticeably blocking Vim anywhere though, that's got to be the main thread.

I've been traveling this week so haven't had a chance to tie up the loose ends on this PR, will try this weekend.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's isn't our fault this time, we're only the middleman here. The responsibility for the blocking is shared between Ensime server which from time to time likes taking a nap before responding, and the naive omnicompletion API Vim provides (and we hook into) which is called synchronously from (I guess since it blocks) the UI thread.

@ches ches force-pushed the remove-server-lifecycle-from-client branch from 02032bc to d54b7d3 Compare January 13, 2017 10:02
@ktonga
Copy link
Contributor

ktonga commented Aug 5, 2017

Hey @ches do you have any outstanding work on this PR to be pushed? I can rebase it and merge it if you don't have the time, I think it is a huge improvement over the global design that could unblock a bunch of other improvements and I'd like to get it merged sooner rather than later.

Cheers.

@ches
Copy link
Contributor Author

ches commented Aug 6, 2017

I do have quite a bit more based on top of this that's sitting WIP actually, and I've been using it for awhile too. I'll try to see what's needed to wrap it up cleanly, won't be able to until after Monday but hopefully can find some time by mid-week.

Perhaps for one thing we can simplify on the caveat noted in the PR description by just going ahead and dropping support for the old server bootstrap installation in this branch—we're 8+ months into sbt-ensime handling that, I'm sure just about everyone is using it now.

@ktonga
Copy link
Contributor

ktonga commented Aug 6, 2017

Totally, let's stop supporting legacy stuff, it is easy enough for people to keep sbt up to date since the recommended plugin installation is add sbt-ensime as a local/per-developer plugin.

I think waiting a few more days for it after waiting for several months is ok :)

@ches
Copy link
Contributor Author

ches commented Aug 10, 2017

Rebasing this has been a bit tricky after the timer change from #374 and plus we're on server v2 by default now, etc. I have it working but it's shakier than it was before these changes. I think I'll finish sorting it out today though.

@ches ches force-pushed the remove-server-lifecycle-from-client branch from d54b7d3 to cd0e8c7 Compare August 26, 2017 12:20
@ches
Copy link
Contributor Author

ches commented Aug 26, 2017

So, I've pushed rebased commits, but the status is still basically as I noted in my last comment: it has some issues that weren't present before. It mostly works, you should be able to run it if you want to try to help me debug, but I'm seeing a few problems:

  • You're told twice that the server is starting, and all the messages about server starting, analyzer/indexer ready, etc. now interrupt you requiring a keypress to acknowledge. Not sure why that's happened, I think usually Vim does this when a message is more than one line long. Maybe we've picked up an extra newline somewhere? I don't see that more than one server process is actually getting started, probably just reporting twice as a result of retries when everything is starting up. Might have even been happening before but it wasn't noticeable without the keypress interruptions. (Edit: interestingly, does not seem to be an issue in Neovim).
  • We sometimes seem to get into a state where the response queue stops being processed: I can use features normally like type inspection, go to definition, completions, and then it seems like trying to go to a definition outside my project (a dependency or stdlib) gets stuck. In debug log it looks like we get a message back from server, but don't dequeue it. Sometimes I can invoke a completion or something and the queue flushes and I see my pending go to def happen.

The interaction of this branch's changes with the timer introduced in #374 is tricky and I've probably just missed reconciling something correctly when rebasing. I'll keep poking at it to try to fix the issues, if anyone tries it now and sees where problems are coming from do let me know!

@ches ches force-pushed the remove-server-lifecycle-from-client branch from cd0e8c7 to ad5045b Compare August 26, 2017 12:56
ches added 5 commits August 27, 2017 13:31
At one time these were to support shutting down the server, but had
become inconsistent and broken. The fact that the toggle_teardown
attribute of EnsimeClient had a default value of True is probably a
hint...

We're working toward more robust support for controlling server
lifecycle now.
I was looking at and updating the overview here and thought it'd make
sense if it was actually expressed in the source :-)

http://ensime.github.io/editors/vim/contributing/#ensime-integration
Killing the server doesn't completely lead to everything restarting yet,
but it's almost there. Might leave the rest to @ktonga on #350.

Generally it's supposed to be an invariant that if client's
self.connected is true, then self.ws should be set--so use that
everywhere instead of having a mix that's confusing.
This allows an Editor instance to be an attribute of the Ensime class
since it avoids use of vim.eval during Ensime's constructor, where that
is a known initialization problem.

Side effects in constructors often lead to trouble so this is probably
the right thing to do anyway.
@ches ches force-pushed the remove-server-lifecycle-from-client branch from ad5045b to 90a7da5 Compare August 27, 2017 06:34
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Factor launcher instance out of EnsimeClient entirely

3 participants