[vlc-commits] [Git][videolan/vlc][master] 5 commits: process: win32: use per-direction events for overlapped I/O

Steve Lhomme (@robUx4) gitlab at videolan.org
Wed Aug 5 08:49:04 UTC 2026



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
f105ca3f by Gabriel Lafond-Thenaille at 2026-08-05T08:08:59+00:00
process: win32: use per-direction events for overlapped I/O

- - - - -
9293d2ae by Gabriel Lafond-Thenaille at 2026-08-05T08:08:59+00:00
process: win32: handle overlapped I/O cancellation races

  Drain the pending operation on every path leaving vlc_process_WindowsPoll():
  until it completes.

  Cancellation can race with completion, so check GetOverlappedResult() after
  CancelIoEx() and report what was transferred.

  A closed pipe is end-of-stream on reads, reported as a zero-byte transfer
  like recv() does. On writes it is an error: a zero-byte success makes the
  caller retry forever, so report EPIPE like send() does.

- - - - -
5ec35330 by Gabriel Lafond-Thenaille at 2026-08-05T08:08:59+00:00
process: add vlc_process_Kill()

Terminate the process and abort its I/O without freeing it, so a thread
blocked in vlc_process_fd_Read() or vlc_process_fd_Write() can be unblocked
before being joined and vlc_process_Terminate() called.

Add the missing stub in missing.c for the targets without process spawning.

On win32, keep a handle on the process: the pid is reusable as soon as the
process exits, so terminating it by pid can hit an unrelated process. After
a kill, a read reports end-of-stream and a write fails with EPIPE, like the
POSIX shutdown() does.

- - - - -
d5b38952 by Gabriel Lafond-Thenaille at 2026-08-05T08:08:59+00:00
preparser: external: kill the processes before joining the threads

A thread blocked on process I/O does not wait on the queue, so the
"closing" broadcast cannot wake it up: the join blocks until the request
timeout expires, or forever when the preparser has no timeout.

- - - - -
5f3b539f by Gabriel Lafond-Thenaille at 2026-08-05T08:08:59+00:00
process: vlc_process_fd_Write doc typo

- - - - -


6 changed files:

- include/vlc_process.h
- src/libvlccore.sym
- src/missing.c
- src/posix/process.c
- src/preparser/external.c
- src/win32/process.c


Changes:

=====================================
include/vlc_process.h
=====================================
@@ -40,12 +40,36 @@
 VLC_API struct vlc_process *
 vlc_process_Spawn(const char *path, int argc, const char *const *argv);
 
+/**
+ * Kill a process and abort its I/O.
+ *
+ * Sends a termination signal to the process and shuts down the communication
+ * channel, so that any in-flight or subsequent I/O on @p process completes
+ * promptly instead of blocking: vlc_process_fd_Read() reports end-of-stream
+ * and vlc_process_fd_Write() fails with EPIPE.
+ *
+ * This does not free @p process: vlc_process_Terminate() must still be called
+ * for that. It can be called multiple times.
+ *
+ * @param [in]  process     Pointer to the vlc_process instance. Must not be
+ *                          NULL.
+ */
+VLC_API void
+vlc_process_Kill(struct vlc_process *process);
+
 /**
  * Stop a vlc_process and wait for its termination.
  *
  * Closes its file descriptors, and waits for it to exit. Optionally sends a
  * termination signal to the process,
  *
+ * @warning This must not be called concurrently with vlc_process_fd_Read() or
+ *          vlc_process_fd_Write() on the same @p process. It closes the
+ *          underlying pipe/socket and frees @p process, so the caller must
+ *          ensure that any thread performing I/O has returned first to avoid a
+ *          use-after-free. Use vlc_process_Kill() to unblock such a thread,
+ *          then join it before calling this.
+ *
  * @param [in]  process        Pointer to the vlc_process instance. Must not
  *                             be NULL.
  * @param [in]  kill_process   Whether to forcibly terminate the process
@@ -67,6 +91,10 @@ vlc_process_Terminate(struct vlc_process *process, bool kill_process);
  * a platform-specific implementation is used due to limitations with poll on
  * non-socket handles.
  *
+ * @warning At most one thread may call this function on a given @p process at
+ *          a time. Reading from one thread while another one writes is
+ *          supported, but two concurrent readers are not.
+ *
  * @param [in]  process     Pointer to the vlc_process instance.
  * @param [out] buf         Buffer where the read data will be stored.
  * @param [in]  size        Maximum number of bytes to read.
@@ -90,13 +118,17 @@ vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size,
  * a platform-specific implementation is used due to limitations with poll on
  * non-socket handles.
  *
+ * @warning At most one thread may call this function on a given @p process at
+ *          a time. Writing from one thread while another one reads is
+ *          supported, but two concurrent writers are not.
+ *
  * @param [in]  process     Pointer to the vlc_process instance.
  * @param [in]  buf         Buffer containing the data to write.
  * @param [in]  size        Number of bytes to write.
  * @param [in]  timeout_ms  Timeout in milliseconds to wait for the pipe to be
  *                          writable.
  *
- * @return      The number of bytes read on success,
+ * @return      The number of bytes written on success,
  *              -1 on error, and errno is set to indicate the error.
  */
 VLC_API ssize_t


