From d54f2ffa50ca710b80cd2e32abda5452c89f9440 Mon Sep 17 00:00:00 2001 From: wisd0me Date: Mon, 21 Aug 2017 23:05:38 +0700 Subject: [PATCH] bugfix: if reading from the socket closed by peer, the next read from another socket in the same lthread will be a guaranteed failure --- CMakeLists.txt | 10 ++++- src/lthread_socket.c | 3 ++ tests/lthread_fdeof_read.c | 67 ++++++++++++++++++++++++++++++++++ tests/lthread_fdeof_write.c | 73 +++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 tests/lthread_fdeof_read.c create mode 100644 tests/lthread_fdeof_write.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 577cffa..dfe055a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set_property(SOURCE ${lthread_files} PROPERTY COMPILE_FLAGS "-O2") install(TARGETS lthread DESTINATION lib) install(FILES src/lthread.h DESTINATION include) -set(UNIT_TEST_PATH ${CMAKE_SOURCE_DIR}/tests) +set(UNIT_TEST_PATH ${CMAKE_BINARY_DIR}/tests) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${UNIT_TEST_PATH}) include_directories(src) @@ -31,6 +31,14 @@ target_link_libraries(lthread_io lthread pthread) add_executable(lthread_join tests/lthread_join.c) target_link_libraries(lthread_join lthread pthread) +add_executable(lthread_fdeof_read tests/lthread_fdeof_read.c) +target_link_libraries(lthread_fdeof_read lthread pthread) + +add_executable(lthread_fdeof_write tests/lthread_fdeof_write.c) +target_link_libraries(lthread_fdeof_write lthread pthread) + enable_testing() add_test(lthread_sleep ${UNIT_TEST_PATH}/lthread_sleep) add_test(lthread_join ${UNIT_TEST_PATH}/lthread_join) +add_test(lthread_fdeof_read ${UNIT_TEST_PATH}/lthread_fdeof_read) +add_test(lthread_fdeof_write ${UNIT_TEST_PATH}/lthread_fdeof_write) diff --git a/src/lthread_socket.c b/src/lthread_socket.c index faa48ed..ad2d3a3 100644 --- a/src/lthread_socket.c +++ b/src/lthread_socket.c @@ -65,6 +65,8 @@ fn \ x { \ ssize_t ret = 0; \ struct lthread *lt = lthread_get_sched()->current_lthread; \ + \ + lt->state &= CLEARBIT(LT_ST_FDEOF); \ while (1) { \ if (lt->state & BIT(LT_ST_FDEOF)) \ return (-1); \ @@ -88,6 +90,7 @@ x { \ ssize_t recvd = 0; \ struct lthread *lt = lthread_get_sched()->current_lthread; \ \ + lt->state &= CLEARBIT(LT_ST_FDEOF); \ while (recvd != length) { \ if (lt->state & BIT(LT_ST_FDEOF)) \ return (-1); \ diff --git a/tests/lthread_fdeof_read.c b/tests/lthread_fdeof_read.c new file mode 100644 index 0000000..cdfdc1c --- /dev/null +++ b/tests/lthread_fdeof_read.c @@ -0,0 +1,67 @@ +#include "lthread.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * test made after the bug was found: + * lthread reads from the fd that was closed by the other side, + * then next read from an alive fd will be a guaranteed failure + */ + + +bool PASSED = false; +const char * const PHRASE = "Hello world!"; + +void +write_pipe(void *arg) +{ + int *pipes = arg; + printf("closing fd of 1 %d\n", pipes[1]); + lthread_close(pipes[1]); /* first read must fail */ + printf("fd of 3 is %d\n", pipes[3]); + lthread_write(pipes[3], PHRASE, strlen(PHRASE) + 1); /* second read must succeed */ + lthread_close(pipes[3]); +} + +void +read_pipe(void *arg) +{ + int *pipes = arg; + char buf[100]; + int ret; + + for (int i = 0; i <= 2; i += 2) { + ret = lthread_read(pipes[i], buf, sizeof(buf), 0); + lthread_close(pipes[i]); + if (ret) { + printf("fd %d, read '%s' [%d]\n", pipes[i], buf, ret); + } else { + printf("read from fd %d failed %d\n", pipes[i], ret); + continue; + } + + if (strcmp(buf, PHRASE) == 0) + PASSED = true; + } +} + +int +main(int argc, char **argv) +{ + lthread_t *lt = NULL; + int pipes[4]; + assert(lthread_pipe(pipes) == 0); + assert(lthread_pipe(pipes + 2) == 0); + + lthread_create(<, read_pipe, pipes); + lthread_create(<, write_pipe, pipes); + lthread_run(); + + return PASSED != true; +} diff --git a/tests/lthread_fdeof_write.c b/tests/lthread_fdeof_write.c new file mode 100644 index 0000000..afe0533 --- /dev/null +++ b/tests/lthread_fdeof_write.c @@ -0,0 +1,73 @@ +#include "lthread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * this test is analogous to lthread_fdeof_read.c, but for write function + * even though the error is encountered only for read-calls, write test was made + * just in case + */ + + +bool PASSED = false; +const char * const PHRASE = "Hello world!"; + +void +write_pipe(void *arg) +{ + int *pipes = arg; + int ret; + + for (int i = 1; i <= 3; i += 2) { + ret = lthread_write(pipes[i], PHRASE, strlen(PHRASE) + 1); + printf("fd of %d is %d, written %d %s\n", i, pipes[i], ret, ret < 0 ? strerror(errno) : ""); + } +} + +void +read_pipe(void *arg) +{ + int *pipes = arg; + char buf[100]; + int ret; + + printf("closing fd of 0 %d\n", pipes[0]); /* first write must fail */ + lthread_close(pipes[0]); + ret = lthread_read(pipes[2], buf, sizeof(buf), 0); /* second write must succeed */ + lthread_close(pipes[2]); + if (ret) { + printf("fd %d, read '%s' [%d]\n", pipes[2], buf, ret); + } else { + printf("read from fd %d failed %d\n", pipes[2], ret); + } + + if (strcmp(buf, PHRASE) == 0) + PASSED = true; +} + +int +main(int argc, char **argv) +{ + struct sigaction act = { 0 }; + act.sa_handler = SIG_IGN; + + sigaction(SIGPIPE, &act, NULL); + + lthread_t *lt = NULL; + int pipes[4]; + assert(lthread_pipe(pipes) == 0); + assert(lthread_pipe(pipes + 2) == 0); + + lthread_create(<, read_pipe, pipes); + lthread_create(<, write_pipe, pipes); + lthread_run(); + + return PASSED != true; +}