# HG changeset patch # User Christian Brabandt # Date 1524596407 -7200 # Node ID 5f6c61a71c029381dcc3c6775063ea19bf0c721b # Parent bdecc420bd2cdc0be97b45b8b67263e382276613 patch 8.0.1761: job in terminal window with no output channel is killed commit https://github.com/vim/vim/commit/4e9d443a25b451e3f2de62e9eeea439aa4c3f039 Author: Bram Moolenaar Date: Tue Apr 24 20:54:07 2018 +0200 patch 8.0.1761: job in terminal window with no output channel is killed Problem: Job in terminal window with no output channel is killed. Solution: Keep the job running when the input is a tty. (Ozaki Kiichi, closes #2734) diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -345,6 +345,15 @@ channel_still_useful(channel_T *channel) } /* + * Return TRUE if "channel" is closeable (i.e. all readable fds are closed). + */ + static int +channel_can_close(channel_T *channel) +{ + return channel->ch_to_be_closed == 0; +} + +/* * Close a channel and free all its resources. */ static void @@ -892,7 +901,7 @@ channel_open( channel->ch_nb_close_cb = nb_close_cb; channel->ch_hostname = (char *)vim_strsave((char_u *)hostname); channel->ch_port = port_in; - channel->ch_to_be_closed |= (1 << PART_SOCK); + channel->ch_to_be_closed |= (1U << PART_SOCK); #ifdef FEAT_GUI channel_gui_register_one(channel, PART_SOCK); @@ -988,7 +997,8 @@ ch_close_part(channel_T *channel, ch_par } *fd = INVALID_FD; - channel->ch_to_be_closed &= ~(1 << part); + /* channel is closed, may want to end the job if it was the last */ + channel->ch_to_be_closed &= ~(1U << part); } } @@ -999,6 +1009,12 @@ channel_set_pipes(channel_T *channel, so { ch_close_part(channel, PART_IN); channel->CH_IN_FD = in; +# if defined(UNIX) + /* Do not end the job when all output channels are closed, wait until + * the job ended. */ + if (isatty(in)) + channel->ch_to_be_closed |= (1U << PART_IN); +# endif } if (out != INVALID_FD) { @@ -1007,7 +1023,7 @@ channel_set_pipes(channel_T *channel, so # endif ch_close_part(channel, PART_OUT); channel->CH_OUT_FD = out; - channel->ch_to_be_closed |= (1 << PART_OUT); + channel->ch_to_be_closed |= (1U << PART_OUT); # if defined(FEAT_GUI) channel_gui_register_one(channel, PART_OUT); # endif @@ -1019,7 +1035,7 @@ channel_set_pipes(channel_T *channel, so # endif ch_close_part(channel, PART_ERR); channel->CH_ERR_FD = err; - channel->ch_to_be_closed |= (1 << PART_ERR); + channel->ch_to_be_closed |= (1U << PART_ERR); # if defined(FEAT_GUI) channel_gui_register_one(channel, PART_ERR); # endif @@ -4200,9 +4216,9 @@ channel_parse_messages(void) } while (channel != NULL) { - if (channel->ch_to_be_closed == 0) + if (channel_can_close(channel)) { - channel->ch_to_be_closed = (1 << PART_COUNT); + channel->ch_to_be_closed = (1U << PART_COUNT); channel_close_now(channel); /* channel may have been freed, start over */ channel = first_channel; @@ -5080,6 +5096,15 @@ job_channel_still_useful(job_T *job) } /* + * Return TRUE if the channel of "job" is closeable. + */ + static int +job_channel_can_close(job_T *job) +{ + return job->jv_channel != NULL && channel_can_close(job->jv_channel); +} + +/* * Return TRUE if the job should not be freed yet. Do not free the job when * it has not ended yet and there is a "stoponexit" flag, an exit callback * or when the associated channel will do something with the job output. @@ -5209,6 +5234,10 @@ job_cleanup(job_T *job) /* Ready to cleanup the job. */ job->jv_status = JOB_FINISHED; + /* When only channel-in is kept open, close explicitly. */ + if (job->jv_channel != NULL) + ch_close_part(job->jv_channel, PART_IN); + if (job->jv_exit_cb != NULL) { typval_T argv[3]; @@ -5413,8 +5442,9 @@ has_pending_job(void) for (job = first_job; job != NULL; job = job->jv_next) /* Only should check if the channel has been closed, if the channel is * open the job won't exit. */ - if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL - && !job_channel_still_useful(job)) + if ((job->jv_status == JOB_STARTED && !job_channel_still_useful(job)) + || (job->jv_status == JOB_FINISHED + && job_channel_can_close(job))) return TRUE; return FALSE; } diff --git a/src/os_unix.c b/src/os_unix.c --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5638,13 +5638,14 @@ mch_job_start(char **argv, job_T *job, j close(fd_err[1]); if (channel != NULL) { - channel_set_pipes(channel, - use_file_for_in || use_null_for_in - ? INVALID_FD : fd_in[1] < 0 ? pty_master_fd : fd_in[1], - use_file_for_out || use_null_for_out - ? INVALID_FD : fd_out[0] < 0 ? pty_master_fd : fd_out[0], - use_out_for_err || use_file_for_err || use_null_for_err - ? INVALID_FD : fd_err[0] < 0 ? pty_master_fd : fd_err[0]); + int in_fd = use_file_for_in || use_null_for_in + ? INVALID_FD : fd_in[1] < 0 ? pty_master_fd : fd_in[1]; + int out_fd = use_file_for_out || use_null_for_out + ? INVALID_FD : fd_out[0] < 0 ? pty_master_fd : fd_out[0]; + int err_fd = use_out_for_err || use_file_for_err || use_null_for_err + ? INVALID_FD : fd_err[0] < 0 ? pty_master_fd : fd_err[0]; + + channel_set_pipes(channel, in_fd, out_fd, err_fd); channel_set_job(channel, job, options); } else diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -1848,3 +1848,14 @@ func Test_zz_ch_log() call assert_match("%s%s", text[2]) call delete('Xlog') endfunc + +func Test_keep_pty_open() + if !has('unix') + return + endif + + let job = job_start(s:python . ' -c "import time;time.sleep(0.2)"', {'out_io': 'null', 'err_io': 'null', 'pty': 1}) + let elapsed = WaitFor({-> job_status(job) ==# 'dead'}) + call assert_inrange(200, 1000, elapsed) + call job_stop(job) +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -762,6 +762,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1761, +/**/ 1760, /**/ 1759,