Skip to content

Commit 4e9d443

Browse files
committed
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)
1 parent 4994373 commit 4e9d443

4 files changed

Lines changed: 59 additions & 15 deletions

File tree

src/channel.c

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,15 @@ channel_still_useful(channel_T *channel)
344344
&& has_err_msg);
345345
}
346346

347+
/*
348+
* Return TRUE if "channel" is closeable (i.e. all readable fds are closed).
349+
*/
350+
static int
351+
channel_can_close(channel_T *channel)
352+
{
353+
return channel->ch_to_be_closed == 0;
354+
}
355+
347356
/*
348357
* Close a channel and free all its resources.
349358
*/
@@ -892,7 +901,7 @@ channel_open(
892901
channel->ch_nb_close_cb = nb_close_cb;
893902
channel->ch_hostname = (char *)vim_strsave((char_u *)hostname);
894903
channel->ch_port = port_in;
895-
channel->ch_to_be_closed |= (1 << PART_SOCK);
904+
channel->ch_to_be_closed |= (1U << PART_SOCK);
896905

897906
#ifdef FEAT_GUI
898907
channel_gui_register_one(channel, PART_SOCK);
@@ -988,7 +997,8 @@ ch_close_part(channel_T *channel, ch_part_T part)
988997
}
989998
*fd = INVALID_FD;
990999

991-
channel->ch_to_be_closed &= ~(1 << part);
1000+
/* channel is closed, may want to end the job if it was the last */
1001+
channel->ch_to_be_closed &= ~(1U << part);
9921002
}
9931003
}
9941004

@@ -999,6 +1009,12 @@ channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err)
9991009
{
10001010
ch_close_part(channel, PART_IN);
10011011
channel->CH_IN_FD = in;
1012+
# if defined(UNIX)
1013+
/* Do not end the job when all output channels are closed, wait until
1014+
* the job ended. */
1015+
if (isatty(in))
1016+
channel->ch_to_be_closed |= (1U << PART_IN);
1017+
# endif
10021018
}
10031019
if (out != INVALID_FD)
10041020
{
@@ -1007,7 +1023,7 @@ channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err)
10071023
# endif
10081024
ch_close_part(channel, PART_OUT);
10091025
channel->CH_OUT_FD = out;
1010-
channel->ch_to_be_closed |= (1 << PART_OUT);
1026+
channel->ch_to_be_closed |= (1U << PART_OUT);
10111027
# if defined(FEAT_GUI)
10121028
channel_gui_register_one(channel, PART_OUT);
10131029
# endif
@@ -1019,7 +1035,7 @@ channel_set_pipes(channel_T *channel, sock_T in, sock_T out, sock_T err)
10191035
# endif
10201036
ch_close_part(channel, PART_ERR);
10211037
channel->CH_ERR_FD = err;
1022-
channel->ch_to_be_closed |= (1 << PART_ERR);
1038+
channel->ch_to_be_closed |= (1U << PART_ERR);
10231039
# if defined(FEAT_GUI)
10241040
channel_gui_register_one(channel, PART_ERR);
10251041
# endif
@@ -4200,9 +4216,9 @@ channel_parse_messages(void)
42004216
}
42014217
while (channel != NULL)
42024218
{
4203-
if (channel->ch_to_be_closed == 0)
4219+
if (channel_can_close(channel))
42044220
{
4205-
channel->ch_to_be_closed = (1 << PART_COUNT);
4221+
channel->ch_to_be_closed = (1U << PART_COUNT);
42064222
channel_close_now(channel);
42074223
/* channel may have been freed, start over */
42084224
channel = first_channel;
@@ -5079,6 +5095,15 @@ job_channel_still_useful(job_T *job)
50795095
return job->jv_channel != NULL && channel_still_useful(job->jv_channel);
50805096
}
50815097

5098+
/*
5099+
* Return TRUE if the channel of "job" is closeable.
5100+
*/
5101+
static int
5102+
job_channel_can_close(job_T *job)
5103+
{
5104+
return job->jv_channel != NULL && channel_can_close(job->jv_channel);
5105+
}
5106+
50825107
/*
50835108
* Return TRUE if the job should not be freed yet. Do not free the job when
50845109
* it has not ended yet and there is a "stoponexit" flag, an exit callback
@@ -5209,6 +5234,10 @@ job_cleanup(job_T *job)
52095234
/* Ready to cleanup the job. */
52105235
job->jv_status = JOB_FINISHED;
52115236

5237+
/* When only channel-in is kept open, close explicitly. */
5238+
if (job->jv_channel != NULL)
5239+
ch_close_part(job->jv_channel, PART_IN);
5240+
52125241
if (job->jv_exit_cb != NULL)
52135242
{
52145243
typval_T argv[3];
@@ -5413,8 +5442,9 @@ has_pending_job(void)
54135442
for (job = first_job; job != NULL; job = job->jv_next)
54145443
/* Only should check if the channel has been closed, if the channel is
54155444
* open the job won't exit. */
5416-
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL
5417-
&& !job_channel_still_useful(job))
5445+
if ((job->jv_status == JOB_STARTED && !job_channel_still_useful(job))
5446+
|| (job->jv_status == JOB_FINISHED
5447+
&& job_channel_can_close(job)))
54185448
return TRUE;
54195449
return FALSE;
54205450
}

src/os_unix.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5638,13 +5638,14 @@ mch_job_start(char **argv, job_T *job, jobopt_T *options)
56385638
close(fd_err[1]);
56395639
if (channel != NULL)
56405640
{
5641-
channel_set_pipes(channel,
5642-
use_file_for_in || use_null_for_in
5643-
? INVALID_FD : fd_in[1] < 0 ? pty_master_fd : fd_in[1],
5644-
use_file_for_out || use_null_for_out
5645-
? INVALID_FD : fd_out[0] < 0 ? pty_master_fd : fd_out[0],
5646-
use_out_for_err || use_file_for_err || use_null_for_err
5647-
? INVALID_FD : fd_err[0] < 0 ? pty_master_fd : fd_err[0]);
5641+
int in_fd = use_file_for_in || use_null_for_in
5642+
? INVALID_FD : fd_in[1] < 0 ? pty_master_fd : fd_in[1];
5643+
int out_fd = use_file_for_out || use_null_for_out
5644+
? INVALID_FD : fd_out[0] < 0 ? pty_master_fd : fd_out[0];
5645+
int err_fd = use_out_for_err || use_file_for_err || use_null_for_err
5646+
? INVALID_FD : fd_err[0] < 0 ? pty_master_fd : fd_err[0];
5647+
5648+
channel_set_pipes(channel, in_fd, out_fd, err_fd);
56485649
channel_set_job(channel, job, options);
56495650
}
56505651
else

src/testdir/test_channel.vim

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,3 +1848,14 @@ func Test_zz_ch_log()
18481848
call assert_match("%s%s", text[2])
18491849
call delete('Xlog')
18501850
endfunc
1851+
1852+
func Test_keep_pty_open()
1853+
if !has('unix')
1854+
return
1855+
endif
1856+
1857+
let job = job_start(s:python . ' -c "import time;time.sleep(0.2)"', {'out_io': 'null', 'err_io': 'null', 'pty': 1})
1858+
let elapsed = WaitFor({-> job_status(job) ==# 'dead'})
1859+
call assert_inrange(200, 1000, elapsed)
1860+
call job_stop(job)
1861+
endfunc

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,8 @@ static char *(features[]) =
761761

762762
static int included_patches[] =
763763
{ /* Add new patch number below this line */
764+
/**/
765+
1761,
764766
/**/
765767
1760,
766768
/**/

0 commit comments

Comments
 (0)