mirror of
https://github.com/vim/vim.git
synced 2025-09-25 03:54:15 -04:00
patch 8.1.0350: Vim may block on ch_sendraw()
Problem: Vim may block on ch_sendraw() when the job is sending data back to Vim, which isn't read yet. (Nate Bosch) Solution: Add the "noblock" option to job_start(). (closes #2548)
This commit is contained in:
@@ -163,6 +163,9 @@ Use |ch_status()| to see if the channel could be opened.
|
|||||||
The "close_cb" is also considered for this.
|
The "close_cb" is also considered for this.
|
||||||
"never" All messages will be kept.
|
"never" All messages will be kept.
|
||||||
|
|
||||||
|
*channel-noblock*
|
||||||
|
"noblock" Same effect as |job-noblock|. Only matters for writing.
|
||||||
|
|
||||||
*waittime*
|
*waittime*
|
||||||
"waittime" The time to wait for the connection to be made in
|
"waittime" The time to wait for the connection to be made in
|
||||||
milliseconds. A negative number waits forever.
|
milliseconds. A negative number waits forever.
|
||||||
@@ -594,6 +597,17 @@ See |job_setoptions()| and |ch_setoptions()|.
|
|||||||
Note: when writing to a file or buffer and when
|
Note: when writing to a file or buffer and when
|
||||||
reading from a buffer NL mode is used by default.
|
reading from a buffer NL mode is used by default.
|
||||||
|
|
||||||
|
*job-noblock*
|
||||||
|
"noblock": 1 When writing use a non-blocking write call. This
|
||||||
|
avoids getting stuck if Vim should handle other
|
||||||
|
messages in between, e.g. when a job sends back data
|
||||||
|
to Vim. It implies that when `ch_sendraw()` returns
|
||||||
|
not all data may have been written yet.
|
||||||
|
This option was added in patch 8.1.0350, test with: >
|
||||||
|
if has("patch-8.1.350")
|
||||||
|
let options['noblock'] = 1
|
||||||
|
endif
|
||||||
|
<
|
||||||
*job-callback*
|
*job-callback*
|
||||||
"callback": handler Callback for something to read on any part of the
|
"callback": handler Callback for something to read on any part of the
|
||||||
channel.
|
channel.
|
||||||
|
@@ -1180,6 +1180,7 @@ channel_set_options(channel_T *channel, jobopt_T *opt)
|
|||||||
channel->ch_part[PART_OUT].ch_mode = opt->jo_out_mode;
|
channel->ch_part[PART_OUT].ch_mode = opt->jo_out_mode;
|
||||||
if (opt->jo_set & JO_ERR_MODE)
|
if (opt->jo_set & JO_ERR_MODE)
|
||||||
channel->ch_part[PART_ERR].ch_mode = opt->jo_err_mode;
|
channel->ch_part[PART_ERR].ch_mode = opt->jo_err_mode;
|
||||||
|
channel->ch_nonblock = opt->jo_noblock;
|
||||||
|
|
||||||
if (opt->jo_set & JO_TIMEOUT)
|
if (opt->jo_set & JO_TIMEOUT)
|
||||||
for (part = PART_SOCK; part < PART_COUNT; ++part)
|
for (part = PART_SOCK; part < PART_COUNT; ++part)
|
||||||
@@ -3722,6 +3723,9 @@ channel_send(
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel->ch_nonblock && !ch_part->ch_nonblocking)
|
||||||
|
channel_set_nonblock(channel, part);
|
||||||
|
|
||||||
if (ch_log_active())
|
if (ch_log_active())
|
||||||
{
|
{
|
||||||
ch_log_lead("SEND ", channel, part);
|
ch_log_lead("SEND ", channel, part);
|
||||||
@@ -4553,6 +4557,12 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported, int supported2)
|
|||||||
== FAIL)
|
== FAIL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
else if (STRCMP(hi->hi_key, "noblock") == 0)
|
||||||
|
{
|
||||||
|
if (!(supported & JO_MODE))
|
||||||
|
break;
|
||||||
|
opt->jo_noblock = get_tv_number(item);
|
||||||
|
}
|
||||||
else if (STRCMP(hi->hi_key, "in_io") == 0
|
else if (STRCMP(hi->hi_key, "in_io") == 0
|
||||||
|| STRCMP(hi->hi_key, "out_io") == 0
|
|| STRCMP(hi->hi_key, "out_io") == 0
|
||||||
|| STRCMP(hi->hi_key, "err_io") == 0)
|
|| STRCMP(hi->hi_key, "err_io") == 0)
|
||||||
|
@@ -1651,6 +1651,7 @@ struct channel_S {
|
|||||||
partial_T *ch_close_partial;
|
partial_T *ch_close_partial;
|
||||||
int ch_drop_never;
|
int ch_drop_never;
|
||||||
int ch_keep_open; /* do not close on read error */
|
int ch_keep_open; /* do not close on read error */
|
||||||
|
int ch_nonblock;
|
||||||
|
|
||||||
job_T *ch_job; /* Job that uses this channel; this does not
|
job_T *ch_job; /* Job that uses this channel; this does not
|
||||||
* count as a reference to avoid a circular
|
* count as a reference to avoid a circular
|
||||||
@@ -1729,6 +1730,7 @@ typedef struct
|
|||||||
ch_mode_T jo_in_mode;
|
ch_mode_T jo_in_mode;
|
||||||
ch_mode_T jo_out_mode;
|
ch_mode_T jo_out_mode;
|
||||||
ch_mode_T jo_err_mode;
|
ch_mode_T jo_err_mode;
|
||||||
|
int jo_noblock;
|
||||||
|
|
||||||
job_io_T jo_io[4]; /* PART_OUT, PART_ERR, PART_IN */
|
job_io_T jo_io[4]; /* PART_OUT, PART_ERR, PART_IN */
|
||||||
char_u jo_io_name_buf[4][NUMBUFLEN];
|
char_u jo_io_name_buf[4][NUMBUFLEN];
|
||||||
|
@@ -47,8 +47,11 @@ endfunc
|
|||||||
func Ch_communicate(port)
|
func Ch_communicate(port)
|
||||||
" Avoid dropping messages, since we don't use a callback here.
|
" Avoid dropping messages, since we don't use a callback here.
|
||||||
let s:chopt.drop = 'never'
|
let s:chopt.drop = 'never'
|
||||||
|
" Also add the noblock flag to try it out.
|
||||||
|
let s:chopt.noblock = 1
|
||||||
let handle = ch_open('localhost:' . a:port, s:chopt)
|
let handle = ch_open('localhost:' . a:port, s:chopt)
|
||||||
unlet s:chopt.drop
|
unlet s:chopt.drop
|
||||||
|
unlet s:chopt.noblock
|
||||||
if ch_status(handle) == "fail"
|
if ch_status(handle) == "fail"
|
||||||
call assert_report("Can't open channel")
|
call assert_report("Can't open channel")
|
||||||
return
|
return
|
||||||
@@ -451,8 +454,9 @@ func Test_raw_pipe()
|
|||||||
call ch_log('Test_raw_pipe()')
|
call ch_log('Test_raw_pipe()')
|
||||||
" Add a dummy close callback to avoid that messages are dropped when calling
|
" Add a dummy close callback to avoid that messages are dropped when calling
|
||||||
" ch_canread().
|
" ch_canread().
|
||||||
|
" Also test the non-blocking option.
|
||||||
let job = job_start(s:python . " test_channel_pipe.py",
|
let job = job_start(s:python . " test_channel_pipe.py",
|
||||||
\ {'mode': 'raw', 'drop': 'never'})
|
\ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
|
||||||
call assert_equal(v:t_job, type(job))
|
call assert_equal(v:t_job, type(job))
|
||||||
call assert_equal("run", job_status(job))
|
call assert_equal("run", job_status(job))
|
||||||
|
|
||||||
@@ -1350,6 +1354,34 @@ endfunc
|
|||||||
|
|
||||||
""""""""""
|
""""""""""
|
||||||
|
|
||||||
|
function ExitCbWipe(job, status)
|
||||||
|
exe g:wipe_buf 'bw!'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" This caused a crash, because messages were handled while peeking for a
|
||||||
|
" character.
|
||||||
|
func Test_exit_cb_wipes_buf()
|
||||||
|
if !has('timers')
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
set cursorline lazyredraw
|
||||||
|
call test_override('redraw_flag', 1)
|
||||||
|
new
|
||||||
|
let g:wipe_buf = bufnr('')
|
||||||
|
|
||||||
|
let job = job_start(['true'], {'exit_cb': 'ExitCbWipe'})
|
||||||
|
let timer = timer_start(300, {-> feedkeys("\<Esc>", 'nt')}, {'repeat': 5})
|
||||||
|
call feedkeys(repeat('g', 1000) . 'o', 'ntx!')
|
||||||
|
call WaitForAssert({-> assert_equal("dead", job_status(job))})
|
||||||
|
call timer_stop(timer)
|
||||||
|
|
||||||
|
set nocursorline nolazyredraw
|
||||||
|
unlet g:wipe_buf
|
||||||
|
call test_override('ALL', 0)
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
""""""""""
|
||||||
|
|
||||||
let g:Ch_unletResponse = ''
|
let g:Ch_unletResponse = ''
|
||||||
func s:UnletHandler(handle, msg)
|
func s:UnletHandler(handle, msg)
|
||||||
let g:Ch_unletResponse = a:msg
|
let g:Ch_unletResponse = a:msg
|
||||||
|
@@ -794,6 +794,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
350,
|
||||||
/**/
|
/**/
|
||||||
349,
|
349,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user