Last active
October 15, 2016 04:37
-
-
Save ichizok/fe9743f46822a9015ed2f7d65238c5db to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/channel.c b/src/channel.c | |
index 2d68287..68d21a5 100644 | |
--- a/src/channel.c | |
+++ b/src/channel.c | |
@@ -4428,6 +4428,40 @@ job_free(job_T *job) | |
} | |
} | |
+ static void | |
+job_cleanup(job_T *job) | |
+{ | |
+ if (job->jv_status != JOB_ENDED) | |
+ return; | |
+ | |
+ ch_log(job->jv_channel, "Job ended"); | |
+ if (job->jv_exit_cb != NULL) | |
+ { | |
+ typval_T argv[3]; | |
+ typval_T rettv; | |
+ int dummy; | |
+ | |
+ /* invoke the exit callback; make sure the refcount is > 0 */ | |
+ ++job->jv_refcount; | |
+ argv[0].v_type = VAR_JOB; | |
+ argv[0].vval.v_job = job; | |
+ argv[1].v_type = VAR_NUMBER; | |
+ argv[1].vval.v_number = job->jv_exitval; | |
+ call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), | |
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, | |
+ job->jv_exit_partial, NULL); | |
+ clear_tv(&rettv); | |
+ --job->jv_refcount; | |
+ channel_need_redraw = TRUE; | |
+ } | |
+ if (job->jv_refcount == 0) | |
+ { | |
+ /* The job was already unreferenced, now that it ended it can be | |
+ * freed. Careful: caller must not use "job" after this! */ | |
+ job_free(job); | |
+ } | |
+} | |
+ | |
#if defined(EXITFREE) || defined(PROTO) | |
void | |
job_free_all(void) | |
@@ -4445,10 +4479,15 @@ job_free_all(void) | |
static int | |
job_still_useful(job_T *job) | |
{ | |
- return job->jv_status == JOB_STARTED | |
- && (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL | |
- || (job->jv_channel != NULL | |
- && channel_still_useful(job->jv_channel))); | |
+ return (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL | |
+ || (job->jv_channel != NULL | |
+ && channel_still_useful(job->jv_channel))); | |
+} | |
+ | |
+ static int | |
+job_still_alive(job_T *job) | |
+{ | |
+ return (job->jv_status == JOB_STARTED) && job_still_useful(job); | |
} | |
/* | |
@@ -4462,7 +4501,7 @@ set_ref_in_job(int copyID) | |
typval_T tv; | |
for (job = first_job; job != NULL; job = job->jv_next) | |
- if (job_still_useful(job)) | |
+ if (job_still_alive(job)) | |
{ | |
tv.v_type = VAR_JOB; | |
tv.vval.v_job = job; | |
@@ -4478,7 +4517,7 @@ job_unref(job_T *job) | |
{ | |
/* Do not free the job when it has not ended yet and there is a | |
* "stoponexit" flag or an exit callback. */ | |
- if (!job_still_useful(job)) | |
+ if (!job_still_alive(job)) | |
{ | |
job_free(job); | |
} | |
@@ -4503,7 +4542,7 @@ free_unused_jobs_contents(int copyID, int mask) | |
for (job = first_job; job != NULL; job = job->jv_next) | |
if ((job->jv_copyID & mask) != (copyID & mask) | |
- && !job_still_useful(job)) | |
+ && !job_still_alive(job)) | |
{ | |
/* Free the channel and ordinary items it contains, but don't | |
* recurse into Lists, Dictionaries etc. */ | |
@@ -4523,7 +4562,7 @@ free_unused_jobs(int copyID, int mask) | |
{ | |
job_next = job->jv_next; | |
if ((job->jv_copyID & mask) != (copyID & mask) | |
- && !job_still_useful(job)) | |
+ && !job_still_alive(job)) | |
{ | |
/* Free the job struct itself. */ | |
job_free_job(job); | |
@@ -4614,34 +4653,31 @@ has_pending_job(void) | |
job_T *job; | |
for (job = first_job; job != NULL; job = job->jv_next) | |
- if (job->jv_status == JOB_STARTED && job_still_useful(job)) | |
+ if (job_still_alive(job)) | |
return TRUE; | |
return FALSE; | |
} | |
+#define MAX_CHECK_ENDED 8 | |
+ | |
/* | |
* Called once in a while: check if any jobs that seem useful have ended. | |
*/ | |
void | |
job_check_ended(void) | |
{ | |
- static time_t last_check = 0; | |
- time_t now; | |
- job_T *job; | |
- job_T *next; | |
+ int i; | |
- /* Only do this once in 10 seconds. */ | |
- now = time(NULL); | |
- if (last_check + 10 < now) | |
+ for (i = 0; i < MAX_CHECK_ENDED; ++i) | |
{ | |
- last_check = now; | |
- for (job = first_job; job != NULL; job = next) | |
- { | |
- next = job->jv_next; | |
- if (job->jv_status == JOB_STARTED && job_still_useful(job)) | |
- job_status(job); /* may free "job" */ | |
- } | |
+ job_T *job = mch_detect_ended_job(first_job); | |
+ | |
+ if (job == NULL) | |
+ break; | |
+ if (job_still_useful(job)) | |
+ job_cleanup(job); /* may free "job" */ | |
} | |
+ | |
if (channel_need_redraw) | |
{ | |
channel_need_redraw = FALSE; | |
@@ -4862,32 +4898,7 @@ job_status(job_T *job) | |
{ | |
result = mch_job_status(job); | |
if (job->jv_status == JOB_ENDED) | |
- ch_log(job->jv_channel, "Job ended"); | |
- if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL) | |
- { | |
- typval_T argv[3]; | |
- typval_T rettv; | |
- int dummy; | |
- | |
- /* invoke the exit callback; make sure the refcount is > 0 */ | |
- ++job->jv_refcount; | |
- argv[0].v_type = VAR_JOB; | |
- argv[0].vval.v_job = job; | |
- argv[1].v_type = VAR_NUMBER; | |
- argv[1].vval.v_number = job->jv_exitval; | |
- call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), | |
- &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, | |
- job->jv_exit_partial, NULL); | |
- clear_tv(&rettv); | |
- --job->jv_refcount; | |
- channel_need_redraw = TRUE; | |
- } | |
- if (job->jv_status == JOB_ENDED && job->jv_refcount == 0) | |
- { | |
- /* The job was already unreferenced, now that it ended it can be | |
- * freed. Careful: caller must not use "job" after this! */ | |
- job_free(job); | |
- } | |
+ job_cleanup(job); | |
} | |
return result; | |
} | |
diff --git a/src/os_unix.c b/src/os_unix.c | |
index 8b0dd55..a39768e 100644 | |
--- a/src/os_unix.c | |
+++ b/src/os_unix.c | |
@@ -5315,6 +5315,42 @@ mch_job_status(job_T *job) | |
return "run"; | |
} | |
+ job_T * | |
+mch_detect_ended_job(job_T *job_list) | |
+{ | |
+# ifdef HAVE_UNION_WAIT | |
+ union wait status; | |
+# else | |
+ int status = -1; | |
+# endif | |
+ pid_t wait_pid = 0; | |
+ job_T *job; | |
+ | |
+# ifdef __NeXT__ | |
+ wait_pid = wait4(-1, &status, WNOHANG, (struct rusage *)0); | |
+# else | |
+ wait_pid = waitpid(-1, &status, WNOHANG); | |
+# endif | |
+ if (wait_pid <= 0) | |
+ /* no process ended */ | |
+ return NULL; | |
+ for (job = job_list; job != NULL; job = job->jv_next) | |
+ { | |
+ if (job->jv_pid == wait_pid) | |
+ { | |
+ if (WIFEXITED(status)) | |
+ /* LINTED avoid "bitwise operation on signed value" */ | |
+ job->jv_exitval = WEXITSTATUS(status); | |
+ else if (WIFSIGNALED(status)) | |
+ job->jv_exitval = -1; | |
+ job->jv_status = JOB_ENDED; | |
+ return job; | |
+ } | |
+ } | |
+ return NULL; | |
+ | |
+} | |
+ | |
int | |
mch_stop_job(job_T *job, char_u *how) | |
{ | |
diff --git a/src/os_win32.c b/src/os_win32.c | |
index 9ff2f7e..56dd590 100644 | |
--- a/src/os_win32.c | |
+++ b/src/os_win32.c | |
@@ -4980,6 +4980,42 @@ mch_job_status(job_T *job) | |
return "run"; | |
} | |
+ job_T * | |
+mch_detect_ended_job(job_T *job_list) | |
+{ | |
+ HANDLE jobHandles[MAXIMUM_WAIT_OBJECTS]; | |
+ job_T *jobArray[MAXIMUM_WAIT_OBJECTS]; | |
+ job_T *job = job_list; | |
+ | |
+ while (job != NULL) | |
+ { | |
+ DWORD n; | |
+ DWORD result; | |
+ | |
+ for (n = 0; n < MAXIMUM_WAIT_OBJECTS | |
+ && job != NULL; job = job->jv_next) | |
+ { | |
+ if (job->jv_status == JOB_STARTED) | |
+ { | |
+ jobHandles[n] = job->jv_proc_info.hProcess; | |
+ jobArray[n] = job; | |
+ ++n; | |
+ } | |
+ } | |
+ if (n == 0) | |
+ continue; | |
+ result = WaitForMultipleObjects(n, jobHandles, FALSE, 0); | |
+ if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) | |
+ { | |
+ job_T *wait_job = jobArray[result - WAIT_OBJECT_0]; | |
+ | |
+ if (STRCMP(mch_job_status(wait_job), "dead") == 0) | |
+ return wait_job; | |
+ } | |
+ } | |
+ return NULL; | |
+} | |
+ | |
int | |
mch_stop_job(job_T *job, char_u *how) | |
{ | |
diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro | |
index b1e9b8e..700b69e 100644 | |
--- a/src/proto/os_unix.pro | |
+++ b/src/proto/os_unix.pro | |
@@ -59,6 +59,7 @@ int mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc); | |
int mch_call_shell(char_u *cmd, int options); | |
void mch_start_job(char **argv, job_T *job, jobopt_T *options); | |
char *mch_job_status(job_T *job); | |
+job_T *mch_detect_ended_job(job_T *job_list); | |
int mch_stop_job(job_T *job, char_u *how); | |
void mch_clear_job(job_T *job); | |
void mch_breakcheck(int force); | |
diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro | |
index b76e347..b21a7ba 100644 | |
--- a/src/proto/os_win32.pro | |
+++ b/src/proto/os_win32.pro | |
@@ -41,6 +41,7 @@ void mch_set_winsize_now(void); | |
int mch_call_shell(char_u *cmd, int options); | |
void mch_start_job(char *cmd, job_T *job, jobopt_T *options); | |
char *mch_job_status(job_T *job); | |
+job_T *mch_detect_ended_job(job_T *job_list); | |
int mch_stop_job(job_T *job, char_u *how); | |
void mch_clear_job(job_T *job); | |
void mch_set_normal_colors(void); | |
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim | |
index fbcd496..e7ba667 100644 | |
--- a/src/testdir/test_channel.vim | |
+++ b/src/testdir/test_channel.vim | |
@@ -1362,6 +1362,24 @@ func Test_exit_callback() | |
endif | |
endfunc | |
+let g:exit_cb_time = {'start': 0, 'end': 0} | |
+function MyExitTimeCb(job, status) | |
+ let g:exit_cb_time.end = reltime(g:exit_cb_time.start) | |
+endfunction | |
+ | |
+func Test_exit_callback_interval() | |
+ if !has('job') | |
+ return | |
+ endif | |
+ | |
+ let g:exit_cb_time.start = reltime() | |
+ let job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'}) | |
+ call WaitFor('g:exit_cb_time.end != 0') | |
+ let elapsed = reltimefloat(g:exit_cb_time.end) | |
+ call assert_true(elapsed > 0.3) | |
+ call assert_true(elapsed < 1.0) | |
+endfunc | |
+ | |
""""""""" | |
let g:Ch_close_ret = 'alive' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment