Skip to content

Commit ee1cffc

Browse files
committed
patch 7.4.1380
Problem: The job exit callback is not implemented. Solution: Add the "exit-cb" option.
1 parent b7522a2 commit ee1cffc

File tree

8 files changed

+147
-15
lines changed

8 files changed

+147
-15
lines changed

src/channel.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,8 @@ invoke_callback(channel_T *channel, char_u *callback, typval_T *argv)
833833

834834
call_func(callback, (int)STRLEN(callback),
835835
&rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL);
836+
clear_tv(&rettv);
837+
836838
/* If an echo command was used the cursor needs to be put back where
837839
* it belongs. */
838840
setcursor();

src/eval.c

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7774,14 +7774,21 @@ job_free(job_T *job)
77747774
job->jv_prev->jv_next = job->jv_next;
77757775

77767776
vim_free(job->jv_stoponexit);
7777+
vim_free(job->jv_exit_cb);
77777778
vim_free(job);
77787779
}
77797780

77807781
static void
77817782
job_unref(job_T *job)
77827783
{
77837784
if (job != NULL && --job->jv_refcount <= 0)
7784-
job_free(job);
7785+
{
7786+
/* Do not free the job when it has not ended yet and there is a
7787+
* "stoponexit" flag or an exit callback. */
7788+
if (job->jv_status != JOB_STARTED
7789+
|| (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL))
7790+
job_free(job);
7791+
}
77857792
}
77867793

