Sorry for the delay! I decided to take some time and write a proper testcase :-)

I have attached patchset V2, let me know if anything is missing. Many Thanks!

Best Regards,
Luca
________________________________________
Da: LIU Hao
Inviato: Sabato, 20 Dicembre, 2025 17:38
A: Luca Bacci; [email protected]
Oggetto: Re: R: [Mingw-w64-public] Implement safe stdio flush on exit

在 2025-12-20 05:42, Luca Bacci 写道:
> Hello!
>
>> I think you can just do `_crt_atexit((_PVFV)(intptr_t) _flushall)` without 
>> introducing a new function
>
> Yeah, consider however that _flushall flushes both output and input streams. 
> Flushing input streams seems to be a no-op on the UCRT, but probably discards 
> data on earlier CRT libraries.

Oh it makes sense. Please send a new patch with these changes:

1. The call to `atexit()` should not be outside `__native_startup_lock`. Maybe
    you can add it around the call to `setvbuf()` in your other patch.

2. `__mingw_safeStdioFlush()` isn't visible externally, so it need not be named
    with a `__mingw_` prefix.

3. Is it really necessary to check the result of `atexit()`? I think such errors
    may be ignored. (Also, even when you do want to handle errors, it's 
incorrect
    to return from the entry-point function; call `abort()` instead.)


--
Best regards,
LIU Hao
From eabf05c7d17281dd61dab99c5098e6e12117c8a8 Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Sat, 13 Dec 2025 13:48:26 +0100
Subject: [PATCH 1/2] crt: Implement safe stdio flush at exit

The Windows CRT flushes all stdio streams open for output
within its DllMain handler. This is not quite safe:

1. A DLL may acquire locks on process termination, which can
   cause instant termination.
2. The flushing operation itself must acquire locks, which goes
   against the rules for DllMain on process termination.

This commit adds an atexit handler that flushes all output streams
before ExitProcess is called. This should also help with execution
environments where ExitProcess behaves like TerminateProcess (see
AppPolicyGetProcessTerminationMethod).

Signed-off-by: Luca Bacci <[email protected]>
---
 mingw-w64-crt/crt/crtexe.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
index f23c5f0e..8768993b 100644
--- a/mingw-w64-crt/crt/crtexe.c
+++ b/mingw-w64-crt/crt/crtexe.c
@@ -77,6 +77,12 @@ __mingw_invalidParameterHandler (const wchar_t * 
__UNUSED_PARAM_1(expression),
 #endif
 }
 
+static void
+safe_flush (void)
+{
+  fflush (NULL);
+}
+
 static int __tmainCRTStartup (void);
 
 int WinMainCRTStartup (void);
@@ -178,6 +184,14 @@ __tmainCRTStartup (void)
         */
        setvbuf (stderr, NULL, _IONBF, 0);
 
+       /* The C RunTime library flushes stdio streams in response to
+        * DLL_PROCESS_DETACH. This is not entirely safe; other DLLs
+        * may cause instant termination during process shutdown.
+        * Here we add an exit handler to flush streams safely.
+        */
+       if (atexit (safe_flush) != 0)
+           abort ();
+
        _pei386_runtime_relocator ();
 #if defined(__x86_64__) && !defined(__SEH__)
        __mingw_init_ehandler ();
-- 
2.49.0.windows.1

From 5b5f4b83aa90d69f0279acc5ef5640c8aa7b70ae Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Tue, 23 Dec 2025 16:32:26 +0100
Subject: [PATCH 2/2] crt: Add test for safe stdio flush on exit

Signed-off-by: Luca Bacci <[email protected]>
---
 mingw-w64-crt/testcases/Makefile.am     |  10 ++
 mingw-w64-crt/testcases/libprocdetach.c |  27 +++++
 mingw-w64-crt/testcases/t_safe_flush.c  | 146 ++++++++++++++++++++++++
 3 files changed, 183 insertions(+)
 create mode 100644 mingw-w64-crt/testcases/libprocdetach.c
 create mode 100644 mingw-w64-crt/testcases/t_safe_flush.c

diff --git a/mingw-w64-crt/testcases/Makefile.am 
b/mingw-w64-crt/testcases/Makefile.am
index 14c59abc..f0d51f3b 100644
--- a/mingw-w64-crt/testcases/Makefile.am
+++ b/mingw-w64-crt/testcases/Makefile.am
@@ -40,6 +40,7 @@ testcase_progs = \
   t_nullptrexception \
   t_printf_g_width \
   t_readdir \
+  t_safe_flush \
   t_snprintf \
   t_snprintf0 \
   t_snprintf1 \
@@ -84,6 +85,12 @@ EXTRA_DIST = \
   t_stprintf_tmpl.h \
   t_swprintf_tmpl.h
 
+libprocdetach.dll: libprocdetach.o
+       $(LINK) -shared libprocdetach.o -Wl,--out-implib,[email protected]
+
+EXTRA_t_safe_flush_DEPENDENCIES = libprocdetach.dll
+t_safe_flush_LDADD = -L. -lprocdetach.dll
+
 tstmaincpp_SOURCES = tstmaincpp.cpp
 t_trycatch_SOURCES = t_trycatch.cpp
 t_trycatch_LDFLAGS = -static
@@ -106,3 +113,6 @@ XFAIL_TESTS = \
 
 # Include the complex math testcase fragment.
 include complex/Makefile.am
