summaryrefslogtreecommitdiff
path: root/src/channel.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/channel.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/channel.c')
-rw-r--r--src/channel.c64
1 files changed, 54 insertions, 10 deletions
diff --git a/src/channel.c b/src/channel.c
index 419939897..cf6827192 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -5161,8 +5161,11 @@ job_free_contents(job_T *job)
}
}
+/*
+ * Remove "job" from the list of jobs.
+ */
static void
-job_free_job(job_T *job)
+job_unlink(job_T *job)
{
if (job->jv_next != NULL)
job->jv_next->jv_prev = job->jv_prev;
@@ -5170,6 +5173,12 @@ job_free_job(job_T *job)
first_job = job->jv_next;
else
job->jv_prev->jv_next = job->jv_next;
+}
+
+ static void
+job_free_job(job_T *job)
+{
+ job_unlink(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)
void
job_free_all(void)
{
while (first_job != NULL)
job_free(first_job);
+ free_jobs_to_free_later();
+
+# ifdef FEAT_TERMINAL
+ free_unused_terminals();
+# 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"
* changed to JOB_ENDED (i.e. after job_status() returned "dead" first or
* 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
job_cleanup(job_T *job)
@@ -5394,15 +5437,13 @@ job_cleanup(job_T *job)
channel_need_redraw = TRUE;
}
- /* 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(). */
+ // 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().
if (job->jv_refcount == 0 && !job_channel_still_useful(job))
- {
- /* The job was already unreferenced and the associated channel was
- * detached, now that it ended it can be freed. Careful: caller must
- * not use "job" after this! */
- job_free(job);
- }
+ // The job was already unreferenced and the associated channel was
+ // detached, now that it ended it can be freed. However, a caller might
+ // still use it, thus free it a bit later.
+ job_free_later(job);
}
/*
@@ -5609,9 +5650,12 @@ job_check_ended(void)
if (job == NULL)
break;
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)
{
channel_need_redraw = FALSE;