diff --git a/src/channel.c b/src/channel.c index c06a107c5b..a2e9d2c317 100644 --- a/src/channel.c +++ b/src/channel.c @@ -1601,7 +1601,7 @@ channel_wait(channel_T *channel, sock_T fd, int timeout) && nread > 0) return OK; diff = deadline - GetTickCount(); - if (diff < 0) + if (diff <= 0) break; /* Wait for 5 msec. * TODO: increase the sleep time when looping more often */ @@ -1900,17 +1900,19 @@ channel_fd2channel(sock_T fd, int *partp) } return NULL; } +# endif +# if defined(WIN32) || defined(PROTO) +/* + * Check the channels for anything that is ready to be read. + * The data is put in the read queue. + */ void channel_handle_events(void) { channel_T *channel; int part; - static int loop = 0; - - /* Skip heavily polling */ - if (loop++ % 2) - return; + sock_T fd; for (channel = first_channel; channel != NULL; channel = channel->ch_next) { @@ -1926,7 +1928,11 @@ channel_handle_events(void) part = PART_SOCK; # endif # endif - channel_read(channel, part, "channel_handle_events"); + { + fd = channel->ch_part[part].ch_fd; + if (fd != INVALID_FD && channel_wait(channel, fd, 0) == OK) + channel_read(channel, part, "channel_handle_events"); + } } } # endif diff --git a/src/eval.c b/src/eval.c index ce135f4807..03119eaec1 100644 --- a/src/eval.c +++ b/src/eval.c @@ -451,9 +451,6 @@ static dict_T *dict_copy(dict_T *orig, int deep, int copyID); static long dict_len(dict_T *d); static char_u *dict2string(typval_T *tv, int copyID); static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate); -#ifdef FEAT_JOB -static void job_free(job_T *job); -#endif static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); static char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); static char_u *string_quote(char_u *str, int function); @@ -633,6 +630,7 @@ static void f_items(typval_T *argvars, typval_T *rettv); # ifdef FEAT_CHANNEL static void f_job_getchannel(typval_T *argvars, typval_T *rettv); # endif +static void f_job_setoptions(typval_T *argvars, typval_T *rettv); static void f_job_start(typval_T *argvars, typval_T *rettv); static void f_job_stop(typval_T *argvars, typval_T *rettv); static void f_job_status(typval_T *argvars, typval_T *rettv); @@ -7751,7 +7749,9 @@ channel_unref(channel_T *channel) } #endif -#ifdef FEAT_JOB +#if defined(FEAT_JOB) || defined(PROTO) +static job_T *first_job = NULL; + static void job_free(job_T *job) { @@ -7765,6 +7765,15 @@ job_free(job_T *job) } # endif mch_clear_job(job); + + if (job->jv_next != NULL) + job->jv_next->jv_prev = job->jv_prev; + if (job->jv_prev == NULL) + first_job = job->jv_next; + else + job->jv_prev->jv_next = job->jv_next; + + vim_free(job->jv_stoponexit); vim_free(job); } @@ -7776,7 +7785,7 @@ job_unref(job_T *job) } /* - * Allocate a job. Sets the refcount to one. + * Allocate a job. Sets the refcount to one and sets options default. */ static job_T * job_alloc(void) @@ -7785,10 +7794,45 @@ job_alloc(void) job = (job_T *)alloc_clear(sizeof(job_T)); if (job != NULL) + { job->jv_refcount = 1; + job->jv_stoponexit = vim_strsave((char_u *)"term"); + + if (first_job != NULL) + { + first_job->jv_prev = job; + job->jv_next = first_job; + } + first_job = job; + } return job; } + static void +job_set_options(job_T *job, jobopt_T *opt) +{ + if (opt->jo_set & JO_STOPONEXIT) + { + vim_free(job->jv_stoponexit); + if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL) + job->jv_stoponexit = NULL; + else + job->jv_stoponexit = vim_strsave(opt->jo_stoponexit); + } +} + +/* + * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag. + */ + void +job_stop_on_exit() +{ + job_T *job; + + for (job = first_job; job != NULL; job = job->jv_next) + if (job->jv_stoponexit != NULL && *job->jv_stoponexit != NUL) + mch_stop_job(job, job->jv_stoponexit); +} #endif static char * @@ -7797,9 +7841,9 @@ get_var_special_name(int nr) switch (nr) { case VVAL_FALSE: return "v:false"; - case VVAL_TRUE: return "v:true"; - case VVAL_NONE: return "v:none"; - case VVAL_NULL: return "v:null"; + case VVAL_TRUE: return "v:true"; + case VVAL_NONE: return "v:none"; + case VVAL_NULL: return "v:null"; } EMSG2(_(e_intern2), "get_var_special_name()"); return "42"; @@ -8260,6 +8304,7 @@ static struct fst # ifdef FEAT_CHANNEL {"job_getchannel", 1, 1, f_job_getchannel}, # endif + {"job_setoptions", 2, 2, f_job_setoptions}, {"job_start", 1, 2, f_job_start}, {"job_status", 1, 1, f_job_status}, {"job_stop", 1, 2, f_job_stop}, @@ -10050,6 +10095,19 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported) opt->jo_set |= JO_ID; opt->jo_id = get_tv_number(item); } + else if (STRCMP(hi->hi_key, "stoponexit") == 0) + { + if (!(supported & JO_STOPONEXIT)) + break; + opt->jo_set |= JO_STOPONEXIT; + opt->jo_stoponexit = get_tv_string_buf_chk(item, + opt->jo_soe_buf); + if (opt->jo_stoponexit == NULL) + { + EMSG2(_(e_invarg2), "stoponexit"); + return FAIL; + } + } else break; --todo; @@ -14733,6 +14791,26 @@ f_items(typval_T *argvars, typval_T *rettv) } #ifdef FEAT_JOB +/* + * Get the job from the argument. + * Returns NULL if the job is invalid. + */ + static job_T * +get_job_arg(typval_T *tv) +{ + job_T *job; + + if (tv->v_type != VAR_JOB) + { + EMSG2(_(e_invarg2), get_tv_string(tv)); + return NULL; + } + job = tv->vval.v_job; + + if (job == NULL) + EMSG(_("E916: not a valid job")); + return job; +} # ifdef FEAT_CHANNEL /* @@ -14741,12 +14819,10 @@ f_items(typval_T *argvars, typval_T *rettv) static void f_job_getchannel(typval_T *argvars, typval_T *rettv) { - if (argvars[0].v_type != VAR_JOB) - EMSG(_(e_invarg)); - else - { - job_T *job = argvars[0].vval.v_job; + job_T *job = get_job_arg(&argvars[0]); + if (job != NULL) + { rettv->v_type = VAR_CHANNEL; rettv->vval.v_channel = job->jv_channel; if (job->jv_channel != NULL) @@ -14755,6 +14831,23 @@ f_job_getchannel(typval_T *argvars, typval_T *rettv) } # endif +/* + * "job_setoptions()" function + */ + static void +f_job_setoptions(typval_T *argvars, typval_T *rettv UNUSED) +{ + job_T *job = get_job_arg(&argvars[0]); + jobopt_T opt; + + if (job == NULL) + return; + clear_job_options(&opt); + if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT) == FAIL) + return; + job_set_options(job, &opt); +} + /* * "job_start()" function */ @@ -14784,8 +14877,9 @@ f_job_start(typval_T *argvars UNUSED, typval_T *rettv) clear_job_options(&opt); opt.jo_mode = MODE_NL; if (get_job_options(&argvars[1], &opt, - JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL) == FAIL) + JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT) == FAIL) return; + job_set_options(job, &opt); #ifndef USE_ARGV ga_init2(&ga, (int)sizeof(char*), 20); @@ -14889,14 +14983,11 @@ theend: static void f_job_status(typval_T *argvars, typval_T *rettv) { - char *result; + job_T *job = get_job_arg(&argvars[0]); + char *result; - if (argvars[0].v_type != VAR_JOB) - EMSG(_(e_invarg)); - else + if (job != NULL) { - job_T *job = argvars[0].vval.v_job; - if (job->jv_status == JOB_ENDED) /* No need to check, dead is dead. */ result = "dead"; @@ -14915,9 +15006,9 @@ f_job_status(typval_T *argvars, typval_T *rettv) static void f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { - if (argvars[0].v_type != VAR_JOB) - EMSG(_(e_invarg)); - else + job_T *job = get_job_arg(&argvars[0]); + + if (job != NULL) { char_u *arg; @@ -14932,7 +15023,7 @@ f_job_stop(typval_T *argvars UNUSED, typval_T *rettv UNUSED) return; } } - if (mch_stop_job(argvars[0].vval.v_job, arg) == FAIL) + if (mch_stop_job(job, arg) == FAIL) rettv->vval.v_number = 0; else rettv->vval.v_number = 1; diff --git a/src/gui_w32.c b/src/gui_w32.c index f3da79425e..3a64691493 100644 --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -2248,10 +2248,6 @@ gui_mch_wait_for_chars(int wtime) parse_queued_messages(); #endif -#ifdef FEAT_CHANNEL - channel_handle_events(); -#endif - /* * Don't use gui_mch_update() because then we will spin-lock until a * char arrives, instead we use GetMessage() to hang until an diff --git a/src/main.c b/src/main.c index b80053dac4..457bb690a9 100644 --- a/src/main.c +++ b/src/main.c @@ -1556,6 +1556,9 @@ getout(int exitval) windgoto((int)Rows - 1, 0); #endif +#ifdef FEAT_JOB + job_stop_on_exit(); +#endif #ifdef FEAT_LUA lua_end(); #endif diff --git a/src/misc2.c b/src/misc2.c index 953bd9ec45..c4700fdd72 100644 --- a/src/misc2.c +++ b/src/misc2.c @@ -6245,6 +6245,11 @@ has_non_ascii(char_u *s) void parse_queued_messages(void) { + /* For Win32 mch_breakcheck() does not check for input, do it here. */ +# if defined(WIN32) && defined(FEAT_CHANNEL) + channel_handle_events(); +# endif + # ifdef FEAT_NETBEANS_INTG /* Process the queued netbeans messages. */ netbeans_parse_messages(); diff --git a/src/os_win32.c b/src/os_win32.c index ae1f0d8afc..631f88e495 100644 --- a/src/os_win32.c +++ b/src/os_win32.c @@ -1470,10 +1470,6 @@ WaitForChar(long msec) serverProcessPendingMessages(); #endif -#ifdef FEAT_CHANNEL - channel_handle_events(); -#endif - if (0 #ifdef FEAT_MOUSE || g_nMouseClick != -1 diff --git a/src/proto/eval.pro b/src/proto/eval.pro index e63205e11c..780b40c532 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -80,6 +80,7 @@ dictitem_T *dict_find(dict_T *d, char_u *key, int len); char_u *get_dict_string(dict_T *d, char_u *key, int save); long get_dict_number(dict_T *d, char_u *key); int channel_unref(channel_T *channel); +void job_stop_on_exit(void); int string2float(char_u *text, float_T *value); char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); diff --git a/src/structs.h b/src/structs.h index 376a5dc505..9af7b0d8d1 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1253,6 +1253,8 @@ typedef enum */ struct jobvar_S { + job_T *jv_next; + job_T *jv_prev; #ifdef UNIX pid_t jv_pid; int jv_exitval; @@ -1262,6 +1264,7 @@ struct jobvar_S HANDLE jv_job_object; #endif jobstatus_T jv_status; + char_u *jv_stoponexit; /* allocated */ int jv_refcount; /* reference count */ channel_T *jv_channel; /* channel for I/O, reference counted */ @@ -1389,6 +1392,7 @@ struct channel_S { #define JO_ERR_TIMEOUT 0x0400 /* stderr timeouts */ #define JO_PART 0x0800 /* "part" */ #define JO_ID 0x1000 /* "id" */ +#define JO_STOPONEXIT 0x2000 /* "stoponexit" */ #define JO_ALL 0xffffff #define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE) @@ -1415,6 +1419,8 @@ typedef struct int jo_err_timeout; int jo_part; int jo_id; + char_u jo_soe_buf[NUMBUFLEN]; + char_u *jo_stoponexit; } jobopt_T; diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index 76c7c4ee2e..61ca9b7321 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -44,7 +44,8 @@ func s:run_server(testfunc, ...) try if has('job') - let s:job = job_start(cmd) + let s:job = job_start(cmd, {"stoponexit": "hup"}) + call job_setoptions(s:job, {"stoponexit": "kill"}) elseif has('win32') exe 'silent !start cmd /c start "test_channel" ' . cmd else @@ -304,16 +305,26 @@ func Test_connect_waittime() call assert_true(reltimefloat(elapsed) < 1.0) endif + " We intend to use a socket that doesn't exist and wait for half a second + " before giving up. If the socket does exist it can fail in various ways. + " Check for "Connection reset by peer" to avoid flakyness. let start = reltime() - let handle = ch_open('localhost:9867', {'waittime': 500}) - if ch_status(handle) != "fail" - " Oops, port does exists. - call ch_close(handle) - else - " Failed connection should wait about 500 msec. - let elapsed = reltime(start) - call assert_true(reltimefloat(elapsed) < 1.0) - endif + try + let handle = ch_open('localhost:9867', {'waittime': 500}) + if ch_status(handle) != "fail" + " Oops, port does exists. + call ch_close(handle) + else + " Failed connection should wait about 500 msec. + let elapsed = reltime(start) + call assert_true(reltimefloat(elapsed) > 0.3) + call assert_true(reltimefloat(elapsed) < 1.0) + endif + catch + if v:exception !~ 'Connection reset by peer' + call assert_false(1, "Caught exception: " . v:exception) + endif + endtry endfunc func Test_raw_pipe() diff --git a/src/version.c b/src/version.c index 21db9f51cb..b9b5ac5cdc 100644 --- a/src/version.c +++ b/src/version.c @@ -762,6 +762,12 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1379, +/**/ + 1378, +/**/ + 1377, /**/ 1376, /**/