summaryrefslogtreecommitdiff
path: root/src/terminal.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-01-29 22:29:07 +0100
committerBram Moolenaar <Bram@vim.org>2019-01-29 22:29:07 +0100
commit2a4857a1fcf1d188e5b985ac21bcfc532eddde94 (patch)
treeba6f315d6bf142f534541381ef5713e077080fee /src/terminal.c
parent50948e4ac24314d5a70404bbc592ffc28755ad9f (diff)
downloadvim-git-2a4857a1fcf1d188e5b985ac21bcfc532eddde94.tar.gz
patch 8.1.0845: having job_status() free the job causes problemsv8.1.0845
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)
Diffstat (limited to 'src/terminal.c')
-rw-r--r--src/terminal.c70
1 files changed, 49 insertions, 21 deletions
diff --git a/src/terminal.c b/src/terminal.c
index f33521a9f..87530af2e 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -803,10 +803,17 @@ free_scrollback(term_T *term)
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.
* Kills the job if there is one.
* 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
free_terminal(buf_T *buf)
@@ -816,6 +823,8 @@ free_terminal(buf_T *buf)
if (term == NULL)
return;
+
+ // Unlink the terminal form the list of terminals.
if (first_term == term)
first_term = term->tl_next;
else
@@ -834,27 +843,41 @@ free_terminal(buf_T *buf)
job_stop(term->tl_job, NULL, "kill");
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;
+}
- free_scrollback(term);
+ void
+free_unused_terminals()
+{
+ while (terminals_to_free != NULL)
+ {
+ term_T *term = terminals_to_free;
- term_free_vterm(term);
- vim_free(term->tl_title);
+ terminals_to_free = term->tl_next;
+
+ free_scrollback(term);
+
+ term_free_vterm(term);
+ vim_free(term->tl_title);
#ifdef FEAT_SESSION
- vim_free(term->tl_command);
+ vim_free(term->tl_command);
#endif
- vim_free(term->tl_kill);
- vim_free(term->tl_status_text);
- vim_free(term->tl_opencmd);
- vim_free(term->tl_eof_chars);
+ vim_free(term->tl_kill);
+ vim_free(term->tl_status_text);
+ vim_free(term->tl_opencmd);
+ vim_free(term->tl_eof_chars);
#ifdef WIN3264
- if (term->tl_out_fd != NULL)
- fclose(term->tl_out_fd);
+ if (term->tl_out_fd != NULL)
+ fclose(term->tl_out_fd);
#endif
- vim_free(term->tl_cursor_color);
- vim_free(term);
- buf->b_term = NULL;
- if (in_terminal_loop == term)
- in_terminal_loop = NULL;
+ vim_free(term->tl_cursor_color);
+ vim_free(term);
+ }
}
/*
@@ -1275,6 +1298,7 @@ term_convert_key(term_T *term, int c, char *buf)
/*
* Return TRUE if the job for "term" is still running.
* If "check_job_status" is TRUE update the job status.
+ * NOTE: "term" may be freed by callbacks.
*/
static int
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
&& 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)
- job_status(term->tl_job);
- return (term->tl_job->jv_status == JOB_STARTED
- || term->tl_job->jv_channel->ch_keep_open);
+ job_status(job);
+ return (job->jv_status == JOB_STARTED
+ || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
}
return FALSE;
}
@@ -2151,9 +2180,8 @@ terminal_loop(int blocking)
#ifdef FEAT_GUI
if (!curbuf->b_term->tl_system)
#endif
- /* TODO: skip screen update when handling a sequence of keys. */
- /* Repeat redrawing in case a message is received while redrawing.
- */
+ // TODO: skip screen update when handling a sequence of keys.
+ // Repeat redrawing in case a message is received while redrawing.
while (must_redraw != 0)
if (update_screen(0) == FAIL)
break;