[vlc-commits] [Git][videolan/vlc][master] 5 commits: preparser: fix a deadlock when using more than 1 external process

Steve Lhomme (@robUx4) gitlab at videolan.org
Wed Apr 22 16:09:33 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
5793ed94 by Gabriel Lafond-Thenaille at 2026-04-22T15:56:00+00:00
preparser: fix a deadlock when using more than 1 external process

- - - - -
6836835d by Gabriel Lafond-Thenaille at 2026-04-22T15:56:00+00:00
meta: merge status in `vlc_meta_Merge`

- - - - -
de0dae38 by Gabriel Lafond-Thenaille at 2026-04-22T15:56:00+00:00
preparser: add a comment to inform about a potential fork bomb

- - - - -
d3498983 by Gabriel Lafond-Thenaille at 2026-04-22T15:56:00+00:00
preparser: prevent a preparser thread from entering an infinite loop

Close a preparser thread if it fails to restart its process and try to
respawn it when receiving another request.

- - - - -
b8388870 by Gabriel Lafond-Thenaille at 2026-04-22T15:56:00+00:00
win32: remove execution right on the src/win32/spawn.c file

- - - - -


4 changed files:

- src/input/meta.c
- src/preparser/external.c
- src/preparser/preparser.c
- src/win32/spawn.c


Changes:

=====================================
src/input/meta.c
=====================================
@@ -262,6 +262,7 @@ void vlc_meta_Merge( vlc_meta_t *dst, const vlc_meta_t *src )
         free( ppsz_all_keys[i] );
     }
     free( ppsz_all_keys );
+    dst->i_status |= src->i_status;
 }
 
 


