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

Neovim installer should be synchronous during vim_starting #104

Closed
junegunn opened this issue Oct 10, 2014 · 30 comments
Closed

Neovim installer should be synchronous during vim_starting #104

junegunn opened this issue Oct 10, 2014 · 30 comments

Comments

@junegunn
Copy link
Owner

Since the new parallel installer for Neovim is asynchronous, we cannot do nvim +PlugUpdate +qa as before. We can either add an option to PlugUpdate command to quit Vim when it's complete or wait for the addition of jobwait() function suggested by @tarruda. Or we can simply guide the users to use the original Vim instead in this case.
See:

@junegunn junegunn changed the title Neovim installer should be synchronous on vim_starting Neovim installer should be synchronous during vim_starting Oct 10, 2014
felipecrv referenced this issue in fwalch/neovim Dec 17, 2014
@adambiggs
Copy link

I'm trying to make a Docker image for my dev environment, but I hit the wall at this point.

RUN vim +PlugInstall +qa in my Dockerfile starts installing the first dozen or so plugins, but then quits right away before completion. Then when I run the docker image, open nvim, and run :PlugInstall manually, those plugins that started installing before are listed with the following error:

HEAD
PlugClean required.

I tried using nvim +'PlugInstall --quit' like you mentioned in #103, but it looks like this isn't implemented yet. Is there any workarounds you know of? Doesn't matter if it's hacky since it only gets run once when building the docker image.

Thanks again for the great tool!

@junegunn
Copy link
Owner Author

@adambiggs

RUN vim +PlugInstall +qa in my Dockerfile starts installing the first dozen or so plugins, but then quits right away before completion

This shouldn't happen. I also have my Dockerfile which does PlugInstall, and it installs just fine. Make sure that you're running the original vim here, not neovim. Is it possible that vim is somehow linked to nvim? Check out the output of vim --version.

@adambiggs
Copy link

@junegunn thanks for the quick reply. I'm running nvim on purpose in this docker image. But I guess I can just add normal vim to the image and use it to install plugins in the Dockerfile.

Probably a good idea to have both versions anyway until NeoVim becomes more stable.

Thanks for your help!

@tarruda
Copy link

tarruda commented Mar 28, 2015

It will be possible to close this after neovim/neovim#2247 is merged.

@tarruda
Copy link

tarruda commented Mar 30, 2015

I've been thinking of how jobwait() can be used to implement this.

Since vim-plug has a maximum number of threads, we cant simply start and wait all curl jobs in a single pass. A solution is to use a "waiter job" that only exits after all downloads are finished. Something like this:

let plugins = GetPlugins()  " collect all plugins and commands that must be executed
let jobs = {}

function! s:StartPending()
  if empty(plugins)
    return 0
  endif
  let plugin = remove(plugins, 0)
  let jobs[jobstart(plugin.install_argv, {'on_exit': 's:OnExit'}))] = 1
  return 1
endfunction

function! s:OnExit(id, status)
  call s:StartPending()
  call remove(jobs, id)
  if empty(jobs)
    " last job, quit the waiter job
    call jobclose(g:waiter, 'stdin')
  endif
endfunction

" Start the initial batch of jobs
for i in range(max_threads)
  if !s:StartPending()
    break
  endif
endwhile

if !empty(jobs)
  " Start a waiter job so the installation will be synchronous
  let waiter = jobstart(['cat', '-'])
  call jobwait([waiter])
endif

@junegunn
Copy link
Owner Author

@tarruda I tried the following code just to see how it works:

diff --git a/plug.vim b/plug.vim
index a03c1da..00dcf4e 100644
--- a/plug.vim
+++ b/plug.vim
@@ -934,6 +934,10 @@ function! s:update_vim()

   call s:bar()
   call s:tick()
+  if s:nvim
+    let waiter = jobstart(['cat', '-'])
+    call jobwait([waiter])
+  endif
 endfunction

 function! s:tick()

