Skip to content

Commit 2a4857a

Browse files
committed
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)
1 parent 50948e4 commit 2a4857a

5 files changed

Lines changed: 109 additions & 31 deletions

File tree

src/channel.c

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5161,15 +5161,24 @@ job_free_contents(job_T *job)
51615161
}
51625162
}
51635163

5164+
/*
5165+
* Remove "job" from the list of jobs.
5166+
*/
51645167
static void
5165-
job_free_job(job_T *job)
5168+
job_unlink(job_T *job)
51665169
{
51675170
if (job->jv_next != NULL)
51685171
job->jv_next->jv_prev = job->jv_prev;
51695172
if (job->jv_prev == NULL)
51705173
first_job = job->jv_next;
51715174
else
51725175
job->jv_prev->jv_next = job->jv_next;
5176+
}
5177+
5178+
static void
5179+
job_free_job(job_T *job)
5180+
{
5181+
job_unlink(job);
51735182
vim_free(job);
51745183
}
51755184

@@ -5183,12 +5192,44 @@ job_free(job_T *job)
51835192
}
51845193
}
51855194

5195+
job_T *jobs_to_free = NULL;
5196+
5197+
/*
5198+
* Put "job" in a list to be freed later, when it's no longer referenced.
5199+
*/
5200+
static void
5201+
job_free_later(job_T *job)
5202+
{
5203+
job_unlink(job);
5204+
job->jv_next = jobs_to_free;
5205+
jobs_to_free = job;
5206+
}
5207+
5208+
static void
5209+
free_jobs_to_free_later(void)
5210+
{
5211+
job_T *job;
5212+
5213+
while (jobs_to_free != NULL)
5214+
{
5215+
job = jobs_to_free;
5216+
jobs_to_free = job->jv_next;
5217+
job_free_contents(job);
5218+
vim_free(job);
5219+
}
5220+
}
5221+
51865222
#if defined(EXITFREE) || defined(PROTO)
51875223
void
51885224
job_free_all(void)
51895225
{
51905226
while (first_job != NULL)
51915227
job_free(first_job);
5228+
free_jobs_to_free_later();
5229+
5230+
# ifdef FEAT_TERMINAL
5231+
free_unused_terminals();
5232+
# endif
51925233
}
51935234
#endif
51945235

@@ -5359,6 +5400,8 @@ win32_build_cmd(list_T *l, garray_T *gap)
53595400
* NOTE: Must call job_cleanup() only once right after the status of "job"
53605401
* changed to JOB_ENDED (i.e. after job_status() returned "dead" first or
53615402
* mch_detect_ended_job() returned non-NULL).
5403+
* If the job is no longer used it will be removed from the list of jobs, and
5404+
* deleted a bit later.
53625405
*/
53635406
void
53645407
job_cleanup(job_T *job)
@@ -5394,15 +5437,13 @@ job_cleanup(job_T *job)
53945437
channel_need_redraw = TRUE;
53955438
}
53965439

5397-
/* Do not free the job in case the close callback of the associated channel
5398-
* isn't invoked yet and may get information by job_info(). */
5440+
// Do not free the job in case the close callback of the associated channel
5441+
// isn't invoked yet and may get information by job_info().
53995442
if (job->jv_refcount == 0 && !job_channel_still_useful(job))
5400-
{
5401-
/* The job was already unreferenced and the associated channel was
5402-
* detached, now that it ended it can be freed. Careful: caller must
5403-
* not use "job" after this! */
5404-
job_free(job);
5405-
}
5443+
// The job was already unreferenced and the associated channel was
5444+
// detached, now that it ended it can be freed. However, a caller might
5445+
// still use it, thus free it a bit later.
5446+
job_free_later(job);
54065447
}
54075448

54085449
/*
@@ -5609,9 +5650,12 @@ job_check_ended(void)
56095650
if (job == NULL)
56105651
break;
56115652
did_end = TRUE;
5612-
job_cleanup(job); // may free "job"
5653+
job_cleanup(job); // may add "job" to jobs_to_free
56135654
}
56145655

5656+
// Actually free jobs that were cleaned up.
5657+
free_jobs_to_free_later();
5658+
56155659
if (channel_need_redraw)
56165660
{
56175661
channel_need_redraw = FALSE;

src/misc2.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6386,6 +6386,9 @@ parse_queued_messages(void)
63866386
// changes, e.g. stdin may have been closed.
63876387
if (job_check_ended())
63886388
continue;
6389+
# endif
6390+
# ifdef FEAT_TERMINAL
6391+
free_unused_terminals();
63896392
# endif
63906393
break;
63916394
}

src/proto/terminal.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ void ex_terminal(exarg_T *eap);
55
int term_write_session(FILE *fd, win_T *wp);
66
int term_should_restore(buf_T *buf);
77
void free_terminal(buf_T *buf);
8+
void free_unused_terminals(void);
89
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
910
int term_job_running(term_T *term);
1011
int term_none_open(term_T *term);

src/terminal.c

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -803,10 +803,17 @@ free_scrollback(term_T *term)
803803
ga_clear(&term->tl_scrollback);
804804
}
805805

806+
807+
// Terminals that need to be freed soon.
808+
term_T *terminals_to_free = NULL;
809+
806810
/*
807811
* Free a terminal and everything it refers to.
808812
* Kills the job if there is one.
809813
* Called when wiping out a buffer.
814+
* The actual terminal structure is freed later in free_unused_terminals(),
815+
* because callbacks may wipe out a buffer while the terminal is still
816+
* referenced.
810817
*/
811818
void
812819
free_terminal(buf_T *buf)
@@ -816,6 +823,8 @@ free_terminal(buf_T *buf)
816823

