Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legerch make read non blocking async option #79

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
os: [ubuntu-latest, windows-latest, macos-11, macos-14]
type: [Debug, RelWithDebInfo, MinSizeRel, Release]
compiler: [default, clang, gcc]
compiler: [default, clang, gcc, tcc]
exclude:
- {os: "macOS-latest", compiler: "clang"}
- {os: "macOS-latest", compiler: "gcc"}
- {os: "macos-11", compiler: "clang"}
- {os: "macos-11", compiler: "gcc"}
- {os: "macos-11", compiler: "tcc"}
- {os: "macos-14", compiler: "clang"}
- {os: "macos-14", compiler: "gcc"}
- {os: "macos-14", compiler: "tcc"}
- {os: "ubuntu-latest", compiler: "default"}
- {os: "ubuntu-latest", compiler: "default"}
- {os: "windows-latest", compiler: "gcc"}
- {os: "windows-latest", compiler: "tcc"}
runs-on: ${{ matrix.os }}

steps:
Expand All @@ -28,7 +34,7 @@ jobs:

- name: Setup dependencies (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get install -y gcc-10 g++-10 clang
run: sudo apt-get install -y gcc-10 g++-10 clang tcc

- name: Setup dependencies (MinGW)
if: matrix.compiler == 'gcc' && startsWith(matrix.os, 'windows')
Expand All @@ -46,6 +52,12 @@ jobs:
working-directory: ${{github.workspace}}/build
run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10

- name: Configure CMake with TCC (Ubuntu)
shell: bash
if: matrix.compiler == 'tcc' && startsWith(matrix.os, 'ubuntu')
working-directory: ${{github.workspace}}/build
run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=tcc -DCMAKE_CXX_COMPILER=g++-10

- name: Configure CMake with MinGW
shell: bash
if: matrix.compiler == 'gcc' && startsWith(matrix.os, 'windows')
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Sanitizers

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest]
type: [Debug, RelWithDebInfo, MinSizeRel, Release]
sanitizer: [address, memory, undefined]
exclude:
- {os: "macOS-latest", sanitizer: "memory"}
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build

- name: Setup dependencies
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get install -y clang

- name: Configure CMake (macOS)
shell: bash
if: startsWith(matrix.os, 'macOS')
working-directory: ${{github.workspace}}/build
run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DSUBPROCESS_USE_SANITIZER=${{ matrix.sanitizer }}

- name: Configure CMake with Clang (Ubuntu)
shell: bash
if: startsWith(matrix.os, 'ubuntu')
working-directory: ${{github.workspace}}/build
run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSUBPROCESS_USE_SANITIZER=${{ matrix.sanitizer }}

- name: Build
working-directory: ${{github.workspace}}/build
shell: bash
run: cmake --build . --config ${{ matrix.type }}

- name: Test
working-directory: ${{github.workspace}}/build
shell: bash
run: if [ "${{ matrix.os }}" == "windows-latest" ] && [ "${{ matrix.compiler }}" != "gcc" ]; then cd ${{ matrix.type }}; fi; ./subprocess_test

- name: Test with Multithreading
working-directory: ${{github.workspace}}/build
shell: bash
if: startsWith(matrix.os, 'windows')
run: if [ "${{ matrix.os }}" == "windows-latest" ] && [ "${{ matrix.compiler }}" != "gcc" ]; then cd ${{ matrix.type }}; fi; ./subprocess_mt_test
42 changes: 35 additions & 7 deletions subprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,23 @@
#pragma warning(pop)
#endif

#if defined(__TINYC__)
#define SUBPROCESS_ATTRIBUTE(a) __attribute((a))
#else
#define SUBPROCESS_ATTRIBUTE(a) __attribute__((a))
#endif