=====================================
src/libvlccore.sym
=====================================
@@ -1068,6 +1068,7 @@ vlc_preparser_req_GetItem
 vlc_preparser_req_Release
 vlc_preparser_Delete
 vlc_process_Spawn
+vlc_process_Kill
 vlc_process_Terminate
 vlc_process_fd_Read
 vlc_process_fd_Write


=====================================
src/missing.c
=====================================
@@ -190,6 +190,13 @@ vlc_process_Spawn(const char *path, int argc, const char *const *argv)
     return NULL;
 }
 
+void
+vlc_process_Kill(struct vlc_process *process)
+{
+    VLC_UNUSED(process);
+    vlc_assert_unreachable();
+}
+
 int
 vlc_process_Terminate(struct vlc_process *process, bool kill_process)
 {


=====================================
src/posix/process.c
=====================================
@@ -120,13 +120,22 @@ end:
     return process;
 }
 
+VLC_API void
+vlc_process_Kill(struct vlc_process *process)
+{
+    assert(process != NULL);
+
+    kill(process->pid, SIGTERM);
+    shutdown(process->fd, SHUT_RDWR);
+}
+
 VLC_API int
 vlc_process_Terminate(struct vlc_process *process, bool kill_process)
 {
     assert(process != NULL);
 
     if (kill_process) {
-        kill(process->pid, SIGTERM);
+        vlc_process_Kill(process);
     }
 
     shutdown(process->fd, SHUT_RDWR);


=====================================
src/preparser/external.c
=====================================
@@ -896,12 +896,18 @@ preparser_pool_Delete(struct preparser_process_pool *pool)
     /* "closing" is now true, this will wake up threads */
     vlc_cond_broadcast(&pool->queue_wait);
 
+    struct preparser_process_thread *thread = NULL;
+    vlc_list_foreach(thread, &pool->threads, node) {
+        if (thread->process != NULL) {
+            vlc_process_Kill(thread->process);
+        }
+    }
+
     vlc_mutex_unlock(&pool->lock);
 
     /* The threads list may not be written at this point, so it is safe to read
      * it without mutex locked (the mutex must be released to join the
      * threads). */
-    struct preparser_process_thread *thread = NULL;
     vlc_list_foreach(thread, &pool->threads, node) {
         vlc_join(thread->thread, NULL);
         if (thread->process != NULL) {


=====================================
src/win32/process.c
=====================================
@@ -14,6 +14,7 @@
 #include <windows.h>
 #include <io.h>
 #include <assert.h>
+#include <stdatomic.h>
 
 #include <vlc_common.h>
 #include <vlc_process.h>
@@ -38,38 +39,88 @@ vlc_process_WindowsPoll_i11e_wake(void *opaque)
     QueueUserAPC(vlc_process_WindowsPoll_i11e_wake_self, th, 0);
 }
 
+/**
+ * Map the result of a failed overlapped operation to an errno-like value.
+ */
+static int
+vlc_process_WindowsMapIoResult(DWORD error, DWORD *bytes, bool is_read,
+                               int dflt)
+{
+    if (is_read) {
+        if (error == ERROR_BROKEN_PIPE || error == ERROR_HANDLE_EOF) {
+            *bytes = 0;
+            return VLC_SUCCESS;
+        }
+    } else if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA ||
+               error == ERROR_PIPE_NOT_CONNECTED) {
+        return EPIPE;
+    }
+    return dflt;
+}
+
+/**
+ * Cancel a pending overlapped operation and wait for its completion.
+ */
+static int vlc_process_WindowsCancelPoll(HANDLE hFd, LPOVERLAPPED lpoverlapped,
+                                         DWORD *bytes, DWORD size,
+                                         bool is_read, int cancel_err)
+{
+    CancelIoEx(hFd, lpoverlapped);
+    if (GetOverlappedResult(hFd, lpoverlapped, bytes, TRUE)) {
+        return VLC_SUCCESS;
+    }
+
+    DWORD error = GetLastError();
+    if (error == ERROR_OPERATION_ABORTED) {
+        if (*bytes > 0 && *bytes <= size) {
+            return VLC_SUCCESS;
+        }
+        *bytes = 0;
+        return cancel_err;
+    }
+    return vlc_process_WindowsMapIoResult(error, bytes, is_read, EINVAL);
+}
+
 static int
 vlc_process_WindowsPoll(HANDLE hFd, LPOVERLAPPED lpoverlapped, DWORD *bytes,
-                        vlc_tick_t timeout_ms)
+                        DWORD size, bool is_read, vlc_tick_t timeout_ms)
 {
     HANDLE th;
     if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
                          GetCurrentProcess(), &th, 0, FALSE,
                          DUPLICATE_SAME_ACCESS)) {
-        return ENOMEM;
+        return vlc_process_WindowsCancelPoll(hFd, lpoverlapped, bytes, size,
+                                             is_read, ENOMEM);
     }
     vlc_interrupt_register(vlc_process_WindowsPoll_i11e_wake, th);
     DWORD waitResult = WaitForSingleObjectEx(lpoverlapped->hEvent,
                                              timeout_ms, TRUE);
