Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions autoload/denops/_internal/job.vim
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ if has('nvim')
\ 'on_stderr': funcref('s:on_recv', [a:options.on_stderr]),
\ 'on_exit': funcref('s:on_exit', [a:options.on_exit]),
\}
return jobstart(a:args, l:options)
try
return jobstart(a:args, l:options)
catch
" NOTE: Call `on_exit` when cmd (args[0]) is not executable.
call timer_start(0, { -> l:options.on_exit(-1, -1, 'exit') })
endtry
endfunction

function! s:stop(job) abort
try
call jobstop(a:job)
call jobwait([a:job])
catch /^Vim\%((\a\+)\)\=:E900/
" NOTE:
" Vim does not raise exception even the job has already closed so fail
Expand Down Expand Up @@ -58,24 +62,27 @@ else
\ 'err_cb': funcref('s:out_cb', [a:options.on_stderr, 'stderr']),
\ 'exit_cb': funcref('s:exit_cb', [a:options.on_exit, 'exit']),
\}
return job_start(a:args, l:options)
let l:job = job_start(a:args, l:options)
if l:job->job_status() ==# "fail"
" NOTE:
" On Windows call `on_exit` when cmd (args[0]) is not executable.
" On Unix a non-existing command results in "dead" instead of "fail",
" and `on_exit` is called by `job_start()`.
call timer_start(0, { -> l:options.exit_cb(-1, -1) })
endif
return l:job
endfunction

function! s:stop(job) abort
call job_stop(a:job)
call timer_start(s:KILL_TIMEOUT_MS, { -> job_stop(a:job, 'kill') })
" Wait until the job is actually closed
while job_status(a:job) ==# 'run'
sleep 10m
endwhile
redraw
endfunction

function! s:out_cb(callback, event, ch, msg) abort
call a:callback(a:ch, a:msg, a:event)
function! s:out_cb(callback, event, job, msg) abort
call a:callback(a:job, a:msg, a:event)
endfunction

function! s:exit_cb(callback, event, ch, status) abort
call a:callback(a:ch, a:status, a:event)
function! s:exit_cb(callback, event, job, status) abort
call a:callback(a:job, a:status, a:event)
endfunction
endif
96 changes: 67 additions & 29 deletions autoload/denops/_internal/server/chan.vim
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ function! denops#_internal#server#chan#connect(addr, options) abort
endif
let l:retry_threshold = a:options.retry_threshold
let l:retry_interval = a:options.retry_interval
let l:previous_exception = ''
for l:i in range(l:retry_threshold)
let l:i = 1
while v:true
call denops#_internal#echo#debug(printf(
\ 'Connecting to channel `%s` [%d/%d]',
\ a:addr,
\ l:i + 1,
\ l:retry_threshold + 1,
\ l:i,
\ l:retry_threshold,
\))
try
call s:connect(a:addr, a:options)
Expand All @@ -43,28 +43,46 @@ function! denops#_internal#server#chan#connect(addr, options) abort
call denops#_internal#echo#debug(printf(
\ 'Failed to connect channel `%s` [%d/%d]: %s',
\ a:addr,
\ l:i + 1,
\ l:retry_threshold + 1,
\ l:i,
\ l:retry_threshold,
\ v:exception,
\))
let l:previous_exception = v:exception
if l:i >= l:retry_threshold
call denops#_internal#echo#error(printf(
\ 'Failed to connect channel `%s`: %s',
\ a:addr,
\ v:exception,
\))
return
endif
endtry
execute printf('sleep %dm', l:retry_interval)
endfor
call denops#_internal#echo#error(printf(
\ 'Failed to connect channel `%s`: %s',
\ a:addr,
\ l:previous_exception,
\))
let l:i += 1
endwhile
endfunction

function! denops#_internal#server#chan#close() abort
" Args:
" options: {
" timeout: number (default: 0)
" }
function! denops#_internal#server#chan#close(options) abort
if s:chan is# v:null
throw '[denops] Channel does not exist yet'
endif
let l:options = extend({
\ 'timeout': 0,
\}, a:options)
let s:closed_on_purpose = 1
call s:rpcclose(s:chan)
let s:chan = v:null
if l:options.timeout ==# 0
return s:force_close(l:options)
endif
if l:options.timeout > 0
let s:force_close_delayer = timer_start(
\ l:options.timeout,
\ { -> s:force_close(l:options) },
\)
endif
call denops#_internal#server#chan#notify('invoke', ['close', []])
endfunction

function! denops#_internal#server#chan#is_connected() abort
Expand All @@ -91,36 +109,56 @@ endfunction

function! s:connect(addr, options) abort
let s:closed_on_purpose = 0
let s:chan = s:rpcconnect(a:addr, {
\ 'on_close': { -> s:on_close(a:options) },
\})
let s:addr = a:addr
let s:options = a:options
let s:chan = s:rpcconnect(a:addr, {
\ 'on_close': { -> s:on_close() },
\})
call denops#_internal#echo#debug(printf('Channel connected (%s)', a:addr))
call s:rpcnotify(s:chan, 'void', [])
endfunction

function! s:on_close(options) abort
function! s:force_close(options) abort
let l:chan = s:chan
let s:chan = v:null
call s:clear_force_close_delayer()
call denops#_internal#echo#warn(printf(
\ 'Channel cannot close gracefully within %d millisec, force close (%s)',
\ a:options.timeout,
\ s:addr,
\))
call s:rpcclose(l:chan)
endfunction

