0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

patch 8.1.0845: having job_status() free the job causes problems

Problem:    Having job_status() free the job causes problems.
Solution:   Do not actually free the job or terminal yet, put it in a list and
            free it a bit later. Do not use a terminal after checking the job
            status.  (closes #3873)
This commit is contained in:
Bram Moolenaar
2019-01-29 22:29:07 +01:00
parent 50948e4ac2
commit 2a4857a1fc
5 changed files with 111 additions and 33 deletions

View File

@@ -5161,8 +5161,11 @@ job_free_contents(job_T *job)
} }
} }
/*
* Remove "job" from the list of jobs.
*/
static void static void
job_free_job(job_T *job) job_unlink(job_T *job)
{ {
if (job->jv_next != NULL) if (job->jv_next != NULL)
job->jv_next->jv_prev = job->jv_prev; job->jv_next->jv_prev = job->jv_prev;
@@ -5170,6 +5173,12 @@ job_free_job(job_T *job)
first_job = job->jv_next; first_job = job->jv_next;
else else
job->jv_prev->jv_next = job->jv_next; job->jv_prev->jv_next = job->jv_next;
}
static void
job_free_job(job_T *job)
{
job_unlink(job);
vim_free(job); vim_free(job);
} }
@@ -5183,12 +5192,44 @@ job_free(job_T *job)
} }
} }
job_T *jobs_to_free = NULL;
/*
* Put "job" in a list to be freed later, when it's no longer referenced.
*/
static void
job_free_later(job_T *job)
{
job_unlink(job);
job->jv_next = jobs_to_free;
jobs_to_free = job;
}
static void
free_jobs_to_free_later(void)
{
job_T *job;
while (jobs_to_free != NULL)
{
job = jobs_to_free;
jobs_to_free = job->jv_next;
job_free_contents(job);
vim_free(job);
}
}
#if defined(EXITFREE) || defined(PROTO) #if defined(EXITFREE) || defined(PROTO)
void void
job_free_all(void) job_free_all(void)
{ {
while (first_job != NULL) while (first_job != NULL)
job_free(first_job); job_free(first_job);
free_jobs_to_free_later();
# ifdef FEAT_TERMINAL
free_unused_terminals();
# endif
} }
#endif #endif
@@ -5359,6 +5400,8 @@ win32_build_cmd(list_T *l, garray_T *gap)
* NOTE: Must call job_cleanup() only once right after the status of "job" * NOTE: Must call job_cleanup() only once right after the status of "job"
* changed to JOB_ENDED (i.e. after job_status() returned "dead" first or * changed to JOB_ENDED (i.e. after job_status() returned "dead" first or
* mch_detect_ended_job() returned non-NULL). * mch_detect_ended_job() returned non-NULL).
* If the job is no longer used it will be removed from the list of jobs, and
* deleted a bit later.
*/ */
void void
job_cleanup(job_T *job) job_cleanup(job_T *job)
@@ -5394,15 +5437,13 @@ job_cleanup(job_T *job)
channel_need_redraw = TRUE; channel_need_redraw = TRUE;
} }
/* Do not free the job in case the close callback of the associated channel // Do not free the job in case the close callback of the associated channel
* isn't invoked yet and may get information by job_info(). */ // isn't invoked yet and may get information by job_info().
if (job->jv_refcount == 0 && !job_channel_still_useful(job)) if (job->jv_refcount == 0 && !job_channel_still_useful(job))
{ // The job was already unreferenced and the associated channel was
/* The job was already unreferenced and the associated channel was // detached, now that it ended it can be freed. However, a caller might
* detached, now that it ended it can be freed. Careful: caller must // still use it, thus free it a bit later.
* not use "job" after this! */ job_free_later(job);
job_free(job);
}
} }
/* /*
@@ -5609,9 +5650,12 @@ job_check_ended(void)
if (job == NULL) if (job == NULL)
break; break;
did_end = TRUE; did_end = TRUE;
job_cleanup(job); // may free "job" job_cleanup(job); // may add "job" to jobs_to_free
} }
// Actually free jobs that were cleaned up.
free_jobs_to_free_later();
if (channel_need_redraw) if (channel_need_redraw)
{ {
channel_need_redraw = FALSE; channel_need_redraw = FALSE;

View File

@@ -6386,6 +6386,9 @@ parse_queued_messages(void)
// changes, e.g. stdin may have been closed. // changes, e.g. stdin may have been closed.
if (job_check_ended()) if (job_check_ended())
continue; continue;
# endif
# ifdef FEAT_TERMINAL
free_unused_terminals();
# endif # endif
break; break;
} }

View File

@@ -5,6 +5,7 @@ void ex_terminal(exarg_T *eap);
int term_write_session(FILE *fd, win_T *wp); int term_write_session(FILE *fd, win_T *wp);
int term_should_restore(buf_T *buf); int term_should_restore(buf_T *buf);
void free_terminal(buf_T *buf); void free_terminal(buf_T *buf);
void free_unused_terminals(void);
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel); void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
int term_job_running(term_T *term); int term_job_running(term_T *term);
int term_none_open(term_T *term); int term_none_open(term_T *term);

View File

@@ -803,10 +803,17 @@ free_scrollback(term_T *term)
ga_clear(&term->tl_scrollback); ga_clear(&term->tl_scrollback);
} }
// Terminals that need to be freed soon.
term_T *terminals_to_free = NULL;
/* /*
* Free a terminal and everything it refers to. * Free a terminal and everything it refers to.
* Kills the job if there is one. * Kills the job if there is one.
* Called when wiping out a buffer. * Called when wiping out a buffer.
* The actual terminal structure is freed later in free_unused_terminals(),
* because callbacks may wipe out a buffer while the terminal is still
* referenced.
*/ */
void void
free_terminal(buf_T *buf) free_terminal(buf_T *buf)
@@ -816,6 +823,8 @@ free_terminal(buf_T *buf)
if (term == NULL) if (term == NULL)
return; return;
// Unlink the terminal form the list of terminals.
if (first_term == term) if (first_term == term)
first_term = term->tl_next; first_term = term->tl_next;
else else
@@ -834,6 +843,22 @@ free_terminal(buf_T *buf)
job_stop(term->tl_job, NULL, "kill"); job_stop(term->tl_job, NULL, "kill");
job_unref(term->tl_job); job_unref(term->tl_job);
} }
term->tl_next = terminals_to_free;
terminals_to_free = term;
buf->b_term = NULL;
if (in_terminal_loop == term)
in_terminal_loop = NULL;
}
void
free_unused_terminals()
{
while (terminals_to_free != NULL)
{
term_T *term = terminals_to_free;
terminals_to_free = term->tl_next;
free_scrollback(term); free_scrollback(term);
@@ -852,9 +877,7 @@ free_terminal(buf_T *buf)
#endif #endif
vim_free(term->tl_cursor_color); vim_free(term->tl_cursor_color);
vim_free(term); vim_free(term);
buf->b_term = NULL; }
if (in_terminal_loop == term)
in_terminal_loop = NULL;
} }
/* /*
@@ -1275,6 +1298,7 @@ term_convert_key(term_T *term, int c, char *buf)
/* /*
* Return TRUE if the job for "term" is still running. * Return TRUE if the job for "term" is still running.
* If "check_job_status" is TRUE update the job status. * If "check_job_status" is TRUE update the job status.
* NOTE: "term" may be freed by callbacks.
*/ */
static int static int
term_job_running_check(term_T *term, int check_job_status) term_job_running_check(term_T *term, int check_job_status)
@@ -1285,10 +1309,15 @@ term_job_running_check(term_T *term, int check_job_status)
&& term->tl_job != NULL && term->tl_job != NULL
&& channel_is_open(term->tl_job->jv_channel)) && channel_is_open(term->tl_job->jv_channel))
{ {
job_T *job = term->tl_job;
// Careful: Checking the job status may invoked callbacks, which close
// the buffer and terminate "term". However, "job" will not be freed
// yet.
if (check_job_status) if (check_job_status)
job_status(term->tl_job); job_status(job);
return (term->tl_job->jv_status == JOB_STARTED return (job->jv_status == JOB_STARTED
|| term->tl_job->jv_channel->ch_keep_open); || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
} }
return FALSE; return FALSE;
} }
@@ -2151,9 +2180,8 @@ terminal_loop(int blocking)
#ifdef FEAT_GUI #ifdef FEAT_GUI
if (!curbuf->b_term->tl_system) if (!curbuf->b_term->tl_system)
#endif #endif
/* TODO: skip screen update when handling a sequence of keys. */ // TODO: skip screen update when handling a sequence of keys.
/* Repeat redrawing in case a message is received while redrawing. // Repeat redrawing in case a message is received while redrawing.
*/
while (must_redraw != 0) while (must_redraw != 0)
if (update_screen(0) == FAIL) if (update_screen(0) == FAIL)
break; break;

View File

@@ -783,6 +783,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
845,
/**/ /**/
844, 844,
/**/ /**/