77877794
/*
@@ -7819,6 +7826,14 @@ job_set_options(job_T *job, jobopt_T *opt)
78197826
else
78207827
job->jv_stoponexit = vim_strsave(opt->jo_stoponexit);
78217828
}
7829+
if (opt->jo_set & JO_EXIT_CB)
7830+
{
7831+
vim_free(job->jv_exit_cb);
7832+
if (opt->jo_exit_cb == NULL || *opt->jo_exit_cb == NUL)
7833+
job->jv_exit_cb = NULL;
7834+
else
7835+
job->jv_exit_cb = vim_strsave(opt->jo_exit_cb);
7836+
}
78227837
}
78237838

78247839
/*
@@ -7830,7 +7845,7 @@ job_stop_on_exit()
78307845
job_T *job;
78317846

78327847
for (job = first_job; job != NULL; job = job->jv_next)
7833-
if (job->jv_stoponexit != NULL && *job->jv_stoponexit != NUL)
7848+
if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL)
78347849
mch_stop_job(job, job->jv_stoponexit);
78357850
}
78367851
#endif
@@ -10030,7 +10045,7 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported)
1003010045
opt->jo_out_cb = get_callback(item);
1003110046
if (opt->jo_out_cb == NULL)
1003210047
{
10033-
EMSG2(_(e_invarg2), "out-db");
10048+
EMSG2(_(e_invarg2), "out-cb");
1003410049
return FAIL;
1003510050
}
1003610051
}
@@ -10108,6 +10123,18 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported)
1010810123
return FAIL;
1010910124
}
1011010125
}
10126+
else if (STRCMP(hi->hi_key, "exit-cb") == 0)
10127+
{
10128+
if (!(supported & JO_EXIT_CB))
10129+
break;
10130+
opt->jo_set |= JO_EXIT_CB;
10131+
opt->jo_exit_cb = get_tv_string_buf_chk(item, opt->jo_ecb_buf);
10132+
if (opt->jo_ecb_buf == NULL)
10133+
{
10134+
EMSG2(_(e_invarg2), "exit-cb");
10135+
return FAIL;
10136+
}
10137+
}
1011110138
else
1011210139
break;
1011310140
--todo;
@@ -14771,7 +14798,7 @@ f_items(typval_T *argvars, typval_T *rettv)
1477114798
dict_list(argvars, rettv, 2);
1477214799
}
1477314800

14774-
#ifdef FEAT_JOB
14801+
#if defined(FEAT_JOB) || defined(PROTO)
1477514802
/*
1477614803
* Get the job from the argument.
1477714804
* Returns NULL if the job is invalid.
@@ -14824,7 +14851,7 @@ f_job_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
1482414851
if (job == NULL)
1482514852
return;
1482614853
clear_job_options(&opt);
14827-
if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT) == FAIL)
14854+
if (get_job_options(&argvars[1], &opt, JO_STOPONEXIT + JO_EXIT_CB) == FAIL)
1482814855
return;
1482914856
job_set_options(job, &opt);
1483014857
}
@@ -14858,7 +14885,8 @@ f_job_start(typval_T *argvars UNUSED, typval_T *rettv)
1485814885
clear_job_options(&opt);
1485914886
opt.jo_mode = MODE_NL;
1486014887
if (get_job_options(&argvars[1], &opt,
14861-
JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + JO_STOPONEXIT) == FAIL)
14888+
JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL
14889+
+ JO_STOPONEXIT + JO_EXIT_CB) == FAIL)
1486214890
return;
1486314891
job_set_options(job, &opt);
1486414892

@@ -14958,6 +14986,77 @@ f_job_start(typval_T *argvars UNUSED, typval_T *rettv)
1495814986
#endif
1495914987
}
1496014988

14989+
/*
14990+
* Get the status of "job" and invoke the exit callback when needed.
14991+
* The returned string is not allocated.
14992+
*/
14993+
static char *
14994+
job_status(job_T *job)
14995+
{
14996+
char *result;
14997+
14998+
if (job->jv_status == JOB_ENDED)
14999+
/* No need to check, dead is dead. */
15000+
result = "dead";
15001+
else if (job->jv_status == JOB_FAILED)
15002+
result = "fail";
15003+
else
15004+
{
15005+
result = mch_job_status(job);
15006+
# ifdef FEAT_CHANNEL
15007+
if (job->jv_status == JOB_ENDED)
15008+
ch_log(job->jv_channel, "Job ended");
15009+
# endif
15010+
if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL)
15011+
{
15012+
typval_T argv[3];
15013+
typval_T rettv;
15014+
int dummy;
15015+
15016+
/* invoke the exit callback */
15017+
argv[0].v_type = VAR_JOB;
15018+
argv[0].vval.v_job = job;
15019+
argv[1].v_type = VAR_NUMBER;
15020+
argv[1].vval.v_number = job->jv_exitval;
15021+
call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb),
15022+
&rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL);
15023+
clear_tv(&rettv);
15024+
}
15025+
if (job->jv_status == JOB_ENDED && job->jv_refcount == 0)
15026+
{
15027+
/* The job already was unreferenced, now that it ended it can be
15028+
* freed. Careful: caller must not use "job" after this! */
15029+
job_free(job);
15030+
}
15031+
}
15032+
return result;
15033+
}
15034+
15035+
/*
15036+
* Called once in a while: check if any jobs with an "exit-cb" have ended.
15037+
*/
15038+
void
15039+
job_check_ended()
15040+
{
15041+
static time_t last_check = 0;
15042+
time_t now;
15043+
job_T *job;
15044+
job_T *next;
15045+
15046+
/* Only do this once in 10 seconds. */
15047+
now = time(NULL);
15048+
if (last_check + 10 < now)
15049+
{
15050+
last_check = now;
15051+
for (job = first_job; job != NULL; job = next)
15052+
{
15053+
next = job->jv_next;
15054+
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL)
15055+
job_status(job); /* may free "job" */
15056+
}
15057+
}
15058+
}
15059+
1496115060
/*
1496215061
* "job_status()" function
1496315062
*/
@@ -14969,13 +15068,7 @@ f_job_status(typval_T *argvars, typval_T *rettv)
1496915068

