[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