=====================================
src/preparser/external.c
=====================================
@@ -125,6 +125,9 @@ struct preparser_process_thread {
     struct vlc_process *process;
     bool process_running;
 
+    /* Status of the current thread */
+    bool stopped;
+
     /* The current task */
     struct preparser_task *task;
 
@@ -475,12 +478,15 @@ struct preparser_process_pool {
     /** Maximum number of threads to run the tasks */
     unsigned max_threads;
 
-    /** List of active vlc_process_executor_thread */
+    /** List of active preparser_process_thread */
     struct vlc_list threads;
 
     /** Number of running thread */
     size_t nthreads;
 
+    /** Number of stopped thread */
+    size_t nthreads_stopped;
+
     /** Unfinished task */
     size_t unfinished;
 
@@ -633,8 +639,6 @@ preparser_pool_Run(void *data)
     assert(thread->owner != NULL);
     assert(thread->process != NULL);
 
-    //struct preparser_process_pool *pool = thread->owner;
-
     vlc_thread_set_name("vlc-pool-runner");
 
 
@@ -676,17 +680,15 @@ preparser_pool_Run(void *data)
          * until it succeeds or the pool is shutting down. */
         vlc_process_Terminate(thread->process, true);
         thread->process_running = false;
-        while (preparser_pool_SpawnProcess(thread) != VLC_SUCCESS) {
-            vlc_mutex_unlock(&thread->owner->lock);
-            sleep(1);
-            vlc_mutex_lock(&thread->owner->lock);
-            if (thread->owner->closing) {
-                goto end;
-            }
+        if (preparser_pool_SpawnProcess(thread) != VLC_SUCCESS) {
+            goto end;
         }
         thread->process_running = true;
     }
 end:
+    thread->owner->nthreads--;
+    thread->owner->nthreads_stopped++;
+    thread->stopped = true;
     vlc_mutex_unlock(&thread->owner->lock);
     return NULL;
 }
@@ -695,9 +697,10 @@ end:
  * Start a new thread process with its external process.
  */
 static int
-preparser_pool_SpawnThread(struct preparser_process_pool *pool)
+preparser_pool_SpawnThreadLocked(struct preparser_process_pool *pool)
 {
     assert(pool != NULL);
+    vlc_mutex_assert(&pool->lock);
     assert(pool->nthreads < pool->max_threads);
 
     struct preparser_process_thread *thread = malloc(sizeof(*thread));
@@ -707,6 +710,7 @@ preparser_pool_SpawnThread(struct preparser_process_pool *pool)
 
     thread->owner = pool;
     thread->task = NULL;
+    thread->stopped = false;
 
     static struct vlc_preparser_msg_serdes_cbs cbs = {
         .write = write_cbs,
@@ -718,10 +722,8 @@ preparser_pool_SpawnThread(struct preparser_process_pool *pool)
         return VLC_EGENERIC;
     }
 
-    vlc_mutex_lock(&pool->lock);
     if (preparser_pool_SpawnProcess(thread) != VLC_SUCCESS) {
         msg_Err(pool->parent, "Fail to create Process in process_pool");
-        vlc_mutex_unlock(&pool->lock);
         vlc_preparser_msg_serdes_Delete(thread->serdes);
         free(thread);
         return VLC_EGENERIC;
@@ -730,7 +732,6 @@ preparser_pool_SpawnThread(struct preparser_process_pool *pool)
 
     int ret = vlc_clone(&thread->thread, preparser_pool_Run, thread);
     if (ret != 0) {
-        vlc_mutex_unlock(&pool->lock);
         vlc_process_Terminate(thread->process, false);
         vlc_preparser_msg_serdes_Delete(thread->serdes);
         free(thread);
@@ -738,17 +739,66 @@ preparser_pool_SpawnThread(struct preparser_process_pool *pool)
     }
     pool->nthreads++;
     vlc_list_append(&thread->node, &pool->threads);
-    vlc_mutex_unlock(&pool->lock);
 
     return VLC_SUCCESS;
 }
 
+static int
+preparser_pool_SpawnThread(struct preparser_process_pool *pool)
+{
+    assert(pool != NULL);
+
+    vlc_mutex_lock(&pool->lock);
+    int ret = preparser_pool_SpawnThreadLocked(pool);
+    vlc_mutex_unlock(&pool->lock);
+
+    return ret;
+}
+
+/**
+ * Join and free all stopped threads in the pool.
+ * Must be called with pool->lock held. Collects stopped threads under the lock,
+ * then releases it once to join them all, and re-acquires it before returning.
+ */
+static void
+preparser_pool_JoinStoppedThreadsLocked(struct preparser_process_pool *pool)
+{
+    vlc_mutex_assert(&pool->lock);
+
+    assert(pool->nthreads_stopped == 0);
+
+    /* Move all stopped threads to a local list while holding the lock */
+    struct vlc_list to_join;
+    vlc_list_init(&to_join);
+
+    struct preparser_process_thread *t;
+    vlc_list_foreach(t, &pool->threads, node) {
+        if (t->stopped) {
+            vlc_list_remove(&t->node);
+            vlc_list_append(&t->node, &to_join);
+            pool->nthreads_stopped--;
+        }
+    }
+
+    vlc_mutex_unlock(&pool->lock);
+    vlc_list_foreach(t, &to_join, node) {
+        vlc_join(t->thread, NULL);
+        if (t->process != NULL)
+            vlc_process_Terminate(t->process, false);
+        vlc_preparser_msg_serdes_Delete(t->serdes);
+        free(t);
+    }
+    vlc_mutex_lock(&pool->lock);
+}
+
 /**
  * Push a new task in the queue and check if a new process thread can be spawn.
  * If there is more unfinished task than spawned process thread and that the
  * max number of process thread is not reached, then a new thread is spwaned.
+ * Return a held reference to the task request, or NULL if no thread is
+ * available and a new one could not be spawned (task is deleted in that case).
  */
-static void
+static struct vlc_preparser_req *
 preparser_pool_Submit(struct preparser_process_pool *pool,
                       struct preparser_task *task)
 {
@@ -759,16 +809,31 @@ preparser_pool_Submit(struct preparser_process_pool *pool,
 
     assert(!pool->closing);
 
+    if (pool->nthreads_stopped != 0) {
+        preparser_pool_JoinStoppedThreadsLocked(pool);
+    }
+
+    if (pool->nthreads == 0) {
+        if (preparser_pool_SpawnThreadLocked(pool) != VLC_SUCCESS) {
+            vlc_mutex_unlock(&pool->lock);
+            preparser_task_Delete(task);
+            return NULL;
+        }
+    }
+
+    struct vlc_preparser_req *req = preparser_task_req_Hold(&task->req);
+
     preparser_pool_QueuePush(pool, task);
+    ++pool->unfinished;
 
-    bool need_new_thread = ++pool->unfinished > pool->nthreads &&
-                           pool->nthreads < pool->max_threads;
-    if (need_new_thread) {
+    /* Opportunistically spawn an extra thread if tasks outnumber threads */
+    if (pool->unfinished > pool->nthreads &&
+        pool->nthreads < pool->max_threads)
         /* If it fails, this is not an error, there is at least one thread */
-        preparser_pool_SpawnThread(pool);
-    }
+        preparser_pool_SpawnThreadLocked(pool);
 
     vlc_mutex_unlock(&pool->lock);
+    return req;
 }
 
 /**
@@ -874,6 +939,7 @@ preparser_pool_New(vlc_object_t *obj, size_t max, vlc_tick_t timeout,
 
     pool->max_threads = max;
     pool->nthreads = 0;
+    pool->nthreads_stopped = 0;
     pool->unfinished = 0;
     pool->timeout = timeout;
     pool->types = types;
@@ -928,9 +994,7 @@ preparser_Push(void *opaque, input_item_t *item, int options,
     }
     preparser_task_InitPush(task, options, &task_cbs, cbs_userdata);
 
-    struct vlc_preparser_req *req = preparser_task_req_Hold(&task->req);
-    preparser_pool_Submit(sys->pool_preparser, task);
-    return req;
+    return preparser_pool_Submit(sys->pool_preparser, task);
 }
 
 /**
@@ -961,9 +1025,7 @@ preparser_GenerateThumbnail(void *opaque, input_item_t *item,
     }
     preparser_task_InitThumbnail(task, thumb_arg, &task_cbs, cbs_userdata);
 
-    struct vlc_preparser_req *req = preparser_task_req_Hold(&task->req);
-    preparser_pool_Submit(sys->pool_thumbnailer, task);
-    return req;
+    return preparser_pool_Submit(sys->pool_thumbnailer, task);
 }
 
 /**
@@ -998,9 +1060,7 @@ preparser_GenerateThumbnailToFiles(void *opaque, input_item_t *item,
     preparser_task_InitThumbnailToFile(task, thumb_arg, outputs, output_count,
                                        &task_cbs, cbs_userdata);
 
-    struct vlc_preparser_req *req = preparser_task_req_Hold(&task->req);
-    preparser_pool_Submit(sys->pool_thumbnailer, task);
-    return req;
+    return preparser_pool_Submit(sys->pool_thumbnailer, task);
 }
 
 /**


=====================================
src/preparser/preparser.c
=====================================
@@ -41,6 +41,9 @@ vlc_preparser_t *vlc_preparser_New(vlc_object_t *obj,
         return NULL;
     }
 #if defined(HAVE_VLC_PROCESS_SPAWN)
+    /* Must reflect cfg->external_process: forcing this to true would cause
+     * vlc-preparser child processes to spawn further children, resulting in
+     * an infinite fork bomb. */
     const bool external_process = cfg->external_process;
 #else
     if (cfg->external_process) {


=====================================
src/win32/spawn.c
=====================================



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9305163d1348dcd64fa89a67bb87e6f38d1476d9...b8388870d0ddbbfb7fa5215d4a7bcca9407ca1b4

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9305163d1348dcd64fa89a67bb87e6f38d1476d9...b8388870d0ddbbfb7fa5215d4a7bcca9407ca1b4
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list