+
+clean-local:
+       rm -f *.dll *.dll.a
diff --git a/mingw-w64-crt/testcases/libprocdetach.c 
b/mingw-w64-crt/testcases/libprocdetach.c
new file mode 100644
index 00000000..b4d1ecc7
--- /dev/null
+++ b/mingw-w64-crt/testcases/libprocdetach.c
@@ -0,0 +1,27 @@
+#include <stdlib.h>
+#include <windows.h>
+
+typedef void (* callback_t)(void *);
+
+callback_t callback;
+void *user_data;
+
+__declspec(dllexport) void
+lib_proc_detach_set_callback(callback_t arg_callback, void *arg_user_data) {
+    callback = arg_callback;
+    user_data = arg_user_data;
+}
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
+
+BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID lpvReserved) {
+    switch (dwReason) {
+    case DLL_PROCESS_DETACH:
+        if (callback != NULL) {
+            callback(user_data);
+        }
+        break;
+    }
+
+    return TRUE;
+}
diff --git a/mingw-w64-crt/testcases/t_safe_flush.c 
b/mingw-w64-crt/testcases/t_safe_flush.c
new file mode 100644
index 00000000..dae4c934
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_safe_flush.c
@@ -0,0 +1,146 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <process.h>
+#include <io.h>
+#include <fcntl.h>
+#include <windows.h>
+#include <versionhelpers.h>
+
+#define STRING "hello world!"
+
+/* child process */
+
+CRITICAL_SECTION cs;
+
+static unsigned int thread_main(void *user_data) {
+    HANDLE handle_event = (HANDLE) user_data;
+
+    /* This thread acquires a critical section and then
+     * sleeps indefinitely. This way the critical section
+     * will be orphaned during process termination
+     */
+    EnterCriticalSection(&cs);
+
+    /* Signal the main thread that it can continue */
+    SetEvent(handle_event);
+    Sleep(INFINITE);
+
+    return 0;
+}
+
+static void spawn_thread(void) {
+    HANDLE handle_event;
+
+    assert(handle_event = CreateEvent(NULL, TRUE, FALSE, NULL));
+    assert(_beginthreadex(NULL, 0, thread_main, handle_event, 0, NULL) != 0);
+    assert(WaitForSingleObject(handle_event, INFINITE) == WAIT_OBJECT_0);
+    assert(CloseHandle(handle_event));
+}
+
+typedef void (* callback_t)(void *);
+
+__declspec(dllimport)
+void lib_proc_detach_set_callback(callback_t, void*);
+
+static void acquire_critical_section(void *user_data) {
+    EnterCriticalSection(&cs);
+}
+
+int child_main(void) {
+    InitializeCriticalSection(&cs);
+
+    /* Spawn the secondary thread which acquires the critical
+     * section (and never releases it)
+     */
+    spawn_thread();
+
+    /* Make our helper DLL acquire the critical section on
+     * DLL_PROCESS_DETACH. This will cause instant termination
+     * of the process
+     */
+    lib_proc_detach_set_callback(acquire_critical_section, NULL);
+
+    /* Set stdout to full-buffering mode (useful in case the test
+     * is run directly from a terminal)
+     */
+    assert(setvbuf(stdout, NULL, _IOFBF, 4096) == 0);
+
+    /* Print a string so that stdout needs flushing at exit */
+    fputs(STRING, stdout);
+    assert(!ferror(stdout));
+
+    /* The C RunTime library flushes streams from its DllMain
+     * handler on DLL_PROCESS_DETACH. However that DllMain
+     * won't run becuase another DLL triggers instant termination.
+     * stdout can only be flushed by the safe flush implemented
+     * in mingw-w64-crt
+     */
+
+    return 0;
+}
+
+/* parent process */
+
+int parent_main(const char *argv0) {
+    int exit_code;
+    int pipefd[2];
+    int back_outfd;
+    intptr_t process;
+    size_t size = 0;
+    char buf[512];
+
+    assert(_pipe(pipefd, sizeof(buf), O_NOINHERIT) == 0);
+
+    /* set stdout fd to write side of pipe, will be used by _spawnl() */
+    assert((back_outfd = dup(STDOUT_FILENO)) >= 0);
+    assert(dup2(pipefd[1], STDOUT_FILENO) == 0);
+    assert(close(pipefd[1]) == 0);
+
+    /* spawn child process to read stdout */
+    process = _spawnl(_P_NOWAIT, _pgmptr, argv0, "safe_flush_test", NULL);
+
+    /* revert back stdout fd */
+    assert(dup2(back_outfd, STDOUT_FILENO) == 0);
+    assert(close(back_outfd) == 0);
+
+    assert(process != -1);
+
+    /* read data as required */
+    while (size < strlen (STRING)) {
+        int delta = read(pipefd[0], buf + size, sizeof(buf) - size);
+        assert(delta > 0);
+        size += delta;
+    }
+
+    /* no more data is available */
+    assert(read(pipefd[0], buf + size, sizeof(buf) - size) == 0);
+    close(pipefd[0]);
+
+    /* wait until child process exits */
+    assert(_cwait(&exit_code, process, _WAIT_CHILD) == process);
+    assert(exit_code == 0);
+
+    assert(size == strlen (STRING));
+    assert(memcmp(buf, STRING, strlen (STRING)) == 0);
+
+    return 0;
+}
+
+/* common */
+
+int main(int argc, char *argv[]) {
+    if (!IsWindowsVistaOrGreater()) {
+        /* This test requires at least Windows Vista (where
+         * CRITICAL_SECTIONs were made shutdown-aware)
+         */
+        return 77;  /* skip */
+    }
+
+    if (argc != 2 || strcmp(argv[1], "safe_flush_test") != 0) {
+        return parent_main(argv[0]);
+    }
+
+    return child_main();
+}
-- 
2.49.0.windows.1

_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to