#if defined(_MSC_VER)
#define subprocess_pure
#define subprocess_weak __inline
#define subprocess_tls __declspec(thread)
#elif defined(__MINGW32__)
#define subprocess_pure __attribute__((pure))
#define subprocess_weak static __attribute__((used))
#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
#define subprocess_weak static SUBPROCESS_ATTRIBUTE(used)
#define subprocess_tls __thread
#elif defined(__clang__) || defined(__GNUC__)
#define subprocess_pure __attribute__((pure))
#define subprocess_weak __attribute__((weak))
#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__)
#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
#define subprocess_weak SUBPROCESS_ATTRIBUTE(weak)
#define subprocess_tls __thread
#else
#error Non clang, non gcc, non MSVC compiler found!
Expand Down Expand Up @@ -187,7 +193,7 @@ subprocess_weak int subprocess_terminate(struct subprocess_s *const process);
///
/// The only safe way to read from the standard output of a process during it's
/// execution is to use the `subprocess_option_enable_async` option in
/// conjuction with this method.
/// conjunction with this method.
subprocess_weak unsigned
subprocess_read_stdout(struct subprocess_s *const process, char *const buffer,
unsigned size);
Expand All @@ -201,7 +207,7 @@ subprocess_read_stdout(struct subprocess_s *const process, char *const buffer,
///
/// The only safe way to read from the standard error of a process during it's
/// execution is to use the `subprocess_option_enable_async` option in
/// conjuction with this method.
/// conjunction with this method.
subprocess_weak unsigned
subprocess_read_stderr(struct subprocess_s *const process, char *const buffer,
unsigned size);
Expand All @@ -224,6 +230,7 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process);
#endif