-    vlc_interrupt_unregister();
+    int interrupted = vlc_interrupt_unregister();
     CloseHandle(th);
+
+    if (interrupted != 0) {
+        return vlc_process_WindowsCancelPoll(hFd, lpoverlapped, bytes, size,
+                                             is_read, EINTR);
+    }
+
     switch (waitResult) {
         case WAIT_OBJECT_0:
             if (GetOverlappedResult(hFd, lpoverlapped, bytes, FALSE)) {
                 return VLC_SUCCESS;
-            } else {
-                return EINVAL;
             }
+            return vlc_process_WindowsMapIoResult(GetLastError(), bytes,
+                                                  is_read, EINVAL);
         case WAIT_TIMEOUT:
             /* Timeout occurred */
-            CancelIo(hFd); /* Cancel the I/O operation */
-            return ETIMEDOUT;
+            return vlc_process_WindowsCancelPoll(hFd, lpoverlapped, bytes,
+                                                 size, is_read, ETIMEDOUT);
         case WAIT_IO_COMPLETION:
             /* Interrupt occurred */
-            CancelIo(hFd); /* Cancel the I/O operation */
-            return EINTR;
+            return vlc_process_WindowsCancelPoll(hFd, lpoverlapped, bytes,
+                                                 size, is_read, EINTR);
         default:
-            return EINVAL;
+            return vlc_process_WindowsCancelPoll(hFd, lpoverlapped, bytes,
+                                                 size, is_read, EINVAL);
     }
 }
 
@@ -77,10 +128,15 @@ struct vlc_process {
     /* Pid of the linked process */
     pid_t pid;
 
+    HANDLE hProcess;
+
     int fd_in;
     int fd_out;
 
-    HANDLE hEvent;
+    HANDLE hEventRead;
+    HANDLE hEventWrite;
+
+    atomic_bool killed;
 };
 
 struct vlc_process*