but I'm seeing a bunch of errors:

Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_git_valid..<SNR>2_system..<SNR>2_job_handle
r..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_log..<SNR>2_logpos..<SNR>2_
job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_log..
<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_git_valid..<SNR>2_system..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_tick..<SNR
>2_git_valid..<SNR>2_system..<SNR>2_job_handler..<SNR>2_to_s..<SNR>2_job_handler..<SNR>2_log..<SNR>2_logpos..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_log..<SNR
>2_logpos..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_git_valid..<SNR>2_system..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_git_valid..<SNR>2_system..<SNR>2_job_handler..<SNR>2_tick..<S
NR>2_log:
line   11:
E21: Cannot make changes, 'modifiable' is off
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_git_valid:
line    4:
E171: Missing :endif
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick:
line   23:
E171: Missing :endif
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler:
line   18:
E171: Missing :endif
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_log:
line   11:
E21: Cannot make changes, 'modifiable' is off
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_log:
line    4:
E21: Cannot make changes, 'modifiable' is off: 18 d _
line   11:
E21: Cannot make changes, 'modifiable' is off
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_log:
line    4:
E21: Cannot make changes, 'modifiable' is off: 42 d _
line   11:
E21: Cannot make changes, 'modifiable' is off

or

Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_log:
line    4:
E523: Not allowed here
line   11:
E523: Not allowed here
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_bar:
line    3:
E523: Not allowed here
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_reap..<SNR>2_bar..<SNR>
2_progress_bar:
line    1:
E523: Not allowed here
Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_job_handler..<SNR>2_tick..<SNR>2_log:
line   11:
E523: Not allowed here
E523: Not allowed here

Looks like job_handler function is having trouble updating the buffer and the probability of error seems to increase as the number of concurrent tasks increases. (not sure) e.g. :PlugUpdate2 vs. :PlugUpdate40.

@tarruda
Copy link

tarruda commented Mar 30, 2015

Hmm, I forgot to mention that jobstart will close stdout if a callback is not passed. Maybe this will fix the problem, though you still need to call redraw somewhere(possibly in the job callbacks)

diff --git a/plug.vim b/plug.vim
index a03c1da..0565523 100644
--- a/plug.vim
+++ b/plug.vim
@@ -929,11 +929,19 @@ function! s:log(bullet, name, lines)
   endif
 endfunction

+function! s:stub()
+  " Need to pass one callback to `cat` or its stdout will be closed
+endfunction
+
 function! s:update_vim()
   let s:jobs = {}

   call s:bar()
   call s:tick()
+  if s:nvim
+    let waiter = jobstart(['cat', '-'], {'on_stdout': 's:stub'})
+    call jobwait([waiter])
+  endif
 endfunction

 function! s:tick()

@junegunn
Copy link
Owner Author

@tarruda I just applied your patch without any modification. On the first run, neovim crashed with segmentation fault after some errors. Since then I can't reproduce segv, but I still see errors.

@tarruda
Copy link

tarruda commented Mar 30, 2015

Ok I will investigate

@tarruda
Copy link

tarruda commented Apr 11, 2015

@junegunn jobwait problems should be fixed by neovim/neovim#2405. I added some extra fixes/improvements to the vim-plug patch above, can you test it?

diff --git a/plug.vim b/plug.vim
index 888ab36..2ed3ad5 100644
--- a/plug.vim
+++ b/plug.vim
@@ -125,10 +125,6 @@ function! s:to_a(v)
   return type(a:v) == s:TYPE.list ? a:v : [a:v]
 endfunction

-function! s:to_s(v)
-  return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
-endfunction
-
 function! s:source(from, ...)
   for pattern in a:000
     for vim in s:lines(globpath(a:from, pattern))
@@ -835,12 +831,18 @@ function! s:job_handler(job_id, data, event) abort
   endif

   if a:event == 'stdout'
-    let self.result .= substitute(s:to_s(a:data), '[\r\n]', '', 'g') . "\n"
-    " To reduce the number of buffer updates
-    let self.tick = get(self, 'tick', -1) + 1
-    if self.tick % len(s:jobs) == 0
-      call s:log(self.new ? '+' : '*', self.name, self.result)
+    if self.error
+      return
+    endif
+    if a:data[0] != "" && !empty(self.result)
+      " continue previous line
+      let self.result .= a:data[0]
+      call remove(a:data, 0)
     endif
+    if !empty(a:data)
+      let self.result .= "\n" . join(a:data, "\n")
+    endif
+    call s:log(self.new ? '+' : '*', self.name, self.result)
   elseif a:event == 'exit'
     let self.running = 0
     if a:data != 0
