diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 56deee7..ab5eca3 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -18,6 +18,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ graphviz \ lcov \ + libclang-rt-18-dev \ lldb \ llvm \ nano \ diff --git a/.vscode/settings.json b/.vscode/settings.json index d95052a..7c9aa87 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,7 @@ "HISTFILE", "hwrap", "lgcov", + "libclang", "lldb", "ltsan", "lubsan", diff --git a/CMakeLists.txt b/CMakeLists.txt index 91d90a5..9599c19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,13 @@ cmake_minimum_required(VERSION 3.15) -project(cpp_channel) -set(PROJECT_VERSION 1.3.0) +project(cpp_channel VERSION 1.3.0) set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard") -set(CMAKE_CXX_STANDARD_REQUIRED YES) -set(CXX_EXTENSIONS NO) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CXX_EXTENSIONS OFF) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(warnings) -include_directories(include) - add_library(msd_channel INTERFACE) target_include_directories(msd_channel INTERFACE include) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 73c807e..d202382 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,4 +1,5 @@ include(FetchContent) + if(NOT benchmark_POPULATED) set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) @@ -10,7 +11,7 @@ endif() function(package_add_benchmark TESTNAME) add_executable(${TESTNAME} ${ARGN}) set_target_warnings(${TESTNAME} PRIVATE) - target_link_libraries(${TESTNAME} benchmark) + target_link_libraries(${TESTNAME} msd_channel benchmark) endfunction() package_add_benchmark(channel_benchmark channel_benchmark.cpp) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 48a2897..0312124 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,10 +2,11 @@ function(add_example NAME) add_executable(${NAME} ${ARGN}) set_target_warnings(${NAME} PRIVATE) + target_link_libraries(${NAME} msd_channel) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(${NAME} PRIVATE -fsanitize=thread) - target_link_libraries(${NAME} -ltsan) + target_link_options(${NAME} PRIVATE -fsanitize=thread) endif() add_dependencies(examples ${NAME}) diff --git a/examples/close.cpp b/examples/close.cpp index 04a6a03..924b223 100644 --- a/examples/close.cpp +++ b/examples/close.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include int main() @@ -12,38 +14,37 @@ int main() const auto input = [](msd::channel& chan, int time_ms) { static int inc = 0; - while (true) { - if (chan.closed()) { - break; - } - + while (!chan.closed()) { chan << ++inc; - std::cout << "in: " << inc << "\n"; std::this_thread::sleep_for(std::chrono::milliseconds{time_ms}); } - - std::cout << "exit input\n"; }; const auto input_future = std::async(input, std::ref(channel), 10); // Close the channel after some time const auto timeout = [](msd::channel& chan, int time_ms) { std::this_thread::sleep_for(std::chrono::milliseconds{time_ms}); + chan.close(); - std::cout << "exit timeout\n"; }; auto timeout_future = std::async(timeout, std::ref(channel), 100); // Display all the data from the channel // When the channel is closed and empty, the iteration will end - const auto write = [](msd::channel& chan, int time_ms) { + std::mutex cout_mutex; + + const auto write = [&cout_mutex](msd::channel& chan, int time_ms) { for (auto out : chan) { - std::cout << "out: " << out << "\n"; + std::string msg{"out: " + std::to_string(out) + "\n"}; + + { + std::lock_guard lock(cout_mutex); + std::cout << msg; + } + std::this_thread::sleep_for(std::chrono::milliseconds{time_ms}); } - - std::cout << "exit write\n"; }; const auto write_future1 = std::async(write, std::ref(channel), 1); const auto write_future2 = std::async(write, std::ref(channel), 100); diff --git a/examples/cmake-project/CMakeLists.txt b/examples/cmake-project/CMakeLists.txt index c130e48..67b338d 100644 --- a/examples/cmake-project/CMakeLists.txt +++ b/examples/cmake-project/CMakeLists.txt @@ -3,13 +3,14 @@ project(cmake_project) set(PROJECT_VERSION 0.1.0) set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED YES) -set(CXX_EXTENSIONS NO) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CXX_EXTENSIONS OFF) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror --coverage") add_executable(cmake_project src/main.cpp) include(FetchContent) + if(NOT channel_POPULATED) FetchContent_Declare(channel URL https://github.com/andreiavrammsd/cpp-channel/archive/v1.3.0.zip DOWNLOAD_EXTRACT_TIMESTAMP TRUE) diff --git a/examples/merge_channels.cpp b/examples/merge_channels.cpp index 26a80ec..5f3f2e8 100644 --- a/examples/merge_channels.cpp +++ b/examples/merge_channels.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -12,25 +13,36 @@ int main() msd::channel input_chan{30}; msd::channel output_chan{10}; + std::mutex cout_mutex; + // Send to channel - const auto writer = [&input_chan](int begin, int end) { + const auto writer = [&input_chan, &cout_mutex](int begin, int end) { for (int i = begin; i <= end; ++i) { input_chan.write(i); std::stringstream msg; msg << "Sent " << i << " from " << std::this_thread::get_id() << "\n"; - std::cout << msg.str(); + + { + std::lock_guard lock(cout_mutex); + std::cout << msg.str(); + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); // simulate work } input_chan.close(); }; - const auto reader = [&output_chan]() { + // Read + const auto reader = [&output_chan, &cout_mutex]() { for (const auto out : output_chan) { // blocking until channel is drained (closed and empty) std::stringstream msg; msg << "Received " << out << " on " << std::this_thread::get_id() << "\n"; - std::cout << msg.str(); + + { + std::lock_guard lock(cout_mutex); + std::cout << msg.str(); + } std::this_thread::sleep_for(std::chrono::milliseconds(200)); // simulate work } diff --git a/examples/multithreading_static_channel.cpp b/examples/multithreading_static_channel.cpp index fb3b83b..d978078 100644 --- a/examples/multithreading_static_channel.cpp +++ b/examples/multithreading_static_channel.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,25 +11,36 @@ int main() { msd::static_channel chan{}; // always buffered + std::mutex cout_mutex; + // Send to channel - const auto writer = [&chan](int begin, int end) { + const auto writer = [&chan, &cout_mutex](int begin, int end) { for (int i = begin; i <= end; ++i) { chan.write(i); std::stringstream msg; msg << "Sent " << i << " from " << std::this_thread::get_id() << "\n"; - std::cout << msg.str(); + + { + std::lock_guard lock(cout_mutex); + std::cout << msg.str(); + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); // simulate work } chan.close(); }; - const auto reader = [&chan]() { + // Read + const auto reader = [&chan, &cout_mutex]() { for (const auto out : chan) { // blocking until channel is drained (closed and empty) std::stringstream msg; msg << "Received " << out << " on " << std::this_thread::get_id() << "\n"; - std::cout << msg.str(); + + { + std::lock_guard lock(cout_mutex); + std::cout << msg.str(); + } std::this_thread::sleep_for(std::chrono::milliseconds(200)); // simulate work } diff --git a/examples/streaming.cpp b/examples/streaming.cpp index d7f689a..7e12dfc 100644 --- a/examples/streaming.cpp +++ b/examples/streaming.cpp @@ -18,13 +18,9 @@ int main() const auto input = [](messages& chan, std::size_t thread, std::chrono::milliseconds pause) { thread_local static std::size_t inc = 0U; - while (true) { - if (chan.closed()) { - return; - } - + while (!chan.closed()) { ++inc; - chan << std::string{std::to_string(inc) + " from: " + std::to_string(thread)}; + chan << std::string{"Streaming " + std::to_string(inc) + " from thread " + std::to_string(thread)}; std::this_thread::sleep_for(pause); } @@ -35,12 +31,6 @@ int main() in_futures.push_back(std::async(input, std::ref(channel), i, std::chrono::milliseconds{500})); } - // Stream incoming data to a destination - const auto out = [](messages& chan, std::ostream& stream, const std::string& separator) { - std::move(chan.begin(), chan.end(), std::ostream_iterator(stream, separator.c_str())); - }; - const auto out_future = std::async(out, std::ref(channel), std::ref(std::cout), "\n"); - // Close the channel after some time const auto timeout = [](messages& chan, std::chrono::milliseconds after) { std::this_thread::sleep_for(after); @@ -48,7 +38,10 @@ int main() }; const auto timeout_future = std::async(timeout, std::ref(channel), std::chrono::milliseconds{3000U}); - out_future.wait(); + // Stream incoming data to a destination + std::move(channel.begin(), channel.end(), std::ostream_iterator(std::cout, "\n")); + + // Wait for other threads for (auto& future : in_futures) { future.wait(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3d449b3..3788c8f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,13 +1,10 @@ -if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") - cmake_policy(SET CMP0135 NEW) -endif() - # Testing framework if(MSVC) option(gtest_force_shared_crt "Use shared (DLL) run-time lib even when Google Test is built as static lib." ON) endif() include(FetchContent) + if(NOT googletest_POPULATED) set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) @@ -22,7 +19,7 @@ function(package_add_test TESTNAME) add_executable(${TESTNAME} ${ARGN}) set_target_warnings(${TESTNAME} PRIVATE) - target_link_libraries(${TESTNAME} gtest gtest_main) + target_link_libraries(${TESTNAME} msd_channel gtest gtest_main) if(CPP_CHANNEL_COVERAGE) target_compile_options(${TESTNAME} PRIVATE --coverage) @@ -35,14 +32,18 @@ function(package_add_test TESTNAME) endif() endif() + set(CPP_CHANNEL_SANITIZER_FLAGS "") if(CPP_CHANNEL_SANITIZERS) - target_compile_options(${TESTNAME} PRIVATE -fsanitize=undefined) - target_link_libraries(${TESTNAME} -lubsan) + set(SANITIZERS -fsanitize=address -fno-sanitize-recover=address -fsanitize=undefined + -fno-sanitize-recover=undefined) + + target_compile_options(${TESTNAME} PRIVATE ${SANITIZERS}) + target_link_options(${TESTNAME} PRIVATE ${SANITIZERS}) endif() if(CPP_CHANNEL_SANITIZE_THREADS) target_compile_options(${TESTNAME} PRIVATE -fsanitize=thread) - target_link_libraries(${TESTNAME} -fsanitize=thread) + target_link_options(${TESTNAME} PRIVATE -fsanitize=thread) endif() add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) @@ -57,8 +58,3 @@ add_custom_target(channel_tests) package_add_test(channel_test channel_test.cpp) package_add_test(blocking_iterator_test blocking_iterator_test.cpp) package_add_test(storage_test storage_test.cpp) - -if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - # Disable warnings about C++17 extensions - target_compile_options(channel_test PRIVATE -Wno-c++17-extensions) -endif()