@@ -105,7 +161,22 @@ vlc_process_Spawn(const char *path, int argc, const char *const *argv)
 
     process->fd_in = -1;
     process->fd_out = -1;
-    process->hEvent = INVALID_HANDLE_VALUE;
+    process->hProcess = NULL;
+    process->hEventRead = NULL;
+    process->hEventWrite = NULL;
+    atomic_init(&process->killed, false);
+
+    process->hEventRead = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (process->hEventRead == NULL) {
+        ret = VLC_ENOMEM;
+        goto end;
+    }
+
+    process->hEventWrite = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (process->hEventWrite == NULL) {
+        ret = VLC_ENOMEM;
+        goto end;
+    }
 
     ret = vlc_pipe(fds);
     if (ret != 0) {
@@ -121,12 +192,6 @@ vlc_process_Spawn(const char *path, int argc, const char *const *argv)
     extfd_in = fds[0];
     process->fd_out = fds[1];
 
-    process->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-    if (process->hEvent == INVALID_HANDLE_VALUE) {
-        errno = EINVAL;
-        goto end;
-    }
-
     int stderr_fd = -1;
     intptr_t h_err = _get_osfhandle(STDERR_FILENO);
     if (h_err != -1 && h_err != -2) {
@@ -152,6 +217,8 @@ vlc_process_Spawn(const char *path, int argc, const char *const *argv)
         goto end;
     }
 
+    process->hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, process->pid);
+
 end:
 
     free(args);
@@ -170,8 +237,11 @@ end:
         if (process->fd_out != -1) {
             vlc_close(process->fd_out);
         }
-        if (process->hEvent != INVALID_HANDLE_VALUE) {
-            CloseHandle(process->hEvent);
+        if (process->hEventRead != NULL) {
+            CloseHandle(process->hEventRead);
+        }
+        if (process->hEventWrite != NULL) {
+            CloseHandle(process->hEventWrite);
         }
         free(process);
         return NULL;
@@ -179,24 +249,45 @@ end:
     return process;
 }
 
+void
+vlc_process_Kill(struct vlc_process *process)
+{
+    assert(process != NULL);
+
+    if (process->hProcess != NULL) {
+        TerminateProcess(process->hProcess, 15);
+    }
+
+    atomic_store(&process->killed, true);
+
+    intptr_t h_in = _get_osfhandle(process->fd_in);
+    if (h_in != -1 && h_in != -2) {
+        CancelIoEx((HANDLE)h_in, NULL);
+    }
+    intptr_t h_out = _get_osfhandle(process->fd_out);
+    if (h_out != -1 && h_out != -2) {
+        CancelIoEx((HANDLE)h_out, NULL);
+    }
+}
+
 int
 vlc_process_Terminate(struct vlc_process *process, bool kill_process)
 {
     assert(process != NULL);
 
     if (kill_process) {
-        HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, process->pid);
-        if (hProcess) {
-            TerminateProcess(hProcess, 15);
-            CloseHandle(hProcess);
-        }
+        vlc_process_Kill(process);
     }
 
     vlc_close(process->fd_in);
     vlc_close(process->fd_out);
-    CloseHandle(process->hEvent);
+    CloseHandle(process->hEventRead);
+    CloseHandle(process->hEventWrite);
 
     int status = vlc_waitpid(process->pid);
+    if (process->hProcess != NULL) {
+        CloseHandle(process->hProcess);
+    }
     process->pid = 0;
     free(process);
     return status;
@@ -209,6 +300,11 @@ vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size,
     assert(process != NULL);
     assert(buf != NULL);
 