1497015069
if (job != NULL)
1497115070
{
14972-
if (job->jv_status == JOB_ENDED)
14973-
/* No need to check, dead is dead. */
14974-
result = "dead";
14975-
else if (job->jv_status == JOB_FAILED)
14976-
result = "fail";
14977-
else
14978-
result = mch_job_status(job);
15071+
result = job_status(job);
1497915072
rettv->v_type = VAR_STRING;
1498015073
rettv->vval.v_string = vim_strsave((char_u *)result);
1498115074
}
@@ -22857,7 +22950,8 @@ copy_tv(typval_T *from, typval_T *to)
2285722950
case VAR_JOB:
2285822951
#ifdef FEAT_JOB
2285922952
to->vval.v_job = from->vval.v_job;
22860-
++to->vval.v_job->jv_refcount;
22953+
if (to->vval.v_job != NULL)
22954+
++to->vval.v_job->jv_refcount;
2286122955
break;
2286222956
#endif
2286322957
case VAR_CHANNEL:

src/macros.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,6 @@
317317
# define PLINES_NOFILL(x) plines(x)
318318
#endif
319319

320-
#if defined(FEAT_CHANNEL) || defined(FEAT_CLIENTSERVER)
320+
#if defined(FEAT_CHANNEL) || defined(FEAT_JOB) || defined(FEAT_CLIENTSERVER)
321321
# define MESSAGE_QUEUE
322322
#endif

src/misc2.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6256,5 +6256,9 @@ parse_queued_messages(void)
62566256
/* Process the queued clientserver messages. */
62576257
server_parse_messages();
62586258
# endif
6259+
# ifdef FEAT_JOB
6260+
/* Check if any jobs have ended. */
6261+
job_check_ended();
6262+
# endif
62596263
}
62606264
#endif

src/proto/eval.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ char_u *get_expr_name(expand_T *xp, int idx);
8787
int call_func(char_u *funcname, int len, typval_T *rettv, int argcount, typval_T *argvars, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict);
8888
int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv);
8989
void dict_extend(dict_T *d1, dict_T *d2, char_u *action);
90+
void job_check_ended(void);
9091
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
9192
float_T vim_round(float_T f);
9293
long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit);

src/structs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,7 @@ struct jobvar_S
12651265
#endif
12661266
jobstatus_T jv_status;
12671267
char_u *jv_stoponexit; /* allocated */
1268+
char_u *jv_exit_cb; /* allocated */
12681269

12691270
int jv_refcount; /* reference count */
12701271
channel_T *jv_channel; /* channel for I/O, reference counted */
@@ -1390,6 +1391,7 @@ struct channel_S {
13901391
#define JO_PART 0x0800 /* "part" */
13911392
#define JO_ID 0x1000 /* "id" */
13921393
#define JO_STOPONEXIT 0x2000 /* "stoponexit" */
1394+
#define JO_EXIT_CB 0x4000 /* "exit-cb" */
13931395
#define JO_ALL 0xffffff
13941396

13951397
#define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE)
@@ -1418,6 +1420,8 @@ typedef struct
14181420
int jo_id;
14191421
char_u jo_soe_buf[NUMBUFLEN];
14201422
char_u *jo_stoponexit;
1423+
char_u jo_ecb_buf[NUMBUFLEN];
1424+
char_u *jo_exit_cb;
14211425
} jobopt_T;
14221426

14231427

src/testdir/test_channel.vim

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,28 @@ func Test_call()
468468
call ch_log('Test_call()')
469469
call s:run_server('s:test_call')
470470
endfunc
471+
472+
"""""""""
473+
474+
let s:job_ret = 'not yet'
475+
function MyExitCb(job, status)
476+
let s:job_ret = 'done'
477+
endfunc
478+
479+
function s:test_exit_callback(port)
480+
call job_setoptions(s:job, {'exit-cb': 'MyExitCb'})
481+
let s:exit_job = s:job
482+
endfunc
483+
484+
func Test_exit_callback()
485+
if has('job')
486+
call s:run_server('s:test_exit_callback')
487+
488+
" the job may take a little while to exit
489+
sleep 50m
490+
491+
" calling job_status() triggers the callback
492+
call job_status(s:exit_job)
493+
call assert_equal('done', s:job_ret)
494+
endif
495+
endfunc

src/version.c

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

748748
static int included_patches[] =
749749
{ /* Add new patch number below this line */
750+
/**/
751+
1380,
750752
/**/
751753
1379,
752754
/**/

0 commit comments

Comments
 (0)