elpy icon indicating copy to clipboard operation
elpy copied to clipboard

[emacs27] virtual env is sometime reset to rpc-env

Open gaetano-guerriero opened this issue 5 years ago • 20 comments

Summary

Using emacs27, the current active virtual environment is lost and is reset to rpc-env environment.

Steps to reproduce

  • activate a virtual environment with pyvenv-workon
  • open a python buffer
  • navigate the buffer
  • check if the selected virtualenv is still active, sometimes elpy rpc-env will be active instead

My configuration

Result of (elpy-config)

Elpy Configuration

Emacs.............: 27.0.91
Elpy..............: 1.33.0
Virtualenv........: myenv (/home/gaetano/.virtualenvs/myenv/)
Interactive Python: python 3.7.5 (/home/gaetano/.virtualenvs/myenv/bin/python)
RPC virtualenv....: rpc-venv (/home/gaetano/.emacs.d/elpy/rpc-venv)
 Python...........: python 3.8.3 (/home/gaetano/.emacs.d/elpy/rpc-venv/bin/python)
 Jedi.............: 0.17.0
 Rope.............: 0.17.0
 Autopep8.........: 1.5.2
 Yapf.............: 0.30.0
 Black............: 19.10b0
Syntax checker....: flake8 (/home/gaetano/.virtualenvs/myenvbin/flake8)

gaetano-guerriero avatar May 11 '20 18:05 gaetano-guerriero

Elpy temporarily activates the rpc-virtualenv when starting a RPC process (that is, when opening a new python buffer).

If you check what is the active virtualenv at this time, it would indeed be the rpc one. However, it should go reactivate your virtualenv quickly after that. Is it what is happening for you ?

(or it could be related to emacs27, Elpy does not support it officially).

galaunay avatar May 27 '20 08:05 galaunay

The active virtualenv change is permanent. It seems to me that with-elpy-rpc-virtualenv-activated sometimes doesn't revert the virtualenv change. This happens not only when starting the RPC process, but also when using elpy rpc ,for instance when jumping to symbol definition.

Currently I'm running instead on emacs27 with (setq elpy-rpc-virtualenv-path 'current) and everything seems ok.

gaetano-guerriero avatar May 27 '20 15:05 gaetano-guerriero

I am not sure why it stays activated. Maybe if you can run this instrumented version of with-elpy-rpc-virtualenv-activated, it may shed some light on what's going wrong...

(defmacro with-elpy-rpc-virtualenv-activated (&rest body)
  "Run BODY with Elpy's RPC virtualenv activated.

During the execution of BODY the following variables are available:
- `current-environment': current environment path.
- `current-environment-binaries': current environment python binaries path.
- `current-environment-is-deactivated': non-nil if the current
  environment has been deactivated (it is not if the RPC environment and
  the current environment are the same)."
  `(if (not (executable-find elpy-rpc-python-command))
       (error "Cannot find executable '%s', please set 'elpy-rpc-python-command' to an existing executable." elpy-rpc-python-command)
     (message "Calling `with-elpy-rpc-virtualenv-activated'")
     (let* ((pyvenv-post-activate-hooks (remq 'elpy-rpc--disconnect
                                              pyvenv-post-activate-hooks))
            (pyvenv-post-deactivate-hooks (remq 'elpy-rpc--disconnect
                                                pyvenv-post-deactivate-hooks))
            (venv-was-activated pyvenv-virtual-env)
            (current-environment-binaries (executable-find
                                           elpy-rpc-python-command))
            (current-environment (directory-file-name (file-name-directory (directory-file-name (file-name-directory current-environment-binaries)))))
            ;; No need to change of venv if they are the same
            (same-venv (or (string= current-environment
                                    (elpy-rpc-get-virtualenv-path))
                           (file-equal-p current-environment
                                         (elpy-rpc-get-virtualenv-path))))
            current-environment-is-deactivated)
       (message "venv-was-activated: %s" venv-was-activated)
       (message "same-venv: %s" same-venv)
       (message "current-environment: %s" current-environment)
       ;; If different than the current one, try to activate the RPC virtualenv
       (unless same-venv
         (condition-case err
             (pyvenv-activate (elpy-rpc-get-or-create-virtualenv))
           ((error quit)
            (if venv-was-activated
                (pyvenv-activate venv-was-activated)
              (pyvenv-deactivate))
            (error err)))
         (setq current-environment-is-deactivated t)
         (message "RPC venv activated"))
       (let (venv-err result)
         ;; Run BODY and catch errors and quit to avoid keeping the RPC
         ;; virtualenv activated
         (condition-case err
             (progn
               (message "Running BODY")
               (setq result (progn ,@body)))
           (error (setq venv-err
                        (if (stringp err)
                            err
                          (car (cdr err)))))
           (quit nil))
         ;; Reactivate the previous environment if necessary
         (unless same-venv
           (if venv-was-activated
               (progn
                 (message "Reactivating old venv: %s" venv-was-activated)
                 (pyvenv-activate venv-was-activated))
             (message "Deactivating the RPC venv")
             (pyvenv-deactivate)))
         ;; Raise errors that could have happened in BODY
         (when venv-err
           (error venv-err))
         result))))