+    /* The channel is shut down, report end-of-stream like POSIX does. */
+    if (atomic_load(&process->killed)) {
+        return 0;
+    }
+
     intptr_t h = _get_osfhandle(process->fd_in);
     if (h == -1) {
         errno = EINVAL;
@@ -218,27 +314,38 @@ vlc_process_fd_Read(struct vlc_process *process, uint8_t *buf, size_t size,
 
     DWORD bytes = 0;
     OVERLAPPED overlapped = {0};
-    overlapped.hEvent = process->hEvent;
+    overlapped.hEvent = process->hEventRead;
 
-    BOOL ret = FALSE;
-    ret = ReadFile(hFd, buf, size, &bytes, &overlapped);
+    /* The event is reused across calls, drop any leftover completion. */
+    ResetEvent(process->hEventRead);
 
-    int err = VLC_SUCCESS;
-
-    if (ret) {
-        return bytes;
+    int err;
+    if (ReadFile(hFd, buf, size, NULL, &overlapped)) {
+        if (GetOverlappedResult(hFd, &overlapped, &bytes, FALSE)) {
+            err = VLC_SUCCESS;
+        } else {
+            err = vlc_process_WindowsMapIoResult(GetLastError(), &bytes, true,
+                                                 EINVAL);
+        }
     } else {
         DWORD error = GetLastError();
         if (error == ERROR_IO_PENDING) {
+            if (atomic_load(&process->killed)) {
+                CancelIoEx(hFd, &overlapped);
+            }
             err = vlc_process_WindowsPoll(hFd, &overlapped, &bytes,
-                                          timeout_ms);
+                                          (DWORD)size, true, timeout_ms);
         } else {
-            err = EINVAL;
+            err = vlc_process_WindowsMapIoResult(error, &bytes, true, EINVAL);
         }
     }
+
     if (err == VLC_SUCCESS) {
         return bytes;
     }
+    if (atomic_load(&process->killed)) {
+        return 0;
+    }
     errno = err;
     return -1;
 }
@@ -250,6 +357,11 @@ vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, size_t siz
     assert(process != NULL);
     assert(buf != NULL);
 
+    if (atomic_load(&process->killed)) {
+        errno = EPIPE;
+        return -1;
+    }
+
     intptr_t h = _get_osfhandle(process->fd_out);
     if (h == -1) {
         errno = EINVAL;
@@ -259,27 +371,35 @@ vlc_process_fd_Write(struct vlc_process *process, const uint8_t *buf, size_t siz
 
     DWORD bytes = 0;
     OVERLAPPED overlapped = {0};
-    overlapped.hEvent = process->hEvent;
+    overlapped.hEvent = process->hEventWrite;
 
-    BOOL ret = FALSE;
-    ret = WriteFile(hFd, buf, size, &bytes, &overlapped);
+    /* The event is reused across calls, drop any leftover completion. */
+    ResetEvent(process->hEventWrite);
 
-    int err = VLC_SUCCESS;
-
-    if (ret) {
-        return bytes;
+    int err;
+    if (WriteFile(hFd, buf, size, NULL, &overlapped)) {
+        if (GetOverlappedResult(hFd, &overlapped, &bytes, FALSE)) {
+            err = VLC_SUCCESS;
+        } else {
+            err = vlc_process_WindowsMapIoResult(GetLastError(), &bytes, false,
+                                                 EINVAL);
+        }
     } else {
         DWORD error = GetLastError();
         if (error == ERROR_IO_PENDING) {
+            if (atomic_load(&process->killed)) {
+                CancelIoEx(hFd, &overlapped);
+            }
             err = vlc_process_WindowsPoll(hFd, &overlapped, &bytes,
-                                          timeout_ms);
+                                          (DWORD)size, false, timeout_ms);
         } else {
-            err = EINVAL;
+            err = vlc_process_WindowsMapIoResult(error, &bytes, false, EINVAL);
         }
     }
+
     if (err == VLC_SUCCESS) {
         return bytes;
     }
-    errno = err;
+    errno = atomic_load(&process->killed) ? EPIPE : err;
     return -1;
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/1ea06348e728887a501a7b07b6ea89b2068f84be...5f3b539f668fae5a7f2d07d75f496eff44a8aaa5

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/1ea06348e728887a501a7b07b6ea89b2068f84be...5f3b539f668fae5a7f2d07d75f496eff44a8aaa5
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list