817824
if (term == NULL)
818825
return;
826+
827+
// Unlink the terminal form the list of terminals.
819828
if (first_term == term)
820829
first_term = term->tl_next;
821830
else
@@ -834,27 +843,41 @@ free_terminal(buf_T *buf)
834843
job_stop(term->tl_job, NULL, "kill");
835844
job_unref(term->tl_job);
836845
}
846+
term->tl_next = terminals_to_free;
847+
terminals_to_free = term;
848+
849+
buf->b_term = NULL;
850+
if (in_terminal_loop == term)
851+
in_terminal_loop = NULL;
852+
}
837853

838-
free_scrollback(term);
854+
void
855+
free_unused_terminals()
856+
{
857+
while (terminals_to_free != NULL)
858+
{
859+
term_T *term = terminals_to_free;
839860

840-
term_free_vterm(term);
841-
vim_free(term->tl_title);
861+
terminals_to_free = term->tl_next;
862+
863+
free_scrollback(term);
864+
865+
term_free_vterm(term);
866+
vim_free(term->tl_title);
842867
#ifdef FEAT_SESSION
843-
vim_free(term->tl_command);
868+
vim_free(term->tl_command);
844869
#endif
845-
vim_free(term->tl_kill);
846-
vim_free(term->tl_status_text);
847-
vim_free(term->tl_opencmd);
848-
vim_free(term->tl_eof_chars);
870+
vim_free(term->tl_kill);
871+
vim_free(term->tl_status_text);
872+
vim_free(term->tl_opencmd);
873+
vim_free(term->tl_eof_chars);
849874
#ifdef WIN3264
850-
if (term->tl_out_fd != NULL)
851-
fclose(term->tl_out_fd);
875+
if (term->tl_out_fd != NULL)
876+
fclose(term->tl_out_fd);
852877
#endif
853-
vim_free(term->tl_cursor_color);
854-
vim_free(term);
855-
buf->b_term = NULL;
856-
if (in_terminal_loop == term)
857-
in_terminal_loop = NULL;
878+
vim_free(term->tl_cursor_color);
879+
vim_free(term);
880+
}
858881
}
859882

860883
/*
@@ -1275,6 +1298,7 @@ term_convert_key(term_T *term, int c, char *buf)
12751298
/*
12761299
* Return TRUE if the job for "term" is still running.
12771300
* If "check_job_status" is TRUE update the job status.
1301+
* NOTE: "term" may be freed by callbacks.
12781302
*/
12791303
static int
12801304
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)
12851309
&& term->tl_job != NULL
12861310
&& channel_is_open(term->tl_job->jv_channel))
12871311
{
1312+
job_T *job = term->tl_job;
1313+
1314+
// Careful: Checking the job status may invoked callbacks, which close
1315+
// the buffer and terminate "term". However, "job" will not be freed
1316+
// yet.
12881317
if (check_job_status)
1289-
job_status(term->tl_job);
1290-
return (term->tl_job->jv_status == JOB_STARTED
1291-
|| term->tl_job->jv_channel->ch_keep_open);
1318+
job_status(job);
1319+
return (job->jv_status == JOB_STARTED
1320+
|| (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
12921321
}
12931322
return FALSE;
12941323
}
@@ -2151,9 +2180,8 @@ terminal_loop(int blocking)
21512180
#ifdef FEAT_GUI
21522181
if (!curbuf->b_term->tl_system)
21532182
#endif
2154-
/* TODO: skip screen update when handling a sequence of keys. */
2155-
/* Repeat redrawing in case a message is received while redrawing.
2156-
*/
2183+
// TODO: skip screen update when handling a sequence of keys.
2184+
// Repeat redrawing in case a message is received while redrawing.
21572185
while (must_redraw != 0)
21582186
if (update_screen(0) == FAIL)
21592187
break;

src/version.c

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

784784
static int included_patches[] =
785785
{ /* Add new patch number below this line */
786+
/**/
787+
845,
786788
/**/
787789
844,
788790
/**/

0 commit comments

Comments
 (0)