galaunay avatar May 27 '20 17:05 galaunay

Here's the output when opening a file and then moving around:

venv-was-activated: /home/gaetano/.virtualenvs/media-director/
same-venv: nil
current-environment: /home/gaetano/.virtualenvs/media-director
Mark set
Calling ‘with-elpy-rpc-virtualenv-activated’
venv-was-activated: /home/gaetano/.virtualenvs/media-director/
same-venv: nil
current-environment: /home/gaetano
RPC venv activated
Running BODY
Reactivating old venv: /home/gaetano/.virtualenvs/media-director/
Waiting for process to die...done
Parsing test_track.py (LALR)... 
Calling ‘with-elpy-rpc-virtualenv-activated’
venv-was-activated: /home/gaetano/.emacs.d/elpy/rpc-venv/
same-venv: nil
current-environment: /home/gaetano
RPC venv activated
Running BODY
Reactivating old venv: /home/gaetano/.emacs.d/elpy/rpc-venv/
current $VIRTUAL_ENV /home/gaetano/.emacs.d/elpy/rpc-venv/
Parsing test_track.py (LALR)... 
Quit
Mark saved where search started
Parsing test_track.py (LALR)...done
Mark set
Mark activated

Line "Waiting processs to die..." seems suspicious.

gaetano-guerriero avatar May 27 '20 18:05 gaetano-guerriero

I don't know which process exactly get killed, but it seems to prevent the RPC virtualenv to get deactivated properly. Can you still deactivate it with (pyvenv-deactivate) after that ? If not, can you get a backtrace (after a toggle-debug-on-error) ?

galaunay avatar May 29 '20 18:05 galaunay

Thank you for help in debugging it. rpc-env can be successfully deactivated with (pyvenv-deactivate) When the old venv reactivation fails with "Waiting for process to die...done" I get no backtrace, after enabling toggle-debug-on-error

gaetano-guerriero avatar Jun 03 '20 09:06 gaetano-guerriero

I have this same issue.

Emacs.............: 28.0.50
Elpy..............: 1.34.0
Virtualenv........: runsomething (/home/at145/.virtualenvs/runsomething/)
Interactive Python: python 3.6.7 (/home/at145/.virtualenvs/runsomething/bin/python)
RPC virtualenv....: rpc-venv (/home/at145/.emacs.d/elpy/rpc-venv)
 Python...........: python3.7 3.7.5 (/home/at145/.emacs.d/elpy/rpc-venv/bin/python3.7)
 Jedi.............: 0.17.1 (0.17.2 available)
 Rope.............: Not found (0.17.0 available)
 Autopep8.........: 1.5.3
 Yapf.............: 0.30.0
 Black............: 19.10b0
Syntax checker....: Not found (flake8)

Xparx avatar Jul 24 '20 15:07 Xparx

Sorry it took me a bit of time to get back to you.