function! s:clear_force_close_delayer() abort
if exists('s:force_close_delayer')
call timer_stop(s:force_close_delayer)
unlet s:force_close_delayer
endif
endfunction

function! s:on_close() abort
let s:chan = v:null
call s:clear_force_close_delayer()
call denops#_internal#echo#debug(printf('Channel closed (%s)', s:addr))
doautocmd <nomodeline> User DenopsClosed
if !a:options.reconnect_on_close || s:closed_on_purpose || s:exiting
doautocmd <nomodeline> User DenopsSystemClosed
if s:chan isnot# v:null || !s:options.reconnect_on_close || s:closed_on_purpose || s:exiting
return
endif
" Reconnect
if s:reconnect_guard(a:options)
if s:reconnect_guard()
return
endif
call denops#_internal#echo#warn('Channel closed. Reconnecting...')
call timer_start(
\ a:options.reconnect_delay,
\ s:options.reconnect_delay,
\ { -> denops#_internal#server#chan#connect(s:addr, s:options) },
\)
endfunction

function! s:reconnect_guard(options) abort
let l:reconnect_threshold = a:options.reconnect_threshold
let l:reconnect_interval = a:options.reconnect_interval
function! s:reconnect_guard() abort
let l:reconnect_threshold = s:options.reconnect_threshold
let l:reconnect_interval = s:options.reconnect_interval
let s:reconnect_count = get(s:, 'reconnect_count', 0) + 1
if s:reconnect_count >= l:reconnect_threshold
call denops#_internal#echo#warn(printf(
Expand All @@ -143,6 +181,6 @@ endfunction
augroup denops_internal_server_chan_internal
autocmd!
autocmd VimLeave * let s:exiting = 1
autocmd User DenopsReady :
autocmd User DenopsClosed :
autocmd User DenopsSystemReady :
autocmd User DenopsSystemClosed :
augroup END
46 changes: 10 additions & 36 deletions autoload/denops/_internal/server/proc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ let s:exiting = 0

" Args:
" options: {
" retry_interval: number
" retry_threshold: number
" restart_on_exit: boolean
" restart_delay: number
" restart_interval: number
Expand All @@ -20,33 +18,9 @@ function! denops#_internal#server#proc#start(options) abort
if s:job isnot# v:null
throw '[denops] Server already exists'
endif
let l:retry_interval = a:options.retry_interval
let l:retry_threshold = a:options.retry_threshold
let l:previous_exception = ''
for l:i in range(l:retry_threshold)
call denops#_internal#echo#debug(printf(
\ 'Spawn server [%d/%d]',
\ l:i + 1,
\ l:retry_threshold + 1,
\))
try
call s:start(a:options)
return v:true
catch
call denops#_internal#echo#debug(printf(
\ 'Failed to spawn server [%d/%d]: %s',
\ l:i + 1,
\ l:retry_threshold + 1,
\ v:exception,
\))
let l:previous_exception = v:exception
endtry
execute printf('sleep %dm', l:retry_interval)
endfor
call denops#_internal#echo#error(printf(
\ 'Failed to spawn server: %s',
\ l:previous_exception,
\))
call denops#_internal#echo#debug('Spawn server')
call s:start(a:options)
return v:true
endfunction

function! denops#_internal#server#proc#stop() abort
Expand Down Expand Up @@ -88,7 +62,7 @@ function! s:start(options) abort
\})
let s:options = a:options
call denops#_internal#echo#debug(printf('Server started: %s', l:args))
doautocmd <nomodeline> User DenopsProcessStarted
doautocmd <nomodeline> User DenopsSystemProcessStarted
endfunction

function! s:on_stdout(store, data) abort
Expand All @@ -101,7 +75,7 @@ function! s:on_stdout(store, data) abort
let a:store.prepared = 1
let l:addr = substitute(a:data, '\r\?\n$', '', 'g')
call denops#_internal#echo#debug(printf('Server listen: %s', l:addr))
execute printf('doautocmd <nomodeline> User DenopsProcessListen:%s', l:addr)
execute printf('doautocmd <nomodeline> User DenopsSystemProcessListen:%s', l:addr)
endfunction

function! s:on_stderr(data) abort
Expand All @@ -115,8 +89,8 @@ endfunction
function! s:on_exit(options, status) abort
let s:job = v:null
call denops#_internal#echo#debug(printf('Server stopped: %s', a:status))
execute printf('doautocmd <nomodeline> User DenopsProcessStopped:%s', a:status)
if !a:options.restart_on_exit || s:stopped_on_purpose || s:exiting
execute printf('doautocmd <nomodeline> User DenopsSystemProcessStopped:%s', a:status)
if s:job isnot# v:null || !a:options.restart_on_exit || s:stopped_on_purpose || s:exiting
return
endif
" Restart
Expand Down Expand Up @@ -158,7 +132,7 @@ endfunction
augroup denops_internal_server_proc_internal
autocmd!
autocmd VimLeave * let s:exiting = 1
autocmd User DenopsProcessStarted :
autocmd User DenopsProcessListen:* :
autocmd User DenopsProcessStopped:* :
autocmd User DenopsSystemProcessStarted :
autocmd User DenopsSystemProcessListen:* :
autocmd User DenopsSystemProcessStopped:* :
augroup END
Loading