SDL: Use non-blocking pipes for process I/O on Windows

From 34b2f4ffcab7b054fb9dbac93717c3695221a184 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 14 Sep 2024 19:18:08 -0700
Subject: [PATCH] Use non-blocking pipes for process I/O on Windows

Fixes https://github.com/libsdl-org/SDL/issues/10846
---
 src/file/SDL_iostream.c                  | 29 ++++++++++++++++++++----
 src/process/windows/SDL_windowsprocess.c | 13 +++++++++++
 2 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c
index 0d9f39fbb055a..41ac90fd84267 100644
--- a/src/file/SDL_iostream.c
+++ b/src/file/SDL_iostream.c
@@ -202,8 +202,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
 
     if (total_need < READAHEAD_BUFFER_SIZE) {
         if (!ReadFile(iodata->h, iodata->data, READAHEAD_BUFFER_SIZE, &bytes, NULL)) {
-            if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
+            DWORD error = GetLastError();
+            switch (error) {
+            case ERROR_BROKEN_PIPE:
+            case ERROR_HANDLE_EOF:
+                break;
+            case ERROR_NO_DATA:
+                *status = SDL_IO_STATUS_NOT_READY;
+                break;
+            default:
                 WIN_SetError("Error reading from datastream");
+                break;
             }
             return 0;
         }
@@ -214,8 +223,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
         total_read += read_ahead;
     } else {
         if (!ReadFile(iodata->h, ptr, (DWORD)total_need, &bytes, NULL)) {
-            if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
+            DWORD error = GetLastError();
+            switch (error) {
+            case ERROR_BROKEN_PIPE:
+            case ERROR_HANDLE_EOF:
+                break;
+            case ERROR_NO_DATA:
+                *status = SDL_IO_STATUS_NOT_READY;
+                break;
+            default:
                 WIN_SetError("Error reading from datastream");
+                break;
             }
             return 0;
         }
@@ -227,7 +245,6 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
 static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
 {
     IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
-    const size_t total_bytes = size;
     DWORD bytes;
 
     if (iodata->left) {
@@ -248,11 +265,13 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
         }
     }
 
-    if (!WriteFile(iodata->h, ptr, (DWORD)total_bytes, &bytes, NULL)) {
+    if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) {
         WIN_SetError("Error writing to datastream");
         return 0;
     }
-
+    if (bytes == 0 && size > 0) {
+        *status = SDL_IO_STATUS_NOT_READY;
+    }
     return bytes;
 }
 
diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c
index 6ad61403e7d95..945edf716588c 100644
--- a/src/process/windows/SDL_windowsprocess.c
+++ b/src/process/windows/SDL_windowsprocess.c
@@ -200,6 +200,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID
     HANDLE stdin_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
     HANDLE stdout_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
     HANDLE stderr_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
+    DWORD pipe_mode = PIPE_NOWAIT;
     bool result = false;
 
     // Keep the malloc() before exec() so that an OOM won't run a process at all
@@ -263,6 +264,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID
             stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE;
             goto done;
         }
+        if (!SetNamedPipeHandleState(stdin_pipe[WRITE_END], &pipe_mode, NULL, NULL)) {
+            WIN_SetError("SetNamedPipeHandleState()");
+            goto done;
+        }
         if (!SetHandleInformation(stdin_pipe[WRITE_END], HANDLE_FLAG_INHERIT, 0) ) {
             WIN_SetError("SetHandleInformation()");
             goto done;
@@ -296,6 +301,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID
             stdout_pipe[WRITE_END] = INVALID_HANDLE_VALUE;
             goto done;
         }
+        if (!SetNamedPipeHandleState(stdout_pipe[READ_END], &pipe_mode, NULL, NULL)) {
+            WIN_SetError("SetNamedPipeHandleState()");
+            goto done;
+        }
         if (!SetHandleInformation(stdout_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) {
             WIN_SetError("SetHandleInformation()");
             goto done;
@@ -338,6 +347,10 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID
                 stderr_pipe[WRITE_END] = INVALID_HANDLE_VALUE;
                 goto done;
             }
+            if (!SetNamedPipeHandleState(stderr_pipe[READ_END], &pipe_mode, NULL, NULL)) {
+                WIN_SetError("SetNamedPipeHandleState()");
+                goto done;
+            }
             if (!SetHandleInformation(stderr_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) {
                 WIN_SetError("SetHandleInformation()");
                 goto done;