It looks like something is failing during (pyvenv-activate YOUR-VENV). which is weird knowing you managed to activate it in the first place... Anyway, there is an instrumented version of pyvenv-activate below. The output will at least tell us where Emacs is waiting.

@Xparx A temporary workaround is to not use Elpy RPC (with (setq elpy-rpc-virtualenv-path 'current).)

(defun pyvenv-activate (directory)
  "Activate the virtual environment in DIRECTORY."
  (interactive (list (read-directory-name "Activate venv: " nil nil nil
					  pyvenv-default-virtual-env-name)))
  (setq directory (expand-file-name directory))
  (message "Deactivating previous venv: %s" pyvenv-virtual-env-name)
  (pyvenv-deactivate)
  (message "Activating new venv: %s" directory)
  (setq pyvenv-virtual-env (file-name-as-directory directory)
        pyvenv-virtual-env-name (file-name-nondirectory
                                 (directory-file-name directory))
        python-shell-virtualenv-path directory
        python-shell-virtualenv-root directory)
  ;; Set venv name as parent directory for generic directories or for
  ;; the user's default venv name
  (when (or (member pyvenv-virtual-env-name '("venv" ".venv" "env" ".env"))
	    (and pyvenv-default-virtual-env-name
		 (string= pyvenv-default-virtual-env-name
			  pyvenv-virtual-env-name)))
    (setq pyvenv-virtual-env-name
          (file-name-nondirectory
           (directory-file-name
            (file-name-directory
             (directory-file-name directory))))))
  (message "pyvenv-virtual-env-name: %s" pyvenv-virtual-env-name)
  ;; Preserve variables from being overwritten.
  (let ((old-exec-path exec-path)
        (old-eshell-path eshell-path-env)
        (old-process-environment process-environment))
    (message "Running 'pre_activate' hooks")
    (unwind-protect
        (pyvenv-run-virtualenvwrapper-hook "pre_activate" pyvenv-virtual-env)
      (setq exec-path old-exec-path
            eshell-path-env old-eshell-path
            process-environment old-process-environment)))
  (message "Running pyvenv pre activate hooks: %s" pyvenv-pre-activate-hooks)
  (run-hooks 'pyvenv-pre-activate-hooks)
  (message "Updating pyvenv variables to new venv")
  (let ((new-directories (append
                          ;; Unix
                          (when (file-exists-p (format "%s/bin" directory))
                            (list (format "%s/bin" directory)))
                          ;; Windows
                          (when (file-exists-p (format "%s/Scripts" directory))
                            (list (format "%s/Scripts" directory)
                                  ;; Apparently, some virtualenv
                                  ;; versions on windows put the
                                  ;; python.exe in the virtualenv root
                                  ;; for some reason?
                                  directory)))))
    (setq pyvenv-old-exec-path exec-path
          pyvenv-old-eshell-path eshell-path-env
          pyvenv-old-process-environment process-environment
          ;; For some reason, Emacs adds some directories to `exec-path'
          ;; but not to `process-environment'?
          exec-path (append new-directories exec-path)
          ;; set eshell path to same as exec-path
          eshell-path-env (mapconcat 'identity exec-path ":")
          process-environment (append
                               (list
                                (format "VIRTUAL_ENV=%s" directory)
                                (format "PATH=%s"
                                        (mapconcat 'identity
                                                   (append new-directories
                                                           (split-string (getenv "PATH")
                                                                         path-separator))
                                                   path-separator))
                                ;; No "=" means to unset
                                "PYTHONHOME")
                               process-environment)
          ))
  (message "Running 'post_activate' hooks")
  (pyvenv-run-virtualenvwrapper-hook "post_activate")
  (message "Running pyvenv post activate hooks: %s" pyvenv-post-activate-hooks)
  (run-hooks 'pyvenv-post-activate-hooks))

galaunay avatar Aug 05 '20 15:08 galaunay

Thanks for the answer. I have set this parameter and it seems to workaround the problem.

Xparx avatar Aug 06 '20 22:08 Xparx

Having a similar problem w/ emacs 28 HEAD. rpc-env gets activated, but it fails to restore the venv afterward.

Using the instrumented with-elpy-rpc-virtualenv-activated:

with-elpy-rpc-virtualenv-activated
Deactivating previous venv: rpc-venv
Activating new venv: /Users/tmiller/.local/share/virtualenvs/pynet-analysis
pyvenv-virtual-env-name: pynet-analysis
Running ’pre_activate’ hooks
Running pyvenv pre activate hooks: nil
Updating pyvenv variables to new venv
Running ’post_activate’ hooks
Running pyvenv post activate hooks: (elpy-rpc--disconnect)
C-x C-g is undefined
Note: standard-indent, tab-width adjusted to 8
Deactivating previous venv: pynet-analysis
Activating new venv: /Users/tmiller/.emacs.d/elpy/rpc-venv
pyvenv-virtual-env-name: rpc-venv
Running ’pre_activate’ hooks
Running pyvenv pre activate hooks: nil
Updating pyvenv variables to new venv
Running ’post_activate’ hooks
Running pyvenv post activate hooks: nil
Deactivating previous venv: rpc-venv
Waiting for process to die...done

Using the instrumented pyvenv-activate (alone) above:

pyvenv-activate
Quit
Deactivating previous venv: nil
Activating new venv: /Users/tmiller/.local/share/virtualenvs/pynet-analysis
pyvenv-virtual-env-name: pynet-analysis
Running ’pre_activate’ hooks
Running pyvenv pre activate hooks: nil
Updating pyvenv variables to new venv
Running ’post_activate’ hooks
Running pyvenv post activate hooks: (elpy-rpc--disconnect)
Quit
Deactivating previous venv: pynet-analysis
Activating new venv: /Users/tmiller/.emacs.d/elpy/rpc-venv
pyvenv-virtual-env-name: rpc-venv
Running ’pre_activate’ hooks
Running pyvenv pre activate hooks: nil
Updating pyvenv variables to new venv
Running ’post_activate’ hooks
Waiting for process to die...done
Quit
Note: standard-indent, tab-width adjusted to 8

Config:

Emacs.............: 28.0.50
Elpy..............: 1.34.0
Virtualenv........: rpc-venv (/Users/tmiller/.emacs.d/elpy/rpc-venv)
Interactive Python: python 3.7.3 (/Users/tmiller/.emacs.d/elpy/rpc-venv/bin/python)
RPC virtualenv....: rpc-venv (/Users/tmiller/.emacs.d/elpy/rpc-venv)
 Python...........: python3 3.7.3 (/Users/tmiller/.emacs.d/elpy/rpc-venv/bin/python3)
 Jedi.............: 0.17.2
 Rope.............: 0.17.0
 Autopep8.........: 1.5.4
 Yapf.............: 0.30.0
 Black............: 19.10b0 (20.8b1 available)
Syntax checker....: flake8 (/Users/tmiller/.emacs.d/elpy/rpc-venv/bin/flake8)

Cerebus avatar Aug 27 '20 18:08 Cerebus

As suspected, Emacs get stuck in pyvenv-activate (in pyvenv-run-virtualenvwrapper-hook to be more precise).

Do you have the package virtualenvwrapper installed in your system python ?

galaunay avatar Sep 26 '20 14:09 galaunay

Yes, I use virtualenvwrapper.

Cerebus avatar Nov 13 '20 19:11 Cerebus

Do you have postactivate and preactivate scripts in ~/.emacs/elpy/rpc-venv/bin ? If so, you can try reinstalling the rpc virtualenv with M-x elpy-rpc-reinstall-virtualenv.

If not, it could be that pyvenv is mistakenly thinking that the rpc-venv has been made with virtualenvwrapper. It then tries to run hooks that does not exist. If is is the case, using the following advice may help:

(defadvice pyvenv-virtualenvwrapper-supported (around not-for-elpy-rpc-venv activate)
  "Ensure no virtualenvwrapper support for the RPC virtualenv"
  (if (string= pyvenv-virtual-env-name "rpc-venv")
      nil
    ad-do-it))

If the issue comes from this, it will be a good idea to report the issue upstream to Pyvenv.

galaunay avatar Nov 15 '20 14:11 galaunay

Swapped back to elpy after suffering w/ python-mode + eglot for a while.

On a fresh reinstall, seems to be to be tracking OK now even w/o the advice. There's no post/preactivate scripts in (newly created) rpc-venv/bin:

❯ ls -l
total 152
-rw-r--r--  1 tmiller  staff  8471 Dec 11 15:44 Activate.ps1
-rw-r--r--  1 tmiller  staff  2225 Dec 11 15:44 activate
-rw-r--r--  1 tmiller  staff  1277 Dec 11 15:44 activate.csh
-rw-r--r--  1 tmiller  staff  2429 Dec 11 15:44 activate.fish
-rwxr-xr-x  1 tmiller  staff   420 Dec 11 15:44 autopep8
-rwxr-xr-x  1 tmiller  staff   256 Dec 11 15:44 black
-rwxr-xr-x  1 tmiller  staff   251 Dec 11 15:44 black-primer
-rwxr-xr-x  1 tmiller  staff   257 Dec 11 15:44 blackd
-rwxr-xr-x  1 tmiller  staff   266 Dec 11 15:44 easy_install
-rwxr-xr-x  1 tmiller  staff   266 Dec 11 15:44 easy_install-3.8
-rwxr-xr-x  1 tmiller  staff   250 Dec 11 15:44 flake8
-rwxr-xr-x  1 tmiller  staff   248 Dec 11 15:44 pip
-rwxr-xr-x  1 tmiller  staff   248 Dec 11 15:44 pip3
-rwxr-xr-x  1 tmiller  staff   248 Dec 11 15:44 pip3.8
-rwxr-xr-x  1 tmiller  staff   248 Dec 11 15:44 pycodestyle
-rwxr-xr-x  1 tmiller  staff   247 Dec 11 15:44 pyflakes
lrwxr-xr-x  1 tmiller  staff     7 Dec 11 15:44 python -> python3
lrwxr-xr-x  1 tmiller  staff    51 Dec 11 15:44 python3 -> /Library/Developer/CommandLineTools/usr/bin/python3
-rwxr-xr-x  1 tmiller  staff   247 Dec 11 15:44 yapf

However, I'm getting a virtualenvwrapper hook output buffer popping up every time I load a python file (w/ empty post_activate/post_deactivate output). Something's off w/ pyvenv but I'm not doing any special config here.

Cerebus avatar Dec 11 '20 21:12 Cerebus

I sorted out the hook buffer (see https://github.com/jorgenschaefer/pyvenv/issues/102 for a fix).

I experimented a bit more and I think the venv switching is a problem with both auto-virtualenv and auto-virtualenvwrapper packages. I've been using auto-virtualenvwrapper, so I swapped that out for auto-virtualenv and it was also having problems keeping the activated venv straight.

I switched over to setting pyvenv-workon in dir-locals and on emacs 27.1/Linux the problem seems resolved. I'll test on macOS and emacs:HEAD later.

Cerebus avatar Dec 13 '20 16:12 Cerebus

On macOS I also had issues w/ rpc-venv stickiness w/ pyvenv-tracking-mode enabled. Turned that off.

Because of brew, I also had to fix elpy-rpc-python-command as a fully-qualified path. Apparently I'd set this to just python3 and that seemed to cause problems.

I also wiped the rpc-venv and had elpy rebuild it before loading any projects or activating another venv. That seemed to settle things.

Everything seems stable now.

Cerebus avatar Dec 14 '20 14:12 Cerebus

Extended editing sessions were still sticking, so I added the defadvice above and that seems to be working. I'll open an issue under pyvenv.

Cerebus avatar Jan 01 '21 00:01 Cerebus

So I can't say for certain that it's the same issue I'm seeing, but I've managed to figure out, at least in one case, why this would happen. @galaunay would love to hear your thoughts on this. Let me know if I need to clarify anytthing :smile:

My steps to reproduce it are as follows, but not that it _requires a slow elpy-rpc "init" execution, basically longer than the eldoc delay.

  1. M-x revert-buffer or (pyvenv-activate "/path/to/venv")
  2. Move around and observe the eldoc changing a couple of times. Notice that the venv is now [rpc-venv].

I was able to reliably reproduce this with a small change, adding a (sleep-for 5) inside elpy-rpc-init.

(defun elpy-rpc-init (library-root environment-binaries &optional success error)
  "Initialize the backend.

This has to be called as the first method, else Elpy won't be
able to respond to other calls.

+LIBRARY-ROOT is the current project root,
+ENVIRONMENT-BINARIES is the path to the python binaries of the environment to work in."
  (message "%S sleeping 5" (format-time-string "%Y-%m-%dT%H:%M:%S"))
  (sleep-for 5)
  (message "%S awake" (format-time-string "%Y-%m-%dT%H:%M:%S"))
  (elpy-rpc "init"
            ;; This uses a vector because otherwise, json-encode in
            ;; older Emacsen gets seriously confused, especially when
            ;; backend is nil.
            (vector `((project_root . ,(expand-file-name library-root))
                      (environment . ,(when environment-binaries
                                        (expand-file-name
                                         environment-binaries)))))
            success error))

My observation leading to this theory is that each time (elpy-rpc "get_calltip_or_oneline_docstring" ...) is called, it calls elpy-rpc-init if the RPC buffer hasn't already been created (for example, if it's taking time to do so). When this happens, it does a nested (elpy-rpc "init" ...) call which also saves the current venv, but this time, the current venv is actually rpc-venv instead of my active venv (or no venv, as it were). Then when the init RPC finishes, it "restores" the venv to the RPC venv.

(elpy-rpc "get_calltip_or_oneline_docstring")
 (elpy-rpc--call "get_calltip_or_oneline_docstring")
  (elpy-rpc--get-rpc-buffer)
   (elpy-rpc-open)
    (elpy-rpc-init)
     (elpy-rpc "init")

And so for the first call to (elpy-rpc "get_calltip_or_oneline_docstring"), venv-was-activated is correct, but when it's called a second time, venv-was-activate is rpc-venv because the first call is still executing the (sleeping) (elpy-rpc-init).

Hopefully that makes sense. I don't have a good idea for a fix just yet, though!

I just reproduced this on emacs 27.2 after installing only elpy and pyvenv from melpa.

Elpy Configuration

Emacs.............: 27.2
Elpy..............: 1.35.0
Virtualenv........: rpc-venv (/home/wade/.emacs.d/elpy/rpc-venv)
Interactive Python: python 3.8.10 (/home/wade/.emacs.d/elpy/rpc-venv/bin/python)
RPC virtualenv....: rpc-venv (/home/wade/.emacs.d/elpy/rpc-venv)
 Python...........: python 3.8.10 (/home/wade/.emacs.d/elpy/rpc-venv/bin/python)
 Jedi.............: 0.18.0
 Autopep8.........: 1.6.0
 Yapf.............: 0.31.0
 Black............: 21.10b0
Syntax checker....: flake8 (/home/wade/.emacs.d/elpy/rpc-venv/bin/flake8)

wwade avatar Nov 02 '21 02:11 wwade

Even adding a small delay like (sleep-for 0 100) ;; 100 ms in elpy-rpc-init can reproduce it with (setq eldoc-idle-delay 0) and some quick C-n C-n to get a couple calls in there, i.e.

  1. (setq eldoc-idle-delay 0)
  2. (pyvenv-activate "/path/to/venv")
  3. C-n C-n

wwade avatar Nov 02 '21 02:11 wwade

@galaunay just wondering if you had a chance to look at my analysis of the problem in this earlier post? I would submit a PR if I had some idea how to fix it, but alas, hoping for an expert to weigh in here.

wwade avatar Jan 20 '22 17:01 wwade