#if !defined(_WIN32)
#include <fcntl.h>
#include <signal.h>
#include <spawn.h>
#include <stdlib.h>
Expand Down Expand Up @@ -414,6 +421,13 @@ struct subprocess_s {
#pragma clang diagnostic pop
#endif

#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#endif
#endif

#if defined(_WIN32)
subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr);
int subprocess_create_named_pipe_helper(void **rd, void **wr) {
Expand Down Expand Up @@ -757,6 +771,7 @@ int subprocess_create_ex(const char *const commandLine[], int options,
int stdinfd[2];
int stdoutfd[2];
int stderrfd[2];
int fd, fd_flags;
pid_t child;
extern char **environ;
char *const empty_environment[1] = {SUBPROCESS_NULL};
Expand Down Expand Up @@ -886,6 +901,13 @@ int subprocess_create_ex(const char *const commandLine[], int options,
// Store the stdout read end
out_process->stdout_file = fdopen(stdoutfd[0], "rb");

// Should we use async behaviour ?
if (options & subprocess_option_enable_async) {
fd = fileno(out_process->stdout_file);
fd_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
}

if (subprocess_option_combined_stdout_stderr ==
(options & subprocess_option_combined_stdout_stderr)) {
out_process->stderr_file = out_process->stdout_file;
Expand Down Expand Up @@ -1173,6 +1195,12 @@ int subprocess_alive(struct subprocess_s *const process) {
return is_alive;
}

#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic pop
#endif
#endif

#if defined(__cplusplus)
} // extern "C"
#endif
Expand Down
15 changes: 14 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
#
# For more information, please refer to <http://unlicense.org/>

project(process)
project(subprocess)
cmake_minimum_required(VERSION 3.15)

set(SUBPROCESS_USE_SANITIZER "" CACHE STRING "Set which Clang Sanitizer to use")

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../)

add_executable(process_return_zero process_return_zero.c)
Expand All @@ -47,6 +49,7 @@ add_executable(process_fail_stackoverflow process_fail_stackoverflow.c)
add_executable(process_hung process_hung.c)
add_executable(process_stdout_data process_stdout_data.c)
add_executable(process_stdout_poll process_stdout_poll.c)
add_executable(process_stdout_poll_wait_first process_stdout_poll_wait_first.c)
add_executable(process_stderr_poll process_stderr_poll.c)
add_executable(process_stdout_large process_stdout_large.c)
add_executable(process_call_return_argc process_call_return_argc.c)
Expand All @@ -61,6 +64,11 @@ add_executable(subprocess_test
post_windows.cpp
)

if(NOT "${SUBPROCESS_USE_SANITIZER}" STREQUAL "")
target_compile_options(subprocess_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
target_link_options(subprocess_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
endif()

set(MSVC_FLAGS "/Wall /WX /wd4514 /wd4710 /wd4711 /wd5045")

if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
Expand Down Expand Up @@ -117,6 +125,11 @@ add_executable(subprocess_mt_test
post_windows.cpp
)

if(NOT "${SUBPROCESS_USE_SANITIZER}" STREQUAL "")
target_compile_options(subprocess_mt_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
target_link_options(subprocess_mt_test PUBLIC -fno-omit-frame-pointer -fsanitize=${SUBPROCESS_USE_SANITIZER})
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_compile_options(subprocess_mt_test PUBLIC "/MT")
Expand Down
78 changes: 68 additions & 10 deletions test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ __declspec(dllimport) int __stdcall SetEnvironmentVariableA(const char *,

#include "subprocess.h"

#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#endif
#endif

UTEST(create, subprocess_destroy_is_idempotent) {
const char *const commandLine[] = {"./process_return_zero", 0};
struct subprocess_s process;
Expand Down Expand Up @@ -284,8 +291,8 @@ UTEST(create, subprocess_return_special_argv) {
}

UTEST(create, subprocess_return_lpcmdline) {
const char *const commandLine[] = {"./process_return_lpcmdline",
"noquotes", "should be quoted", 0};
const char *const commandLine[] = {"./process_return_lpcmdline", "noquotes",
"should be quoted", 0};
struct subprocess_s process;
int ret = -1;
size_t cmp_index, index;
Expand All @@ -306,7 +313,8 @@ UTEST(create, subprocess_return_lpcmdline) {

// comparing from the back skips exe name
cmp_index = strlen(compare) - 1;
for (index = strlen(temp) - 1; index != 0 && cmp_index != 0; index--,cmp_index--) {
for (index = strlen(temp) - 1; index != 0 && cmp_index != 0;
index--, cmp_index--) {
if (temp[index] != compare[cmp_index])
ASSERT_TRUE(0);
}
Expand Down Expand Up @@ -594,11 +602,11 @@ UTEST(subprocess, read_stdout_async_small) {
ASSERT_EQ(0, subprocess_create(commandLine, subprocess_option_enable_async,
&process));

do {
while (subprocess_alive(&process)) {
bytes_read = subprocess_read_stdout(&process, data + index,
sizeof(data) - 1 - index);
index += bytes_read;
} while (bytes_read != 0);
}

ASSERT_EQ(13u, index);

Expand All @@ -624,11 +632,11 @@ UTEST(subprocess, read_stdout_async) {
ASSERT_EQ(0, subprocess_create(commandLine, subprocess_option_enable_async,
&process));

do {
while (subprocess_alive(&process)) {
bytes_read = subprocess_read_stdout(&process, data + index,
sizeof(data) - 1 - index);
index += bytes_read;
} while (bytes_read != 0);
}

ASSERT_EQ(212992u, index);

Expand All @@ -654,20 +662,70 @@ UTEST(subprocess, poll_stdout_async) {
ASSERT_EQ(0, subprocess_create(commandLine, subprocess_option_enable_async,
&process));

do {
while (subprocess_alive(&process)) {
bytes_read = subprocess_read_stdout(&process, data + index,
sizeof(data) - 1 - index);

// Send the control character to the subprocess to tell it to stop after
// we've read at least one thing from it's stdout (meaning the read was
// definitely async).
if (index == 0) {
if (bytes_read > 0) {
fputc('s', subprocess_stdin(&process));
fflush(subprocess_stdin(&process));
}

index += bytes_read;
} while (bytes_read != 0);
}

ASSERT_EQ(212992u, index);

for (index = 0; index < 16384; index++) {
const char *const helloWorld = "Hello, world!";
ASSERT_TRUE(0 == memcmp(data + (index * strlen(helloWorld)), helloWorld,
strlen(helloWorld)));
}

ASSERT_EQ(0, subprocess_join(&process, &ret));
ASSERT_EQ(0, subprocess_destroy(&process));
ASSERT_EQ(ret, 0);
}

UTEST(subprocess, poll_stdout_async_wait_first) {
const char *const commandLine[] = {"./process_stdout_poll_wait_first",
"16384", 0};
struct subprocess_s process;
int ret = -1;
static char data[1048576 + 1] = {0};
unsigned index = 0;
unsigned bytes_read = 0;

ASSERT_EQ(0, subprocess_create(commandLine, subprocess_option_enable_async,
&process));

bytes_read =
subprocess_read_stdout(&process, data + index, sizeof(data) - 1 - index);

// We first have zero bytes read because we haven't wrote the control
// character to stdin.
ASSERT_EQ(0u, bytes_read);

fputc('s', subprocess_stdin(&process));
fflush(subprocess_stdin(&process));

while (subprocess_alive(&process)) {
bytes_read = subprocess_read_stdout(&process, data + index,
sizeof(data) - 1 - index);

// Send the control character to the subprocess to tell it to stop after
// we've read at least one thing from it's stdout (meaning the read was
// definitely async).
if (bytes_read > 0) {
fputc('s', subprocess_stdin(&process));
fflush(subprocess_stdin(&process));
}

index += bytes_read;
}

ASSERT_EQ(212992u, index);

Expand Down
Loading
Loading