Hi Takashi,

On 6/17/2025 5:54 AM, Takashi Yano via Cygwin wrote:
Hi,

If system() is called in parallel in threads, system()
fails with exit code 127.

Just compile pthread_system.c attached and run.

I believe system() is multi-thread safe so the STC
should work.

I regret to inform that system() is not currently MT-safe. I discovered this a few years ago when porting YAFU (Yet Another Factoring Utility).

I ended up supplying a wrapper that serialized the system() call itself but allowed multiple wrappers (and thus child processes) to be running at the same time. I never got around to supplying a fix for Cygwin. I've attached the code.
HTH somehow,

..mark
/*
 * safe_system.c
 * Thread-safe version of system() for platforms needing it, like Cygwin.
 *
 * Copyright © 2018 Mark A. Geisert.  Placed in public domain.
 *
 * POSIX states that system() need not be thread-safe.  On Cygwin it isn't.
 * POSIX also states that system() cannot return to its caller until the
 * task it has spawned has completed.
 *
 * If a Cygwin process uses system() simultaneously from multiple pthreads,
 * the Cygwin DLL reports internal errors and often hangs the process.  This
 * is easily worked around by wrapping system() calls in a critical section.
 * However, doing this has the effect of running the (potentially long
 * duration) task spawned by system() while holding the critical section the
 * entire time.  This behavior ends up serializing the pthreads that use
 * system() and the benefits of parallelization are lost.
 *
 * After investigation of various approaches, what is done here is to launch
 * the spawned task as a background task, obtain its pid, and wait for that
 * pid to become invalid (which indicates the task has completed or aborted).
 *
 * Unfortunately, Plan A: waiting for the task with the usual waitpid() or
 * wait() cannot be done because the task of interest is not a direct child
 * of the running process.  There is a shell process interposed, as required
 * by POSIX for system() usage.
 *
 * Implemented here is Plan C: a Windows handle to the spawned task/process
 * (not the shell) is waited upon.  On platforms other than Windows or Cygwin,
 * Plan B described below might be workable.
 */

#if defined(__CYGWIN__)

#include <dlfcn.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/cygwin.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

//XXX: CODE WRITTEN AND TESTED ON x86_64 AND HAS NOT BEEN TESTED ON x86

volatile int initializing;   // 0: not yet, 1: in progress, 2: initialized
CRITICAL_SECTION systemcrit; // critical section to serialize system() use
volatile int usecount;       // something to keep tracking pidfiles separated

int
safe_system(const char *cmd)
{
    // Initialize in thread-safe manner
    if (0 == InterlockedCompareExchange(&initializing, 1, 0)) {
        InitializeCriticalSection(&systemcrit);
        initializing++;
    }
    else while (1 == initializing) {
        usleep(10000); // 10ms
    }

    // Allocate space for name of pidfile
    char *pidfilename = alloca(64);

    // Allocate space for command buf including glue for the tracking pidfile
    char *buf = alloca(strlen(cmd) + 128);

    // Wrap system() call in a critical section as it's not thread-safe
    EnterCriticalSection(&systemcrit);

    // Set up the complete augmented command string
    sprintf(pidfilename, "_yafu_system_.%d", usecount++);
    char *ptr = stpcpy(buf, cmd);
    // The '&' puts task in background; that task's pid is captured in pidfile
    sprintf(ptr, " & echo -n $! > %s", pidfilename);

    // Call Cygwin's system() with augmented command string
#if 1
    fprintf(stderr, "*SYSTEM* >>%s<<\n", buf);
#endif
    int ret = system(buf);
    LeaveCriticalSection(&systemcrit);

    // Open pidfile identifying spawned task then read it, close it, delete it
    FILE *f = fopen(pidfilename, "r");
    if (!f)
        return -1; // bail if unable to open pidfile
    fread(buf, 1, 80, f);
    fclose(f);
    remove(pidfilename);
    pid_t pid = atoi(buf);

    // Plan A: wait() for spawned task. Doesn't work as task is not a child.
    // Plan B: use kill(pid, 0) to test pid for validity. Works, but ugly.
    // Plan C: (Windows only) open a handle to task and sync on it. Perfect.
    DWORD winpid = cygwin_internal(CW_CYGWIN_PID_TO_WINPID, pid);
    HANDLE h = OpenProcess(SYNCHRONIZE, FALSE, winpid);
    if (h == INVALID_HANDLE_VALUE)
        return -1; // bail if unable to open spawned task/process
    WaitForSingleObject(h, INFINITE);
    CloseHandle(h);

    return ret;
}

#else

int
safe_system(const char *cmd)
{
    return system(cmd);
}

#endif
-- 
Problem reports:      https://cygwin.com/problems.html
FAQ:                  https://cygwin.com/faq/
Documentation:        https://cygwin.com/docs.html
Unsubscribe info:     https://cygwin.com/ml/#unsubscribe-simple

Reply via email to