gcv / julia-snail

An Emacs development environment for Julia
GNU General Public License v3.0
231 stars 21 forks source link

`completion-at-point` freezes emacs when REPL is busy #123

Open sjkobayashi opened 1 year ago

sjkobayashi commented 1 year ago

I often run functions that take a lot of time to evaluate (such as simulation and optimization) in Julia's REPL. When REPL is busy, emacs can hang up when company-mode automatically tries completion or if I manually invoke completion-at-point. I have to repeatedly press C-g to unfreeze emacs. I imagine this happens because completion relies on REPL? Is there a way to avoid this other than disabling company-mode?

gcv commented 1 year ago

Yes, this happens because Snail just uses the REPL thread. This problem should go away once #104 is fixed, which I'm starting to think needs to be a priority improvement.

gcv commented 11 months ago

I just released a change which should fix #104 and make this problem go away (a88755d). It'll be in MELPA in a couple of hours. Could you please give it a try and let me know what you think?

sjkobayashi commented 10 months ago

Thank you for looking into this! It seems like it works when no computation is involved, but it still hangs when there's computation.

For instance, completion pops up when running the following:

for i in 1:30
    println(i)
    sleep(1.0)
end

But it hangs when running something like the following:

using BenchmarkTools
@btime exp.(rand(100000));
gcv commented 10 months ago

It probably happens because Snail uses Julia tasks internally to manage concurrency. I will look into converting to (native) threads. I didn't do this initially because I've run into problems with JULIA_NUM_THREADS before (being unexpectedly set to 1 and therefore not really doing any meaningful multithreading). Thank you for the information, I'll update this ticket when I have some time to work on it.

sjkobayashi commented 10 months ago

I see. Did that happen even with --threads=auto argument?

gcv commented 10 months ago

Oh wow. I didn't know about the auto option. Since it has been available since 1.7, it means I have not looked into this in a long time. :) Thanks for telling me about it!

I'll definitely look into converting Snail from tasks to threads, then. Not exactly sure how much work it'll take.

Do you think I should also make Snail default to using the auto thread count option as long as the user hasn't overridden it? Or should I just document that it's a recommended value for julia-snail-extra-args?

sjkobayashi commented 10 months ago

Happy to help! It seems like the -t/--threads argument takes precedence before JULIA_NUM_THREADS, so it may be safer to just say that it's a recommended value. A bit unrelated, but you may find this upcoming feature on 1.10 interesting too: https://github.com/JuliaLang/julia/pull/48600

gcv commented 10 months ago

I have threads kind of working on this branch: https://github.com/gcv/julia-snail/tree/experimental-threads

It seems to avoid locking up, but it has another problem: task interruption stopped working (from https://github.com/gcv/julia-snail/issues/104 and https://github.com/gcv/julia-snail/commit/a88755dcb46380d6df2cf54c8e82d5aa15780768). The trick I used to stop a task on the current thread (schedule(task, InterruptException(), error=true)) causes all kinds of nasty crashes when used on a different thread.

I'm not sure what to do about it. Interrupting computation seems pretty important, but I don't see a robust way to implement it with Julia's current multithreading primitives.

sjkobayashi commented 9 months ago

This could change too many things, but is it possible to have another Julia process for code completion? I'm not quite familiar with how VS Code's Julia extension works, but it seems like it uses SymbolServer.jl to obtain info about packages without loading them.

gcv commented 9 months ago

A two-process solution is out of scope for Snail, but it should be possible to combine it with existing LSP tooling. You can disable Snail's non-REPL features as documented on the wiki (someone asked about this before). Then enable LanguageServer.jl. Emacs 29 comes with Eglot pre-installed, or you can try lsp-mode.

For Snail, I think the right solution is to introduce strict separation between computational commands sent to Julia by Snail itself and those sent by the user. The former would go into the :interactive threadpool, while the latter would be scheduled as tasks on the regular threadpool (documentation: https://docs.julialang.org/en/v1/manual/multi-threading/#man-threadpools). That would keep task interruption working for user-initiated computation, and should keep responsiveness for code completion.

That requires changing the Snail wire protocol to support tagging commands as :system and :user, which is a decent amount of work.

sjkobayashi commented 9 months ago

Thanks for the suggestion and explanation. I've been using task interruption and find it very useful, so I will look into the language server option for now.