@@ -849,6 +851,7 @@ function! s:job_handler(job_id, data, event) abort
     call s:reap(self.name)
     call s:tick()
   endif
+  redraw
 endfunction

 function! s:spawn(name, cmd, opts)
@@ -892,6 +895,10 @@ function! s:reap(name)
   call s:bar()

   call remove(s:jobs, a:name)
+  if empty(s:jobs) && exists('s:waiter')
+    call jobclose(s:waiter, 'stdin')
+    unlet s:waiter
+  endif
 endfunction

 function! s:bar()
@@ -929,11 +936,27 @@ function! s:log(bullet, name, lines)
   endif
 endfunction

+function! s:stub()
+  " Need to pass one callback to `cat` or its stdout will be closed
+endfunction
+
 function! s:update_vim()
   let s:jobs = {}

   call s:bar()
   call s:tick()
+  if s:nvim
+    let s:waiter = jobstart(['cat', '-'], {'on_stdout': 's:stub'})
+    if jobwait([s:waiter])[0] == -2
+      " interrupted
+      for [name, j] in items(s:jobs)
+        silent! call jobstop(j.jobid)
+        let j.error = 1
+        let j.result = 'Interrupted!'
+      endfor
+      unlet s:waiter
+    endif
+  endif
 endfunction

 function! s:tick()

@junegunn
Copy link
Owner Author

@tarruda Will do. By the way, we were planning to apply the waiting only when has('vim_starting') since Neovim version of the installer has been asynchronous from the first release. But actually I don't really see the benefit of it being asynchronous, except that it looks cool. By making it synchronous, we can simplify the code and make it easier to test.
Another point I noticed from the patch is that it removes self.tick % len(s:jobs) == 0 condition that is for cutting down the CPU usage of Neovim. The overhead is especially noticeable when you fresh-install plugins using many number of threads (e.g. :PlugInstall40) in which case Neovim process uses 100% of a core and can slow down the whole process. Similar tricks are used by Ruby and Python versions as well.

@tarruda
Copy link

tarruda commented Apr 11, 2015

Another point I noticed from the patch is that it removes self.tick % len(s:jobs) == 0 condition that is for cutting down the CPU usage of Neovim

True, I removed that when testing and forgot to add it back

@tarruda
Copy link

tarruda commented Apr 11, 2015

The jobwait fix is merged

@junegunn
Copy link
Owner Author

@tarruda Yes, it's working!

@junegunn
Copy link
Owner Author

@tarruda I haven't checked if it's a bug in your patch or in jobwait, but I see some errors when I PlugInstall 56 plugins on an empty plugged directory.

Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler..<SNR>2_log:
line   11:
E21: Cannot make changes, 'modifiable' is off
E21: Cannot make changes, 'modifiable' is off
E21: Cannot make changes, 'modifiable' is off
E21: Cannot make changes, 'modifiable' is off

@vheon
Copy link
Contributor

vheon commented Apr 11, 2015

@junegunn be sure that vim-plug has the cursor in the right buffer. I was getting something similar when tinkering with :terminal for the do hooks, since it is async I would spawn the terminal and vim-plug would continue updating the plug buffer when the cursor was in the terminal which is a buffer nomodifiable.

@junegunn
Copy link
Owner Author

@vheon In fact I was testing with the patch @tarruda posted above, which makes Neovim installer synchronous. So I don't think the point is related :)

@starcraftman
Copy link
Contributor

@junegunn Figured I'd give this a go too. I couldn't reproduce your particular bug. Instead I got my own set. Used latest from PlugUpgrade with @tarruda 's patch.

  1. On PlugUpdate, my maxfuncdepth (100) is exceeded. That's after a completed PlugInstall.

E132: Function call depth is higher than 'maxfuncdepth' Error detected while processing function <SNR>2_update..<SNR>2_update_impl..<SNR>2_update_vim..<SNR>2_job_handler. .<SNR>2_tick..<SNR>2_git_valid:

  1. The waiter job doesn't exit properly at the end. This seems to happen when I call PlugInstall when all plugins are in fact installed.

@junegunn
Copy link
Owner Author

