Skip to content

Commit

Permalink
Make reads non-blocking if async is specific on POSIX.
Browse files Browse the repository at this point in the history
  • Loading branch information
sheredom committed Feb 5, 2024
1 parent 7e59b69 commit 2cee853
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 16 deletions.
16 changes: 16 additions & 0 deletions subprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,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 @@ -770,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 @@ -899,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");

// Set non blocking on stdout if we are async
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 All @@ -907,6 +916,13 @@ int subprocess_create_ex(const char *const commandLine[], int options,
close(stderrfd[1]);
// Store the stderr read end
out_process->stderr_file = fdopen(stderrfd[0], "rb");

// Set non blocking on stdout if we are async
if (options & subprocess_option_enable_async) {
fd = fileno(out_process->stderr_file);
fd_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
}
}

// Store the child's pid
Expand Down
16 changes: 15 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,7 +49,9 @@ 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_stderr_poll_wait_first process_stderr_poll_wait_first.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 +65,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 +126,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
125 changes: 113 additions & 12 deletions test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,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 @@ -313,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 @@ -601,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 @@ -631,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 @@ -661,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 All @@ -700,7 +751,7 @@ UTEST(subprocess, poll_stderr_async) {
ASSERT_EQ(0, subprocess_create(commandLine, subprocess_option_enable_async,
&process));

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

Expand All @@ -713,7 +764,57 @@ UTEST(subprocess, poll_stderr_async) {
}

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_stderr_async_wait_first) {
const char *const commandLine[] = {"./process_stderr_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_stderr(&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_stderr(&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 stderr (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
2 changes: 1 addition & 1 deletion test/process_call_return_argc.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include <direct.h>
#define chdir(x) _chdir(x)
#elif defined(__MINGW32__)
#include <unistd.h> // chdir
#include <unistd.h> // chdir
#endif

int main(int argc, const char *const argv[]) {
Expand Down
2 changes: 1 addition & 1 deletion test/process_return_lpcmdline.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ int main(int argc, const char *const argv[]) {
printf("\"%s\"", argv[i]);
else
printf("%s", argv[i]);
if (i != (argc-1))
if (i != (argc - 1))
printf(" ");
}
#endif
Expand Down
44 changes: 44 additions & 0 deletions test/process_stderr_poll_wait_first.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *const argv[]) {
int index;
const int max = atoi(argv[1]);

// We first wait for a signal on stdin to begin processing.
while (fgetc(stdin) != 's') {
}

do {
for (index = 0; index < max; index++) {
fprintf(stderr, "Hello, world!");
}
} while (fgetc(stdin) != 's');

return 0;
}
44 changes: 44 additions & 0 deletions test/process_stdout_poll_wait_first.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *const argv[]) {
int index;
const int max = atoi(argv[1]);

// We first wait for a signal on stdin to begin processing.
while (fgetc(stdin) != 's') {
}

do {
for (index = 0; index < max; index++) {
printf("Hello, world!");
}
} while (fgetc(stdin) != 's');

return 0;
}
Loading

0 comments on commit 2cee853

Please sign in to comment.