@starcraftman Yes, I also tried the much simpler patch @tarruda first posted again to make sure that the cause of the errors is not in the vimscript from plug.vim, and I'm still running into the similar errors. It's clear that there are some issues with jobwait(). We'll have to wait until it's stable.

@junegunn
Copy link
Owner Author

junegunn commented May 2, 2015

We could just wait for jobwait() to be fixed but for the moment I have an idea. We could fall back to python installer which is blocking if has('vim_starting').

@starcraftman I tried the following patch, but the current python installer on master branch doesn't seem to work on neovim. IIRC, you were working on making it work on Neovim. Is it ready? We could try your fix if it doesn't require you extra work. Thanks.

diff --git a/plug.vim b/plug.vim
index d0e2dad..7079dd5 100644
--- a/plug.vim
+++ b/plug.vim
@@ -72,8 +72,6 @@ let s:plug_tab = get(s:, 'plug_tab', -1)
 let s:plug_buf = get(s:, 'plug_buf', -1)
 let s:mac_gui = has('gui_macvim') && has('gui_running')
 let s:is_win = has('win32') || has('win64')
-let s:py2 = has('python') && !has('nvim') && !s:is_win && !has('win32unix')
-let s:ruby = has('ruby') && !has('nvim') && (v:version >= 703 || v:version == 702 && has('patch374'))
 let s:nvim = has('nvim') && exists('*jobwait') && !s:is_win
 let s:me = resolve(expand('<sfile>:p'))
 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
@@ -746,6 +744,9 @@ function! s:update_impl(pull, force, args) abort
     echohl None
   endif

+  let py2 = has('python') && (!s:nvim || has('vim_starting')) && !s:is_win && !has('win32unix')
+  let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374'))
+
   let s:update = {
     \ 'start':   reltime(),
     \ 'all':     todo,
@@ -754,7 +755,7 @@ function! s:update_impl(pull, force, args) abort
     \ 'pull':    a:pull,
     \ 'force':   a:force,
     \ 'new':     {},
-    \ 'threads': (s:py2 || s:ruby || s:nvim) ? min([len(todo), threads]) : 1,
+    \ 'threads': (py2 || ruby || s:nvim) ? min([len(todo), threads]) : 1,
     \ 'bar':     '',
     \ 'fin':     0
   \ }
@@ -767,20 +768,20 @@ function! s:update_impl(pull, force, args) abort
         \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''

   " Python version requirement (>= 2.7)
-  if s:py2 && !s:ruby && !s:nvim && s:update.threads > 1
+  if py2 && !ruby && s:update.threads > 1
     redir => pyv
     silent python import platform; print(platform.python_version())
     redir END
-    let s:py2 = s:version_requirement(
+    let py2 = s:version_requirement(
           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
   endif
-  if (s:py2 || s:ruby) && !s:nvim && s:update.threads > 1
+  if (py2 || ruby) && s:update.threads > 1
     try
       let imd = &imd
       if s:mac_gui
         set noimd
       endif
-      if s:ruby
+      if ruby
         call s:update_ruby()
       else
         call s:update_python()

@starcraftman
Copy link
Contributor

@junegunn I did manage to get the python installer working on neovim, I found putting all the buffer control into the python __main__ & using a queue to send messages worked. One thing I never fixed was the nvim installer isn't interruptible with Ctrl+C. Apart from that, I don't remember any regressions or issues.

You can test the your patch against my vim-plug fork on branch nvim_py. Just remember to set s:nvim = 0.

@junegunn
Copy link
Owner Author

junegunn commented May 2, 2015

@starcraftman Thanks. What do you think about the suggestion? It allows us to "fix" the issue right away but it seems like a stopgap method since I think it's better that we do not require python when jobwait() is stable.

@starcraftman
Copy link
Contributor

@junegunn The idea is fine but there may be some hiccups. The main one I see is that not all nvim users will have the python lib installed. Nvim doesn't honor the has('python') either. If you start nvim & type echo has('python') you get 0. However, I suspect most users probably do have it since many have gundo/syntastic/ultisnips.

Do you plan on documenting this temporary dependence? Or flag it via an error? If the latter, maybe you could check for it via something like:

function! PyExe()
try
  python3 1 + 1
  return 'python3'
catch
  try
    python 1 + 1
    return 'python'
  catch
    return ''
  endtry
endtry
endfunction

Additionally, my installer fails on Windows but they are now trying to get that working. They dropped many of the has() checks, we would have to verify has('win32') & others are still honoured or else change that too.

Edit: I might have been optimistic at the state of Windows build we can probably ignore that case for now.

@starcraftman
Copy link
Contributor

@junegunn See my fork at https://github.com/starcraftman/vim-plug. The branch starting has simple changes to make synchronous install on nvim. Is that all you were imagining?

Edit: Ha! Managed to segfault vim with the py3 test somehow.

@junegunn
Copy link
Owner Author

junegunn commented May 3, 2015

@starcraftman

If you start nvim & type echo has('python') you get 0.

Hmm, I'm getting 1, am I missing something? nvim -Nu NONE -c 'echo has("python")'

@starcraftman
Copy link
Contributor

@junegunn Guess I made a typo, restored back to way it was. So I guess I'll just review the starting branch tomorrow and make a PR.

junegunn added a commit that referenced this issue May 6, 2015
Use Python installer on Neovim during `vim_starting` (#104)
@mhinz
Copy link
Contributor

mhinz commented Jun 5, 2015

FWIW, testing for python and python3 should work without problems as long as the neovim module was installed with either pip2 or pip3 respectively.

@starcraftman
Copy link
Contributor

@mhinz Ya, I mistakenly thought the has method wouldn't work anymore. All taken care of in the patch I made beginning of may.

This issue remains open because the neovim installer shouldn't depend on my py installer for synchronous startup install. I haven't retested to see if the jobwait has been fixed.

nicknovitski added a commit to nicknovitski/devbox that referenced this issue Oct 30, 2015
Vim was giving me static: every time I opened a file, I had to Ctrl-C
before text would appear.  Then when I tried to save, it would warn me
"file already exists".  Then when I tried to open another file in a tab
or split, it just wouldn't work.

Installing the neovim python package is currently necessary to make
`nvim +PlugInstall` work right: junegunn/vim-plug#104
nicknovitski added a commit to nicknovitski/devbox that referenced this issue Oct 30, 2015
Vim was giving me static: every time I opened a file, I had to Ctrl-C
before text would appear.  Then when I tried to save, it would warn me
"file already exists".  Then when I tried to open another file in a tab
or split, it just wouldn't work.

Installing the neovim python package is currently necessary to make
`nvim +PlugInstall` work right: junegunn/vim-plug#104
vilhalmer added a commit to vilhalmer/System that referenced this issue Jan 3, 2016
- Remove vim-plug from this repository, install it and all plugins on
  first run. Will be further improved when junegunn/vim-plug#104 is
  resolved.
- Change runtimepath to include $XDG_DATA_HOME, move autoload there.
- Separate plugin configuration from init.vim.

The end result of this is that nvim's configuration directory is
essentially static, which is a huge improvement over .gitignoring parts
of it. One more step towards being able to have site-specific config.
razor-x added a commit to makenew/nvimrc that referenced this issue Apr 18, 2016
@neersighted
Copy link

Any progress on this? Just hit this issue myself.

@junegunn
Copy link
Owner Author

junegunn commented Oct 17, 2016

I just realized that Vimscript relinquishes control on sleep on the latest versions of Vim and Neovim. @blueyed mentioned that it was a recent change. Anyway, so it's now trivial to implement this.

diff --git a/plug.vim b/plug.vim
index f63c79a..e269dda 100644
--- a/plug.vim
+++ b/plug.vim
@@ -948,7 +948,7 @@ function! s:update_impl(pull, force, args) abort
     call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
   endif

-  let python = (has('python') || has('python3')) && (!s:nvim || has('vim_starting'))
+  let python = (has('python') || has('python3')) && !s:nvim
   let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && s:check_ruby()

   let s:update = {
@@ -1014,6 +1014,12 @@ function! s:update_impl(pull, force, args) abort
     endtry
   else
     call s:update_vim()
+    while s:nvim && has('vim_starting')
+      sleep 100m
+      if s:update.fin
+        break
+      endif
+    endwhile
   endif
 endfunction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants