├── bztools ├── BUILD ├── toolchain.bzl ├── shell │ └── BUILD └── package.bzl ├── .bazeliskrc ├── .gitignore ├── unittest ├── hello.py ├── DynamicLoaderTestImpl.h ├── DynamicLoaderTestImpl.cc ├── DynamicLoaderTestImplFunctions.cc ├── config.h ├── DynamicLoaderTestInterface.h ├── CMakeLists.txt ├── test_runnable.cc ├── test_logger.cc ├── test_dynamic_loader.cc ├── test_subprocess.cc ├── test_thread_pool.cc ├── test_buffer.cc ├── test_scheduler.cc ├── test_io.cc ├── BUILD ├── test_lock.cc └── test_event_loop.cc ├── examples ├── tcp_stress │ ├── CMakeLists.txt │ ├── BUILD │ ├── config.h │ ├── simple_client.cc │ └── simple_server.cc ├── file_transfer │ ├── CMakeLists.txt │ ├── config.h │ ├── BUILD │ ├── file_transfer_server.cc │ └── file_transfer_client.cc ├── io_evlp │ ├── CMakeLists.txt │ ├── config.h │ ├── udp_client.cc │ ├── BUILD │ ├── udp_server.cc │ ├── tcp_client.cc │ └── tcp_server.cc ├── CMakeLists.txt ├── BUILD └── README.md ├── .bazelrc ├── .clang-format ├── src ├── lib │ ├── common.cc │ ├── dynamic_loader.cc │ ├── runnable.cc │ ├── thread_pool.cc │ ├── subprocess.cc │ ├── event_loop_kqueue.cc │ ├── event_loop_epoll.cc │ ├── logger.cc │ ├── ipc.cc │ ├── event_loop_common.cc │ ├── lock.cc │ └── utils.cc ├── include │ └── cppev │ │ ├── cppev.h │ │ ├── common.h │ │ ├── dynamic_loader.h │ │ ├── ipc.h │ │ ├── subprocess.h │ │ ├── runnable.h │ │ ├── thread_pool.h │ │ ├── logger.h │ │ ├── buffer.h │ │ ├── scheduler.h │ │ ├── utils.h │ │ ├── lock.h │ │ └── event_loop.h ├── BUILD └── CMakeLists.txt ├── CMakeLists.txt ├── MODULE.bazel ├── .pre-commit-config.yaml ├── .github └── workflows │ └── cppev-build-and-test.yaml ├── README.md └── LICENSE /bztools/BUILD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bazeliskrc: -------------------------------------------------------------------------------- 1 | USE_BAZEL_VERSION=8.4.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | bazel-* 3 | .vscode/ 4 | whatever/ 5 | -------------------------------------------------------------------------------- /unittest/hello.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | print("hello") 4 | -------------------------------------------------------------------------------- /examples/tcp_stress/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | compile_target(simple_server simple_server.cc) 2 | compile_target(simple_client simple_client.cc) 3 | -------------------------------------------------------------------------------- /examples/file_transfer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | compile_target(file_transfer_server file_transfer_server.cc) 2 | compile_target(file_transfer_client file_transfer_client.cc) 3 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | build --cxxopt="-std=c++17" 2 | build --cxxopt="-O3" 3 | build --cxxopt="-Wall" 4 | build --cxxopt="-Wextra" 5 | test --local_test_jobs=1 6 | test --test_timeout=10 7 | test --flaky_test_attempts=3 8 | -------------------------------------------------------------------------------- /examples/io_evlp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | compile_target(udp_server udp_server.cc) 2 | compile_target(udp_client udp_client.cc) 3 | compile_target(tcp_server tcp_server.cc) 4 | compile_target(tcp_client tcp_client.cc) 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | Language: Cpp 4 | IndentWidth: 4 5 | ColumnLimit: 80 6 | BreakBeforeBraces: Allman 7 | AllowShortFunctionsOnASingleLine: None 8 | AccessModifierOffset: -4 9 | IndentCaseLabels: false 10 | IndentCaseBlocks: true 11 | AllowShortLambdasOnASingleLine: true 12 | -------------------------------------------------------------------------------- /src/lib/common.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/common.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | namespace sysconfig 7 | { 8 | 9 | int udp_buffer_size = 1500; 10 | 11 | int event_number = 2048; 12 | 13 | int buffer_io_step = 1024; 14 | 15 | int reactor_shutdown_timeout = 5000; 16 | 17 | } // namespace sysconfig 18 | 19 | } // namespace cppev 20 | -------------------------------------------------------------------------------- /examples/file_transfer/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_file_transfer_config_h_6C0224787A17_ 2 | #define _cppev_file_transfer_config_h_6C0224787A17_ 3 | 4 | const int PORT = 8891; 5 | 6 | const int CHUNK_SIZE = 10 * 1024 * 1024; // 10MB 7 | 8 | const char *FILENAME = "/tmp/test_cppev_file_transfer_6C0224787A17.file"; 9 | 10 | const int CONCURRENCY = 10; 11 | 12 | #endif // config.h 13 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(compile_target target_name_) 2 | add_executable(${target_name_} ${target_name_}.cc) 3 | target_link_libraries(${target_name_} cppev) 4 | target_compile_options(${target_name_} PRIVATE $) 5 | endfunction(compile_target) 6 | 7 | add_subdirectory(tcp_stress) 8 | add_subdirectory(file_transfer) 9 | add_subdirectory(io_evlp) 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12.0) 2 | 3 | project(cppev) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_POSITION_INDEPENDENT_CODE on) 7 | 8 | add_subdirectory(src) 9 | add_subdirectory(examples) 10 | 11 | find_package(GTest) 12 | if (GTest_FOUND) 13 | add_subdirectory(unittest) 14 | endif() 15 | 16 | install( 17 | TARGETS cppev LIBRARY DESTINATION lib 18 | ) 19 | install( 20 | DIRECTORY src/include/cppev DESTINATION include 21 | ) 22 | -------------------------------------------------------------------------------- /src/include/cppev/cppev.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_cppev_h_6C0224787A17_ 2 | #define _cppev_cppev_h_6C0224787A17_ 3 | 4 | #include "cppev/buffer.h" 5 | #include "cppev/common.h" 6 | #include "cppev/event_loop.h" 7 | #include "cppev/io.h" 8 | #include "cppev/ipc.h" 9 | #include "cppev/lock.h" 10 | #include "cppev/logger.h" 11 | #include "cppev/runnable.h" 12 | #include "cppev/subprocess.h" 13 | #include "cppev/tcp.h" 14 | #include "cppev/thread_pool.h" 15 | #include "cppev/utils.h" 16 | 17 | #endif // cppev.h 18 | -------------------------------------------------------------------------------- /unittest/DynamicLoaderTestImpl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DynamicLoaderTestInterface.h" 4 | 5 | namespace cppev 6 | { 7 | 8 | class DynamicLoaderTestImpl : public DynamicLoaderTestInterface 9 | { 10 | public: 11 | std::string add(int x, int y) const noexcept override; 12 | 13 | std::string add(const std::string &x, 14 | const std::string &y) const noexcept override; 15 | 16 | std::string type() const noexcept override; 17 | }; 18 | 19 | } // namespace cppev 20 | -------------------------------------------------------------------------------- /unittest/DynamicLoaderTestImpl.cc: -------------------------------------------------------------------------------- 1 | #include "DynamicLoaderTestImpl.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | std::string DynamicLoaderTestImpl::add(int x, int y) const noexcept 7 | { 8 | return std::to_string(x + y); 9 | } 10 | 11 | std::string DynamicLoaderTestImpl::add(const std::string &x, 12 | const std::string &y) const noexcept 13 | { 14 | return x + y; 15 | } 16 | 17 | std::string DynamicLoaderTestImpl::type() const noexcept 18 | { 19 | return "impl"; 20 | } 21 | 22 | } // namespace cppev 23 | -------------------------------------------------------------------------------- /examples/io_evlp/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_event_driven_io_config_h_6C0224787A17_ 2 | #define _cppev_event_driven_io_config_h_6C0224787A17_ 3 | 4 | const int TCP_IPV4_PORT = 18004; 5 | 6 | const int TCP_IPV6_PORT = 18006; 7 | 8 | const char *TCP_UNIX_PATH = "/tmp/test_cppev_tcp_unix_6C0224787A17.sock"; 9 | 10 | const int UDP_IPV4_PORT = 19004; 11 | 12 | const int UDP_IPV6_PORT = 19006; 13 | 14 | const char *UDP_UNIX_PATH = "/tmp/test_cppev_udp_unix_6C0224787A17.sock"; 15 | 16 | const char *MSG = "Cppev\0Nice"; 17 | 18 | const int MSG_LEN = 10; 19 | 20 | #endif // config.h 21 | -------------------------------------------------------------------------------- /examples/tcp_stress/BUILD: -------------------------------------------------------------------------------- 1 | cc_binary( 2 | name = "simple_client", 3 | srcs = [ 4 | "config.h", 5 | "simple_client.cc", 6 | ], 7 | linkstatic = False, 8 | visibility = [ 9 | "//examples:__pkg__", 10 | ], 11 | deps = [ 12 | "//src:cppev", 13 | ], 14 | ) 15 | 16 | cc_binary( 17 | name = "simple_server", 18 | srcs = [ 19 | "config.h", 20 | "simple_server.cc", 21 | ], 22 | linkstatic = False, 23 | visibility = [ 24 | "//examples:__pkg__", 25 | ], 26 | deps = [ 27 | "//src:cppev", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /bztools/toolchain.bzl: -------------------------------------------------------------------------------- 1 | ShellCommandInfo = provider( 2 | doc = "Information about shell commands location in different platforms.", 3 | fields = [ 4 | "cp", 5 | "tar", 6 | ], 7 | ) 8 | 9 | def _shell_command_toolchain_impl(ctx): 10 | toolchain_info = platform_common.ToolchainInfo( 11 | shell_command_info = ShellCommandInfo( 12 | cp = ctx.attr.cp, 13 | tar = ctx.attr.tar, 14 | ), 15 | ) 16 | return [toolchain_info] 17 | 18 | shell_command_toolchain = rule( 19 | implementation = _shell_command_toolchain_impl, 20 | attrs = { 21 | "cp": attr.string(), 22 | "tar": attr.string(), 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Bazel now uses Bzlmod by default to manage external dependencies. 3 | # Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel. 4 | # 5 | # For more details, please check https://github.com/bazelbuild/bazel/issues/18958 6 | ############################################################################### 7 | 8 | module(name = "cppev") 9 | 10 | bazel_dep(name = "platforms", version = "0.0.10") 11 | bazel_dep(name = "googletest", version = "1.15.2") 12 | 13 | register_toolchains( 14 | "//bztools/shell:linux_command_toolchain", 15 | "//bztools/shell:osx_command_toolchain", 16 | ) 17 | -------------------------------------------------------------------------------- /unittest/DynamicLoaderTestImplFunctions.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "DynamicLoaderTestImpl.h" 4 | 5 | extern "C" 6 | { 7 | cppev::DynamicLoaderTestInterface *DynamicLoaderTestImplConstructor() 8 | { 9 | return new cppev::DynamicLoaderTestImpl(); 10 | } 11 | 12 | void DynamicLoaderTestImplDestructor(cppev::DynamicLoaderTestInterface *ptr) 13 | { 14 | delete ptr; 15 | } 16 | 17 | std::shared_ptr 18 | DynamicLoaderTestImplSharedPtrConstructor() 19 | { 20 | return std::dynamic_pointer_cast( 21 | std::make_shared()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/file_transfer/BUILD: -------------------------------------------------------------------------------- 1 | cc_binary( 2 | name = "file_transfer_client", 3 | srcs = [ 4 | "config.h", 5 | "file_transfer_client.cc", 6 | ], 7 | dynamic_deps = [ 8 | "//src:cppev_dynamic", 9 | ], 10 | visibility = [ 11 | "//examples:__pkg__", 12 | ], 13 | deps = [ 14 | "//src:cppev", 15 | ], 16 | ) 17 | 18 | cc_binary( 19 | name = "file_transfer_server", 20 | srcs = [ 21 | "config.h", 22 | "file_transfer_server.cc", 23 | ], 24 | dynamic_deps = [ 25 | "//src:cppev_dynamic", 26 | ], 27 | visibility = [ 28 | "//examples:__pkg__", 29 | ], 30 | deps = [ 31 | "//src:cppev", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /src/lib/dynamic_loader.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/dynamic_loader.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | dynamic_loader::dynamic_loader(const std::string &filename, dyld_mode mode) 7 | : handle_(nullptr) 8 | { 9 | auto all_mode = RTLD_GLOBAL; 10 | if (mode == dyld_mode::lazy) 11 | { 12 | all_mode |= RTLD_LAZY; 13 | } 14 | else 15 | { 16 | all_mode |= RTLD_NOW; 17 | } 18 | handle_ = dlopen(filename.c_str(), all_mode); 19 | if (handle_ == nullptr) 20 | { 21 | throw_runtime_error(std::string("dlopen error : ").append(dlerror())); 22 | } 23 | } 24 | 25 | dynamic_loader::~dynamic_loader() noexcept 26 | { 27 | dlclose(handle_); 28 | } 29 | 30 | } // namespace cppev 31 | -------------------------------------------------------------------------------- /examples/BUILD: -------------------------------------------------------------------------------- 1 | load("//bztools:package.bzl", "package_files") 2 | 3 | # Attention: 4 | # Cannot package staticly linked cc_test, action conflict reported for the object file. 5 | package_files( 6 | name = "cppev_examples", 7 | testonly = True, 8 | dev = True, 9 | excludes = [ 10 | "//examples/io_evlp:config.h", 11 | ], 12 | files = [ 13 | "//examples/file_transfer:file_transfer_client", 14 | "//examples/file_transfer:file_transfer_server", 15 | "//examples/io_evlp:tcp_client", 16 | "//examples/io_evlp:tcp_server", 17 | "//examples/io_evlp:udp_client", 18 | "//examples/io_evlp:udp_server", 19 | "//examples/tcp_stress:simple_client", 20 | "//examples/tcp_stress:simple_server", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /examples/io_evlp/udp_client.cc: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "cppev/cppev.h" 3 | 4 | void send_to_servers() 5 | { 6 | cppev::event_loop evlp; 7 | 8 | auto udp_ipv4 = cppev::io_factory::get_sockudp(cppev::family::ipv4); 9 | auto udp_ipv6 = cppev::io_factory::get_sockudp(cppev::family::ipv6); 10 | auto udp_unix = cppev::io_factory::get_sockudp(cppev::family::local); 11 | 12 | udp_ipv4->wbuffer().put_string(MSG, MSG_LEN); 13 | udp_ipv6->wbuffer().put_string(MSG, MSG_LEN); 14 | udp_unix->wbuffer().put_string(MSG, MSG_LEN); 15 | 16 | udp_ipv4->send("127.0.0.1", UDP_IPV4_PORT); 17 | udp_ipv6->send("::1", UDP_IPV6_PORT); 18 | udp_unix->send_unix(UDP_UNIX_PATH); 19 | } 20 | 21 | int main() 22 | { 23 | send_to_servers(); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /bztools/shell/BUILD: -------------------------------------------------------------------------------- 1 | load("//bztools:toolchain.bzl", "shell_command_toolchain") 2 | 3 | toolchain_type(name = "toolchain_type") 4 | 5 | shell_command_toolchain( 6 | name = "linux_command", 7 | cp = "/bin/cp", 8 | tar = "/bin/tar", 9 | ) 10 | 11 | shell_command_toolchain( 12 | name = "osx_command", 13 | cp = "/bin/cp", 14 | tar = "/usr/bin/tar", 15 | ) 16 | 17 | toolchain( 18 | name = "linux_command_toolchain", 19 | exec_compatible_with = [ 20 | "@platforms//os:linux", 21 | ], 22 | toolchain = "linux_command", 23 | toolchain_type = "toolchain_type", 24 | ) 25 | 26 | toolchain( 27 | name = "osx_command_toolchain", 28 | exec_compatible_with = [ 29 | "@platforms//os:osx", 30 | ], 31 | toolchain = "osx_command", 32 | toolchain_type = "toolchain_type", 33 | ) 34 | -------------------------------------------------------------------------------- /src/include/cppev/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_common_h_6C0224787A17_ 2 | #define _cppev_common_h_6C0224787A17_ 3 | 4 | #define CPPEV_PUBLIC __attribute__((visibility("default"))) 5 | #define CPPEV_INTERNAL __attribute__((visibility("default"))) 6 | #define CPPEV_PRIVATE __attribute__((visibility("hidden"))) 7 | 8 | namespace cppev 9 | { 10 | 11 | namespace sysconfig 12 | { 13 | 14 | // Buffer size for udp socket. 15 | CPPEV_PUBLIC extern int udp_buffer_size; 16 | 17 | // File descriptor numbers for each epoll / kevent. 18 | CPPEV_PUBLIC extern int event_number; 19 | 20 | // Default batch size for stream's read and write. 21 | CPPEV_PUBLIC extern int buffer_io_step; 22 | 23 | // Timeout for reactor shutdown in milliseconds. 24 | CPPEV_PUBLIC extern int reactor_shutdown_timeout; 25 | 26 | } // namespace sysconfig 27 | 28 | } // namespace cppev 29 | 30 | #endif // common.h 31 | -------------------------------------------------------------------------------- /examples/tcp_stress/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_tcp_stress_config_h_6C0224787A17_ 2 | #define _cppev_tcp_stress_config_h_6C0224787A17_ 3 | 4 | /* 5 | * Concurrency Number 6 | */ 7 | #ifdef __linux__ 8 | const int IPV4_CONCURRENCY = 8000; 9 | const int IPV6_CONCURRENCY = 8000; 10 | const int UNIX_CONCURRENCY = 2000; 11 | const int SERVER_WORKER_NUM = 32; 12 | const int CLIENT_WORKER_NUM = 32; 13 | const bool SINGLE_ACPT = false; 14 | const int CONTOR_NUM = 3; 15 | #else 16 | const int IPV4_CONCURRENCY = 100; 17 | const int IPV6_CONCURRENCY = 100; 18 | const int UNIX_CONCURRENCY = 100; 19 | const int SERVER_WORKER_NUM = 3; 20 | const int CLIENT_WORKER_NUM = 3; 21 | const bool SINGLE_ACPT = true; 22 | const int CONTOR_NUM = 1; 23 | #endif 24 | 25 | const int IPV4_PORT = 8884; 26 | const int IPV6_PORT = 8886; 27 | const char *UNIX_PATH = "/tmp/test_cppev_tcp_stress_6C0224787A17.sock"; 28 | 29 | #endif // config.h 30 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - id: check-case-conflict 12 | - id: detect-private-key 13 | - id: mixed-line-ending 14 | - repo: local 15 | hooks: 16 | - id: buildifier 17 | name: Format Bazel files 18 | entry: buildifier -lint=fix -mode=fix 19 | language: system 20 | types: [ bazel ] 21 | - repo: local 22 | hooks: 23 | - id: clang-format 24 | name: Format C++ files 25 | entry: clang-format -style=file -i 26 | language: system 27 | files: \.(c|cc|cpp|h|hpp)$ 28 | types: [ text ] 29 | -------------------------------------------------------------------------------- /unittest/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_unittest_config_h_6C0224787A17_ 2 | #define _cppev_unittest_config_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cppev 9 | { 10 | 11 | template 12 | void performance_test(Mutex &lock) 13 | { 14 | int count = 0; 15 | 16 | int add_num = 50000; 17 | int thr_num = 50; 18 | 19 | auto task = [&]() 20 | { 21 | for (int i = 0; i < add_num; ++i) 22 | { 23 | std::unique_lock _(lock); 24 | ++count; 25 | } 26 | }; 27 | 28 | std::vector thrs; 29 | for (int i = 0; i < thr_num; ++i) 30 | { 31 | thrs.emplace_back(task); 32 | } 33 | for (int i = 0; i < thr_num; ++i) 34 | { 35 | thrs[i].join(); 36 | } 37 | 38 | EXPECT_EQ(count, add_num * thr_num); 39 | } 40 | 41 | } // namespace cppev 42 | 43 | #endif // config.h 44 | -------------------------------------------------------------------------------- /src/BUILD: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "cppev", 3 | srcs = glob([ 4 | "lib/*.cc", 5 | ]), 6 | hdrs = glob([ 7 | "include/cppev/*.h", 8 | ]), 9 | copts = [ 10 | "-fvisibility=hidden", 11 | ], 12 | includes = [ 13 | "include/", 14 | ], 15 | linkopts = [ 16 | "-lpthread", 17 | ] + select({ 18 | "@platforms//os:linux": [ 19 | "-lrt", 20 | "-ldl", 21 | ], 22 | "@platforms//os:osx": [], 23 | }), 24 | visibility = [ 25 | "//visibility:public", 26 | ], 27 | ) 28 | 29 | cc_shared_library( 30 | name = "cppev_dynamic", 31 | user_link_flags = select({ 32 | "@platforms//os:linux": [ 33 | "-Wl,--no-undefined", 34 | ], 35 | # osx -undefined is deprecated 36 | "@platforms//os:osx": [], 37 | }), 38 | visibility = [ 39 | "//visibility:public", 40 | ], 41 | deps = [ 42 | "cppev", 43 | ], 44 | ) 45 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (LIB 2 | lib/logger.cc 3 | lib/io.cc 4 | lib/utils.cc 5 | lib/common.cc 6 | lib/event_loop_epoll.cc 7 | lib/event_loop_kqueue.cc 8 | lib/event_loop_common.cc 9 | lib/tcp.cc 10 | lib/subprocess.cc 11 | lib/ipc.cc 12 | lib/lock.cc 13 | lib/runnable.cc 14 | lib/dynamic_loader.cc 15 | lib/thread_pool.cc 16 | ) 17 | 18 | option(CPPEV_DEBUG "print debug log" OFF) 19 | 20 | add_library(cppev STATIC ${LIB}) 21 | 22 | target_include_directories(cppev PUBLIC include) 23 | target_compile_options(cppev PUBLIC -O3 -Wall -Wextra) 24 | target_compile_options(cppev PRIVATE -fvisibility=hidden) 25 | target_link_libraries(cppev dl pthread) 26 | if (CPPEV_DEBUG) 27 | target_compile_definitions(cppev PRIVATE -DCPPEV_DEBUG) 28 | endif() 29 | 30 | if (CMAKE_HOST_APPLE) 31 | message(STATUS "Platform : Apple") 32 | else() 33 | message(STATUS "Platform : Linux") 34 | target_link_libraries(cppev rt) 35 | target_link_options(cppev PRIVATE "-Wl,--no-undefined") 36 | endif() 37 | -------------------------------------------------------------------------------- /examples/io_evlp/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//examples:__pkg__"]) 2 | 3 | cc_library( 4 | name = "lib", 5 | hdrs = [ 6 | "config.h", 7 | ], 8 | ) 9 | 10 | cc_binary( 11 | name = "tcp_client", 12 | srcs = [ 13 | "tcp_client.cc", 14 | ], 15 | dynamic_deps = [ 16 | "//src:cppev_dynamic", 17 | ], 18 | deps = [ 19 | ":lib", 20 | "//src:cppev", 21 | ], 22 | ) 23 | 24 | cc_binary( 25 | name = "tcp_server", 26 | srcs = [ 27 | "tcp_server.cc", 28 | ], 29 | dynamic_deps = [ 30 | "//src:cppev_dynamic", 31 | ], 32 | deps = [ 33 | ":lib", 34 | "//src:cppev", 35 | ], 36 | ) 37 | 38 | cc_binary( 39 | name = "udp_client", 40 | srcs = [ 41 | "udp_client.cc", 42 | ], 43 | deps = [ 44 | ":lib", 45 | "//src:cppev", 46 | ], 47 | ) 48 | 49 | cc_binary( 50 | name = "udp_server", 51 | srcs = [ 52 | "udp_server.cc", 53 | ], 54 | deps = [ 55 | ":lib", 56 | "//src:cppev", 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /unittest/DynamicLoaderTestInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cppev 7 | { 8 | 9 | class DynamicLoaderTestInterface 10 | { 11 | public: 12 | virtual ~DynamicLoaderTestInterface() = default; 13 | virtual std::string add(int, int) const noexcept = 0; 14 | virtual std::string add(const std::string &, 15 | const std::string &) const noexcept = 0; 16 | 17 | virtual std::string type() const noexcept 18 | { 19 | return "base"; 20 | } 21 | 22 | virtual std::string base() const noexcept 23 | { 24 | return "base"; 25 | } 26 | 27 | void set_var(int var) noexcept 28 | { 29 | this->var = var; 30 | } 31 | 32 | int get_var() const noexcept 33 | { 34 | return var; 35 | } 36 | 37 | private: 38 | int var; 39 | }; 40 | 41 | using DynamicLoaderTestInterfaceConstructorType = 42 | DynamicLoaderTestInterface *(); 43 | 44 | using DynamicLoaderTestInterfaceDestructorType = 45 | void(DynamicLoaderTestInterface *); 46 | 47 | // Not recommended due to warned by clang -Wreturn-type-c-linkage. 48 | using DynamicLoaderTestInterfaceSharedPtrConstructorType = 49 | std::shared_ptr(); 50 | 51 | } // namespace cppev 52 | -------------------------------------------------------------------------------- /src/include/cppev/dynamic_loader.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_dynamic_loader_h_6C0224787A17_ 2 | #define _cppev_dynamic_loader_h_6C0224787A17_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "cppev/common.h" 9 | #include "cppev/utils.h" 10 | 11 | namespace cppev 12 | { 13 | 14 | enum class CPPEV_PUBLIC dyld_mode 15 | { 16 | lazy, 17 | now, 18 | }; 19 | 20 | class CPPEV_PUBLIC dynamic_loader 21 | { 22 | public: 23 | explicit dynamic_loader(const std::string &filename, dyld_mode mode); 24 | 25 | dynamic_loader(const dynamic_loader &) = delete; 26 | dynamic_loader &operator=(const dynamic_loader &) = delete; 27 | dynamic_loader(dynamic_loader &&other) = delete; 28 | dynamic_loader &operator=(dynamic_loader &&other) = delete; 29 | 30 | ~dynamic_loader() noexcept; 31 | 32 | template 33 | Function *load(const std::string &func) const 34 | { 35 | void *ptr = dlsym(handle_, func.c_str()); 36 | if (ptr == nullptr) 37 | { 38 | // errno is set in macOS, but not in linux 39 | throw_runtime_error( 40 | std::string("dlsym error : ").append(dlerror())); 41 | } 42 | return reinterpret_cast(ptr); 43 | } 44 | 45 | private: 46 | void *handle_; 47 | }; 48 | 49 | } // namespace cppev 50 | 51 | #endif // dynamic_loader.h 52 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples Tutorial 2 | 3 | ### 1. Tcp Stress Test 4 | 5 | Tcp server initiates listening thread(s) to support ipc4 / ipv6 / unix protocol family. 6 | 7 | Tcp client initiates connecting thread(s) to connect to server. 8 | 9 | Both server and client initiate thread pool to handle tcp connection. 10 | 11 | * Usage 12 | 13 | $ cd examples/tcp_stress 14 | $ ./simple_server # Shell-1 15 | $ ./simple_client # Shell-2 16 | 17 | ### 2. Large File Transfer 18 | 19 | Tcp client sends the required file' name. 20 | 21 | Tcp server caches and transfers the required file. 22 | 23 | Tcp client receives the file and stores it to disk. 24 | 25 | * Usage 26 | 27 | $ cd example/file_transfer 28 | $ touch /tmp/test_cppev_file_transfer_6C0224787A17.file 29 | $ # Write to the file to make it large such as 20MB or more # 30 | $ ./file_transfer_server # Shell-1 31 | $ ./file_transfer_client # Shell-2 32 | $ openssl md5 /tmp/test_cppev_file_transfer_6C0224787A17.file* 33 | 34 | ### 3. IO Event Loop 35 | 36 | Use the original event loop to connect via tcp / udp. 37 | 38 | * Usage 39 | 40 | $ cd examples/io_evlp 41 | $ ./tcp_server # Shell-1 42 | $ ./tcp_client # Shell-2 43 | $ ./udp_server # Shell-3 44 | $ ./udp_client # Shell-4 45 | -------------------------------------------------------------------------------- /.github/workflows/cppev-build-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Cppev CI 2 | run-name: Cppev CI 🚀 3 | on: [push] 4 | jobs: 5 | bazel-build-and-test: 6 | strategy: 7 | matrix: 8 | os: [ ubuntu-latest, macos-latest ] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: '>=1.20.0' 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.11' 18 | 19 | - run: go install github.com/bazelbuild/bazelisk@latest 20 | - run: ln $(go env GOPATH)/bin/bazelisk $(go env GOPATH)/bin/bazel 21 | 22 | - run: bazel build //... 23 | - run: bazel test //... 24 | 25 | cmake-build: 26 | strategy: 27 | matrix: 28 | os: [ ubuntu-latest, macos-latest ] 29 | cmake_build_type: [ RelWithDebInfo ] 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Set reusable strings 35 | id: strings 36 | shell: bash 37 | run: | 38 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"; 39 | 40 | - name: Configure CMake 41 | run: > 42 | cmake -B ${{ steps.strings.outputs.build-output-dir }} 43 | -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} 44 | -S ${{ github.workspace }} 45 | 46 | - name: Build 47 | run: | 48 | cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.cmake_build_type }} 49 | -------------------------------------------------------------------------------- /src/lib/runnable.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/runnable.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | runnable::runnable() : fut_(prom_.get_future()) 7 | { 8 | } 9 | 10 | runnable::~runnable() = default; 11 | 12 | bool runnable::cancel() noexcept 13 | { 14 | return 0 == pthread_cancel(thr_); 15 | } 16 | 17 | void runnable::run() 18 | { 19 | auto thr_func = [](void *arg) -> void * 20 | { 21 | runnable *pseudo_this = static_cast(arg); 22 | if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr) != 0) 23 | { 24 | throw_logic_error("pthread_setcancelstate error"); 25 | } 26 | if (pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr) != 0) 27 | { 28 | throw_logic_error("pthread_setcanceltype error"); 29 | } 30 | pseudo_this->run_impl(); 31 | pseudo_this->prom_.set_value(true); 32 | return nullptr; 33 | }; 34 | int ret = pthread_create(&thr_, nullptr, thr_func, this); 35 | if (ret != 0) 36 | { 37 | throw_system_error("pthread_create error", ret); 38 | } 39 | } 40 | 41 | void runnable::join() 42 | { 43 | int ret = pthread_join(thr_, nullptr); 44 | if (ret != 0) 45 | { 46 | throw_system_error("pthread_join error", ret); 47 | } 48 | } 49 | 50 | void runnable::detach() 51 | { 52 | int ret = pthread_detach(thr_); 53 | if (ret != 0) 54 | { 55 | throw_system_error("pthread_detach error", ret); 56 | } 57 | } 58 | 59 | void runnable::send_signal(int sig) noexcept 60 | { 61 | pthread_kill(thr_, sig); 62 | } 63 | 64 | } // namespace cppev 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Cppev is a high performance C++ asyncio / multithreading / multiprocessing library.** 2 | 3 | # Architecture 4 | 5 | ### IO 6 | 7 | Support io operations of disk-file / pipe / fifo / socket (socket protocol-type tcp / udp, protocol-family ipv4 / ipv6 / unix-domain). 8 | 9 | Support io event listening by io-multiplexing, event-type readable / writable, event-mode level-trigger / edge-trigger / oneshot. 10 | 11 | ### Multithreading 12 | 13 | Support subthread / threadpool. 14 | 15 | ### Interprocess Communication 16 | 17 | Support semaphore / shared-memory. 18 | 19 | ### Thread and Process Synchronization 20 | 21 | Support thread / process level signal-handing / mutex / condition-variable / read-write-lock. 22 | 23 | ### Binary File Loading 24 | 25 | Support executable-file loading by subprocess, dynamic-library loading in runtime. 26 | 27 | ### Reactor 28 | 29 | Support tcp server / client by multi-threading / nonblocking-io / level-trigger-event-listening with performance and robustness. 30 | 31 | # Usage 32 | 33 | ### Prerequisite 34 | 35 | OS : Linux / macOS 36 | Dependency : googletest 37 | 38 | ### Build with cmake 39 | 40 | Build 41 | 42 | $ mkdir build && cd build 43 | $ cmake .. && make 44 | 45 | Install 46 | 47 | $ make install 48 | 49 | Run Unittest 50 | 51 | $ cd unittest && ctest 52 | 53 | 54 | ### Build with bazelisk 55 | 56 | Build 57 | 58 | $ bazel build //... 59 | 60 | Run Unittest 61 | 62 | $ bazel test //... 63 | 64 | # Getting Started 65 | 66 | Please see the examples along with a tutorial. 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025, cppev repository owner. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /unittest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | option(CPPEV_TEST_ENABLE_DLOPEN_ENV_SEARCH "enable dlopen env search test" OFF) 4 | 5 | function(compile_and_enable_test testname_) 6 | add_executable(${testname_} ${testname_}.cc) 7 | add_test(NAME ${testname_} COMMAND ${testname_}) 8 | target_link_libraries(${testname_} cppev gtest) 9 | find_library(FS_LIB NAME stdc++fs) 10 | if (FS_LIB) 11 | target_link_libraries(${testname_} stdc++fs) 12 | endif() 13 | target_compile_options(${testname_} PRIVATE $) 14 | if (CPPEV_TEST_ENABLE_DLOPEN_ENV_SEARCH AND (${testname_} STREQUAL "test_dynamic_loader")) 15 | message(STATUS "enable dlopen env search test") 16 | target_compile_definitions(${testname_} PRIVATE -DCPPEV_TEST_ENABLE_DLOPEN_ENV_SEARCH=1) 17 | endif() 18 | endfunction(compile_and_enable_test) 19 | 20 | add_library(DynamicLoaderTestImpl SHARED DynamicLoaderTestImpl.cc) 21 | add_library(DynamicLoaderTestImplFunctions SHARED DynamicLoaderTestImplFunctions.cc) 22 | target_link_libraries(DynamicLoaderTestImplFunctions DynamicLoaderTestImpl) 23 | 24 | file(COPY hello.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 25 | 26 | compile_and_enable_test(test_logger) 27 | compile_and_enable_test(test_thread_pool) 28 | compile_and_enable_test(test_runnable) 29 | compile_and_enable_test(test_buffer) 30 | compile_and_enable_test(test_io) 31 | compile_and_enable_test(test_event_loop) 32 | compile_and_enable_test(test_lock) 33 | compile_and_enable_test(test_utils) 34 | compile_and_enable_test(test_subprocess) 35 | compile_and_enable_test(test_ipc) 36 | compile_and_enable_test(test_scheduler) 37 | compile_and_enable_test(test_dynamic_loader) 38 | -------------------------------------------------------------------------------- /examples/io_evlp/udp_server.cc: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "cppev/cppev.h" 3 | 4 | cppev::fd_event_handler binding_socket_callback = 5 | [](const std::shared_ptr &iop) -> void 6 | { 7 | cppev::sockudp *iopu = dynamic_cast(iop.get()); 8 | if (iopu == nullptr) 9 | { 10 | cppev::throw_logic_error("server bind socket dynamic cast error!"); 11 | } 12 | auto cli = iopu->recv(); 13 | auto message = iopu->rbuffer().get_string(); 14 | assert(message == std::string(MSG, 10)); 15 | LOG_INFO_FMT("udp bind sock readable --> fd %d --> %s [%d] --> peer: %s %d", 16 | iopu->fd(), message.c_str(), message.size(), 17 | std::get<0>(cli).c_str(), std::get<1>(cli)); 18 | LOG_INFO << "Whole message is: " << message; 19 | }; 20 | 21 | void start_server_loop() 22 | { 23 | cppev::event_loop evlp; 24 | 25 | auto udp_ipv4 = cppev::io_factory::get_sockudp(cppev::family::ipv4); 26 | auto udp_ipv6 = cppev::io_factory::get_sockudp(cppev::family::ipv6); 27 | auto udp_unix = cppev::io_factory::get_sockudp(cppev::family::local); 28 | 29 | udp_ipv4->bind(UDP_IPV4_PORT); 30 | udp_ipv6->bind(UDP_IPV6_PORT); 31 | udp_unix->bind_unix(UDP_UNIX_PATH, true); 32 | 33 | evlp.fd_register_and_activate(udp_ipv4, cppev::fd_event::fd_readable, 34 | binding_socket_callback); 35 | evlp.fd_register_and_activate(udp_ipv6, cppev::fd_event::fd_readable, 36 | binding_socket_callback); 37 | evlp.fd_register_and_activate(udp_unix, cppev::fd_event::fd_readable, 38 | binding_socket_callback); 39 | 40 | evlp.loop_forever(); 41 | } 42 | 43 | int main() 44 | { 45 | start_server_loop(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /examples/io_evlp/tcp_client.cc: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "cppev/cppev.h" 3 | 4 | cppev::fd_event_handler connecting_socket_callback = 5 | [](const std::shared_ptr &iop) -> void 6 | { 7 | cppev::socktcp *iopt = dynamic_cast(iop.get()); 8 | if (iopt == nullptr) 9 | { 10 | cppev::throw_logic_error("client connect socket dynamic cast error!"); 11 | } 12 | if (!iopt->check_connect()) 13 | { 14 | LOG_ERROR << "fd " << iop->fd() << " failed to connect"; 15 | return; 16 | } 17 | iopt->wbuffer().put_string(MSG, MSG_LEN); 18 | iopt->write_all(); 19 | }; 20 | 21 | void connect_to_servers() 22 | { 23 | cppev::event_loop evlp; 24 | 25 | auto tcp_ipv4 = cppev::io_factory::get_socktcp(cppev::family::ipv4); 26 | auto tcp_ipv6 = cppev::io_factory::get_socktcp(cppev::family::ipv6); 27 | auto tcp_unix = cppev::io_factory::get_socktcp(cppev::family::local); 28 | auto tcp_ipv4_to_ipv6 = cppev::io_factory::get_socktcp(cppev::family::ipv4); 29 | 30 | tcp_ipv4->connect("127.0.0.1", TCP_IPV4_PORT); 31 | tcp_ipv6->connect("::1", TCP_IPV6_PORT); 32 | tcp_unix->connect_unix(TCP_UNIX_PATH); 33 | 34 | evlp.fd_register_and_activate(tcp_ipv4, cppev::fd_event::fd_writable, 35 | connecting_socket_callback); 36 | evlp.fd_register_and_activate(tcp_ipv6, cppev::fd_event::fd_writable, 37 | connecting_socket_callback); 38 | evlp.fd_register_and_activate(tcp_unix, cppev::fd_event::fd_writable, 39 | connecting_socket_callback); 40 | 41 | // Connection is writable when second tcp shake hand is ok 42 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 43 | 44 | evlp.loop_once(); 45 | } 46 | 47 | int main() 48 | { 49 | connect_to_servers(); 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /unittest/test_runnable.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "cppev/runnable.h" 7 | #include "cppev/utils.h" 8 | 9 | namespace cppev 10 | { 11 | 12 | const int delay = 200; 13 | 14 | class runnable_tester : public runnable 15 | { 16 | public: 17 | void run_impl() override 18 | { 19 | std::this_thread::sleep_for(std::chrono::milliseconds(delay)); 20 | } 21 | }; 22 | 23 | const int sig = SIGTERM; 24 | 25 | class runnable_tester_wait_for_signal : public runnable 26 | { 27 | public: 28 | void run_impl() override 29 | { 30 | handle_signal(sig); 31 | thread_wait_for_signal(sig); 32 | } 33 | }; 34 | 35 | TEST(TestRunnable, test_join) 36 | { 37 | runnable_tester tester; 38 | tester.run(); 39 | tester.join(); 40 | } 41 | 42 | TEST(TestRunnable, test_detach) 43 | { 44 | runnable_tester tester; 45 | tester.run(); 46 | tester.detach(); 47 | std::this_thread::sleep_for(std::chrono::milliseconds(delay * 2)); 48 | } 49 | 50 | TEST(TestRunnable, test_wait_for_timeout) 51 | { 52 | runnable_tester tester; 53 | tester.run(); 54 | bool ok = tester.wait_for(std::chrono::milliseconds(delay / 10)); 55 | EXPECT_FALSE(ok); 56 | tester.join(); 57 | } 58 | 59 | TEST(TestRunnable, test_wait_for_ok) 60 | { 61 | runnable_tester tester; 62 | tester.run(); 63 | bool ok = tester.wait_for(std::chrono::milliseconds(delay * 2)); 64 | EXPECT_TRUE(ok); 65 | tester.join(); 66 | } 67 | 68 | TEST(TestRunnable, test_send_signal) 69 | { 70 | runnable_tester_wait_for_signal tester; 71 | tester.run(); 72 | std::this_thread::sleep_for(std::chrono::milliseconds(delay / 10)); 73 | tester.send_signal(sig); 74 | tester.join(); 75 | } 76 | 77 | } // namespace cppev 78 | 79 | int main(int argc, char **argv) 80 | { 81 | testing::InitGoogleTest(); 82 | return RUN_ALL_TESTS(); 83 | } 84 | -------------------------------------------------------------------------------- /unittest/test_logger.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppev/logger.h" 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::string log_file_path = 8 | std::string(std::filesystem::path(argv[0]).parent_path()) + 9 | "/logger_output_file.log"; 10 | 11 | // Configure logger 12 | std::ofstream log_file(log_file_path); 13 | cppev::logger::get_instance().add_output_stream(log_file); 14 | 15 | std::vector levels = { 16 | cppev::log_level::debug, cppev::log_level::info, 17 | cppev::log_level::warning, cppev::log_level::error, 18 | cppev::log_level::fatal, 19 | }; 20 | 21 | for (auto level : levels) 22 | { 23 | cppev::logger::get_instance().set_log_level(level); 24 | 25 | // Stream-style logging 26 | LOG_DEBUG << "LOG_DEBUG Message" << " " << std::string("count") << " " 27 | << 1; 28 | LOG_INFO << "LOG_INFO Message" << " " << std::string("count") << " " 29 | << 2; 30 | LOG_WARNING << "LOG_WARNING Message" << " " << std::string("count") 31 | << " " << 3; 32 | LOG_ERROR << "LOG_ERROR Message" << " " << std::string("count") << " " 33 | << 4; 34 | LOG_FATAL << "LOG_FATAL Message" << " " << std::string("count") << " " 35 | << 5; 36 | 37 | auto logging_task = []() 38 | { 39 | // Formatted logging 40 | LOG_DEBUG_FMT("LOG_DEBUG_FMT Message : %s %d", "count", 1); 41 | LOG_INFO_FMT("LOG_INFO_FMT Message : %s %d", "count", 2); 42 | LOG_WARNING_FMT("LOG_WARNING_FMT Message : %s %d", "count", 3); 43 | LOG_ERROR_FMT("LOG_ERROR_FMT Message : %s %d", "count", 4); 44 | LOG_FATAL_FMT("LOG_FATAL_FMT Message : %s %d", "count", 5); 45 | }; 46 | std::thread thr(logging_task); 47 | thr.join(); 48 | 49 | std::cout << std::endl; 50 | } 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /src/include/cppev/ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_ipc_h_6C0224787A17_ 2 | #define _cppev_ipc_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "cppev/common.h" 10 | #include "cppev/utils.h" 11 | 12 | namespace cppev 13 | { 14 | 15 | class CPPEV_PUBLIC shared_memory final 16 | { 17 | public: 18 | shared_memory(const std::string &name, int size, mode_t mode = 0600); 19 | 20 | shared_memory(const shared_memory &) = delete; 21 | shared_memory &operator=(const shared_memory &) = delete; 22 | shared_memory(shared_memory &&other) noexcept; 23 | shared_memory &operator=(shared_memory &&other) noexcept; 24 | 25 | ~shared_memory() noexcept; 26 | 27 | template 28 | SharedClass *construct(Args &&...args) 29 | { 30 | SharedClass *object = 31 | new (ptr_) SharedClass(std::forward(args)...); 32 | if (object == nullptr) 33 | { 34 | throw_runtime_error("placement new error"); 35 | } 36 | return object; 37 | } 38 | 39 | void unlink(); 40 | 41 | void *ptr() const noexcept; 42 | 43 | int size() const noexcept; 44 | 45 | bool creator() const noexcept; 46 | 47 | private: 48 | void move(shared_memory &&other) noexcept; 49 | 50 | std::string name_; 51 | 52 | int size_; 53 | 54 | void *ptr_; 55 | 56 | bool creator_; 57 | }; 58 | 59 | class CPPEV_PUBLIC semaphore final 60 | { 61 | public: 62 | explicit semaphore(const std::string &name, mode_t mode = 0600); 63 | 64 | semaphore(const semaphore &) = delete; 65 | semaphore &operator=(const semaphore &) = delete; 66 | semaphore(semaphore &&other) noexcept; 67 | semaphore &operator=(semaphore &&other) noexcept; 68 | 69 | ~semaphore() noexcept; 70 | 71 | bool try_acquire(); 72 | 73 | void acquire(int count = 1); 74 | 75 | void release(int count = 1); 76 | 77 | void unlink(); 78 | 79 | bool creator() const noexcept; 80 | 81 | private: 82 | void move(semaphore &&other) noexcept; 83 | 84 | std::string name_; 85 | 86 | sem_t *sem_; 87 | 88 | bool creator_; 89 | }; 90 | 91 | } // namespace cppev 92 | 93 | #endif // ipc.h 94 | -------------------------------------------------------------------------------- /src/lib/thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/thread_pool.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | thread_pool_task_queue_runnable::thread_pool_task_queue_runnable( 7 | thread_pool_task_queue *tptq) noexcept 8 | : tptq_(tptq) 9 | { 10 | } 11 | 12 | void thread_pool_task_queue_runnable::run_impl() 13 | { 14 | thread_pool_task_handler handler; 15 | while (true) 16 | { 17 | { 18 | std::unique_lock lock(tptq_->lock_); 19 | if (tptq_->queue_.empty()) 20 | { 21 | if (tptq_->stop_) 22 | { 23 | break; 24 | } 25 | tptq_->cond_.wait( 26 | lock, [this]() -> bool 27 | { return tptq_->queue_.size() || tptq_->stop_; }); 28 | } 29 | if (tptq_->queue_.empty() && tptq_->stop_) 30 | { 31 | break; 32 | } 33 | handler = std::move(tptq_->queue_.front()); 34 | tptq_->queue_.pop(); 35 | } 36 | tptq_->cond_.notify_all(); 37 | handler(); 38 | } 39 | } 40 | 41 | thread_pool_task_queue::thread_pool_task_queue(int thr_num) 42 | : thread_pool( 43 | thr_num, this), 44 | stop_(false) 45 | { 46 | } 47 | 48 | thread_pool_task_queue::~thread_pool_task_queue() = default; 49 | 50 | void thread_pool_task_queue::add_task( 51 | const thread_pool_task_handler &h) noexcept 52 | { 53 | std::unique_lock lock(lock_); 54 | queue_.push(h); 55 | cond_.notify_one(); 56 | } 57 | 58 | void thread_pool_task_queue::add_task(thread_pool_task_handler &&h) noexcept 59 | { 60 | std::unique_lock lock(lock_); 61 | queue_.push(std::forward(h)); 62 | cond_.notify_one(); 63 | } 64 | 65 | void thread_pool_task_queue::add_task( 66 | const std::vector &vh) noexcept 67 | { 68 | std::unique_lock lock(lock_); 69 | for (const auto &h : vh) 70 | { 71 | queue_.push(h); 72 | } 73 | cond_.notify_all(); 74 | } 75 | 76 | void thread_pool_task_queue::stop() noexcept 77 | { 78 | { 79 | std::unique_lock lock(lock_); 80 | stop_ = true; 81 | cond_.notify_all(); 82 | } 83 | join(); 84 | } 85 | 86 | } // namespace cppev 87 | -------------------------------------------------------------------------------- /src/include/cppev/subprocess.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_subprocess_h_6C0224787A17_ 2 | #define _cppev_subprocess_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cppev/io.h" 11 | 12 | namespace cppev 13 | { 14 | 15 | namespace subprocess 16 | { 17 | 18 | CPPEV_PUBLIC std::tuple exec_cmd( 19 | const std::string &cmd, const std::vector &env = {}); 20 | 21 | } // namespace subprocess 22 | 23 | class CPPEV_PUBLIC subp_open final 24 | { 25 | public: 26 | explicit subp_open(const std::string &cmd, 27 | const std::vector &env); 28 | 29 | subp_open(const subp_open &) = delete; 30 | subp_open &operator=(const subp_open &) = delete; 31 | 32 | subp_open(subp_open &&other); 33 | subp_open &operator=(subp_open &&other); 34 | 35 | ~subp_open(); 36 | 37 | bool poll(); 38 | 39 | template 40 | void wait(const std::chrono::duration &interval) 41 | { 42 | /* 43 | * Q : Why polling is essential? 44 | * A : Buffer size of pipe is limited, so if you just wait for the 45 | * subprocess terminates, subprocess may block at writing to stdout or 46 | * stderr. That means you need to simultaneously deal with the io and 47 | * query the subprocess termination. 48 | */ 49 | while (!poll()) 50 | { 51 | communicate(); 52 | std::this_thread::sleep_for(interval); 53 | } 54 | communicate(); 55 | } 56 | 57 | void wait(); 58 | 59 | void communicate(const char *input, int len); 60 | 61 | void communicate(); 62 | 63 | void communicate(const std::string &input); 64 | 65 | void terminate(); 66 | 67 | void kill(); 68 | 69 | void send_signal(int sig); 70 | 71 | int returncode() const noexcept; 72 | 73 | const char *stdout() const noexcept; 74 | 75 | const char *stderr() const noexcept; 76 | 77 | pid_t pid() const noexcept; 78 | 79 | private: 80 | std::string cmd_; 81 | 82 | std::vector env_; 83 | 84 | std::unique_ptr stdin_; 85 | 86 | std::unique_ptr stdout_; 87 | 88 | std::unique_ptr stderr_; 89 | 90 | pid_t pid_; 91 | 92 | int returncode_; 93 | }; 94 | 95 | } // namespace cppev 96 | 97 | #endif // subprocess.h 98 | -------------------------------------------------------------------------------- /src/include/cppev/runnable.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_runnable_h_6C0224787A17_ 2 | #define _cppev_runnable_h_6C0224787A17_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cppev/utils.h" 13 | 14 | /* 15 | Q1 : Why a new thread library ? 16 | A1 : Previously std::thread doesn't support pthread_cancel and pthread_kill, 17 | but now std::jthread is recommended. Or if you prefer subthread 18 | implemented by subclass. 19 | 20 | Q2 : Is runnable a full encapsulation of pthread ? 21 | A2 : Remain components of pthread: 22 | 1) Per-Thread Context Routines : better use "thread_local". 23 | 2) Cleanup Routines : just coding in "run_impl". 24 | 3) Thread Routines : These routines are not essential 25 | pthread_exit / pthread_once / pthread_self / pthread_equal 26 | */ 27 | 28 | namespace cppev 29 | { 30 | 31 | class CPPEV_PUBLIC runnable 32 | { 33 | public: 34 | runnable(); 35 | 36 | runnable(const runnable &) = delete; 37 | runnable &operator=(const runnable &) = delete; 38 | runnable(runnable &&) = delete; 39 | runnable &operator=(runnable &&) = delete; 40 | 41 | virtual ~runnable(); 42 | 43 | // Derived class should override 44 | virtual void run_impl() = 0; 45 | 46 | // Cancel thread 47 | virtual bool cancel() noexcept; 48 | 49 | // Create and run thread 50 | void run(); 51 | 52 | // Wait until thread finish 53 | void join(); 54 | 55 | // Detach thread 56 | void detach(); 57 | 58 | // Send signal to thread 59 | void send_signal(int sig) noexcept; 60 | 61 | // Wait for thread 62 | // @return whether thread finishes 63 | template 64 | bool wait_for(const std::chrono::duration &span) 65 | { 66 | std::future_status stat = fut_.wait_for(span); 67 | bool ret = false; 68 | switch (stat) 69 | { 70 | case std::future_status::ready: 71 | ret = true; 72 | break; 73 | case std::future_status::timeout: 74 | ret = false; 75 | break; 76 | default: 77 | throw_logic_error("future::wait_for error"); 78 | break; 79 | } 80 | return ret; 81 | } 82 | 83 | private: 84 | pthread_t thr_; 85 | 86 | std::promise prom_; 87 | 88 | std::future fut_; 89 | }; 90 | 91 | } // namespace cppev 92 | 93 | #endif // runnable.h 94 | -------------------------------------------------------------------------------- /examples/file_transfer/file_transfer_server.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cppev/logger.h" 8 | #include "cppev/tcp.h" 9 | 10 | class filecache final 11 | { 12 | public: 13 | const cppev::buffer *lazyload(const std::string &filename) 14 | { 15 | std::unique_lock lock(lock_); 16 | if (hash_.count(filename) != 0) 17 | { 18 | return &(hash_[filename]->rbuffer()); 19 | } 20 | LOG_INFO << "start loading file"; 21 | int fd = open(filename.c_str(), O_RDONLY); 22 | std::shared_ptr iops = 23 | std::make_shared(fd); 24 | iops->read_all(CHUNK_SIZE); 25 | close(fd); 26 | hash_[filename] = iops; 27 | LOG_INFO << "finish loading file"; 28 | return &(iops->rbuffer()); 29 | } 30 | 31 | private: 32 | std::mutex lock_; 33 | 34 | std::unordered_map> hash_; 35 | }; 36 | 37 | cppev::reactor::tcp_event_handler on_read_complete = 38 | [](const std::shared_ptr &iopt) -> void 39 | { 40 | LOG_INFO << "start callback : on_read_complete"; 41 | std::string filename = iopt->rbuffer().get_string(-1, false); 42 | if (filename[filename.size() - 1] != '\n') 43 | { 44 | return; 45 | } 46 | iopt->rbuffer().clear(); 47 | filename = filename.substr(0, filename.size() - 1); 48 | LOG_INFO << "client request file : " << filename; 49 | 50 | const cppev::buffer *bf = 51 | reinterpret_cast(cppev::reactor::external_data(iopt)) 52 | ->lazyload(filename); 53 | 54 | iopt->wbuffer().put_string(bf->data(), bf->size()); 55 | cppev::reactor::async_write(iopt); 56 | LOG_INFO << "end callback : on_read_complete"; 57 | }; 58 | 59 | cppev::reactor::tcp_event_handler on_write_complete = 60 | [](const std::shared_ptr &iopt) -> void 61 | { 62 | LOG_INFO << "start callback : on_write_complete"; 63 | cppev::reactor::safely_close(iopt); 64 | LOG_INFO << "end callback : on_write_complete"; 65 | }; 66 | 67 | int main() 68 | { 69 | cppev::thread_block_signal(SIGINT); 70 | 71 | filecache cache; 72 | cppev::reactor::tcp_server server(3, false, &cache); 73 | server.set_on_read_complete(on_read_complete); 74 | server.set_on_write_complete(on_write_complete); 75 | server.listen(PORT, cppev::family::ipv4); 76 | server.run(); 77 | 78 | cppev::thread_wait_for_signal(SIGINT); 79 | 80 | server.shutdown(); 81 | 82 | LOG_INFO << "main thread exited"; 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /examples/io_evlp/tcp_server.cc: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "cppev/cppev.h" 3 | 4 | cppev::fd_event_handler accepted_socket_callback = 5 | [](const std::shared_ptr &iop) -> void 6 | { 7 | cppev::socktcp *iops = dynamic_cast(iop.get()); 8 | if (iops == nullptr) 9 | { 10 | cppev::throw_logic_error("server connected socket dynamic cast error!"); 11 | } 12 | iops->read_all(); 13 | auto sock = iops->sockname(); 14 | auto peer = iops->peername(); 15 | auto message = iops->rbuffer().get_string(); 16 | assert(message == std::string(MSG, 10)); 17 | LOG_INFO_FMT( 18 | "tcp connection readable --> fd %d --> %s [%d] --> sock: %s %d | peer: " 19 | "%s %d", 20 | iops->fd(), message.c_str(), message.size(), std::get<0>(sock).c_str(), 21 | std::get<1>(sock), std::get<0>(peer).c_str(), std::get<1>(peer)); 22 | LOG_INFO << "Whole message is: " << message; 23 | iop->evlp().fd_remove_and_deactivate(iop, cppev::fd_event::fd_readable); 24 | }; 25 | 26 | cppev::fd_event_handler listening_socket_callback = 27 | [](const std::shared_ptr &iop) -> void 28 | { 29 | cppev::socktcp *iopt = dynamic_cast(iop.get()); 30 | if (iopt == nullptr) 31 | { 32 | cppev::throw_logic_error("server listening socket dynamic cast error!"); 33 | } 34 | std::shared_ptr conn = 35 | std::dynamic_pointer_cast(iopt->accept(1).front()); 36 | iop->evlp().fd_register_and_activate(conn, cppev::fd_event::fd_readable, 37 | accepted_socket_callback); 38 | }; 39 | 40 | void start_server_loop() 41 | { 42 | cppev::event_loop evlp; 43 | 44 | auto tcp_ipv4 = cppev::io_factory::get_socktcp(cppev::family::ipv4); 45 | auto tcp_ipv6 = cppev::io_factory::get_socktcp(cppev::family::ipv6); 46 | auto tcp_unix = cppev::io_factory::get_socktcp(cppev::family::local); 47 | 48 | tcp_ipv4->bind(TCP_IPV4_PORT); 49 | tcp_ipv6->bind(TCP_IPV6_PORT); 50 | tcp_unix->bind_unix(TCP_UNIX_PATH, true); 51 | 52 | tcp_ipv4->listen(); 53 | tcp_ipv6->listen(); 54 | tcp_unix->listen(); 55 | 56 | evlp.fd_register_and_activate(tcp_ipv4, cppev::fd_event::fd_readable, 57 | listening_socket_callback); 58 | evlp.fd_register_and_activate(tcp_ipv6, cppev::fd_event::fd_readable, 59 | listening_socket_callback); 60 | evlp.fd_register_and_activate(tcp_unix, cppev::fd_event::fd_readable, 61 | listening_socket_callback); 62 | 63 | evlp.loop_forever(); 64 | } 65 | 66 | int main() 67 | { 68 | start_server_loop(); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /examples/tcp_stress/simple_client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple Client 3 | * 4 | * The simple client just echo any message back to the server. 5 | */ 6 | 7 | #include "config.h" 8 | #include "cppev/logger.h" 9 | #include "cppev/tcp.h" 10 | 11 | /* 12 | * Define Handler 13 | * 14 | * On the socket connect succeeded : Log the info. 15 | * 16 | * On the socket read from sys-buffer completed : Write the message back to the 17 | * server. 18 | * 19 | * On the socket write to sys-buffer completed : Log the info. 20 | * 21 | * On the socket closed : Log the info. 22 | */ 23 | cppev::reactor::tcp_event_handler on_connect = 24 | [](const std::shared_ptr &iopt) -> void 25 | { LOG_DEBUG_FMT("Fd %d on accept finish", iopt->fd()); }; 26 | 27 | cppev::reactor::tcp_event_handler on_read_complete = 28 | [](const std::shared_ptr &iopt) -> void 29 | { 30 | std::string message = iopt->rbuffer().get_string(); 31 | LOG_INFO_FMT("Received message : %s", message.c_str()); 32 | 33 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 34 | 35 | iopt->wbuffer().put_string(message); 36 | cppev::reactor::async_write(iopt); 37 | LOG_DEBUG_FMT("Fd %d on read finish", iopt->fd()); 38 | }; 39 | 40 | cppev::reactor::tcp_event_handler on_write_complete = 41 | [](const std::shared_ptr &iopt) -> void 42 | { LOG_DEBUG_FMT("Fd %d on write finish", iopt->fd()); }; 43 | 44 | cppev::reactor::tcp_event_handler on_closed = 45 | [](const std::shared_ptr &iopt) -> void 46 | { LOG_DEBUG_FMT("Fd %d on close finish", iopt->fd()); }; 47 | 48 | /* 49 | * Start Client 50 | * 51 | * Use io-threads to perform the handler and connector-threads to perform the 52 | * connect operation. Set handlers to the client, then use ipv4/ipv6/unix tcp 53 | * sockets to perform the stress test. 54 | */ 55 | int main() 56 | { 57 | cppev::logger::get_instance().set_log_level(cppev::log_level::info); 58 | 59 | cppev::thread_block_signal(SIGINT); 60 | 61 | cppev::reactor::tcp_client client(CLIENT_WORKER_NUM, CONTOR_NUM); 62 | client.set_on_connect(on_connect); 63 | client.set_on_read_complete(on_read_complete); 64 | client.set_on_write_complete(on_write_complete); 65 | client.set_on_closed(on_closed); 66 | 67 | // Please lower the concurrency number if server refused to connect 68 | // in your OS, especially the unix domain socket. 69 | client.add("127.0.0.1", IPV4_PORT, cppev::family::ipv4, IPV4_CONCURRENCY); 70 | client.add("::1", IPV6_PORT, cppev::family::ipv6, IPV6_CONCURRENCY); 71 | client.add_unix(UNIX_PATH, UNIX_CONCURRENCY); 72 | 73 | client.run(); 74 | 75 | cppev::thread_wait_for_signal(SIGINT); 76 | 77 | client.shutdown(); 78 | 79 | LOG_INFO << "main thread exited"; 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /unittest/test_dynamic_loader.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "DynamicLoaderTestInterface.h" 7 | #include "cppev/dynamic_loader.h" 8 | #include "cppev/utils.h" 9 | 10 | namespace cppev 11 | { 12 | 13 | std::string ld_full_name = std::string("libDynamicLoaderTestImplFunctions") + 14 | #ifdef __linux__ 15 | ".so"; 16 | #else 17 | ".dylib"; 18 | #endif // __linux__ 19 | 20 | std::string ld_path; 21 | 22 | static void test_class(DynamicLoaderTestInterface *base_cls) 23 | { 24 | EXPECT_EQ(base_cls->add(66, 66), std::to_string(66 + 66)); 25 | EXPECT_EQ(base_cls->add("66", "66"), "6666"); 26 | EXPECT_EQ(base_cls->type(), "impl"); 27 | EXPECT_EQ(base_cls->DynamicLoaderTestInterface::type(), "base"); 28 | EXPECT_EQ(base_cls->base(), "base"); 29 | EXPECT_EQ(base_cls->DynamicLoaderTestInterface::base(), "base"); 30 | base_cls->set_var(66); 31 | EXPECT_EQ(base_cls->get_var(), 66); 32 | } 33 | 34 | class TestDynamicLoader : public testing::TestWithParam> 35 | { 36 | }; 37 | 38 | TEST_P(TestDynamicLoader, test_base_impl_new_delete_loader) 39 | { 40 | auto p = GetParam(); 41 | 42 | dynamic_loader dyld(std::get<0>(p), dyld_mode::lazy); 43 | 44 | auto *constructor = dyld.load( 45 | "DynamicLoaderTestImplConstructor"); 46 | auto *destructor = dyld.load( 47 | "DynamicLoaderTestImplDestructor"); 48 | 49 | DynamicLoaderTestInterface *base_cls = constructor(); 50 | 51 | test_class(base_cls); 52 | 53 | destructor(base_cls); 54 | } 55 | 56 | TEST_P(TestDynamicLoader, test_base_impl_shared_ptr_loader) 57 | { 58 | auto p = GetParam(); 59 | dynamic_loader dyld(std::get<0>(p), dyld_mode::now); 60 | 61 | auto *shared_ptr_constructor = 62 | dyld.load( 63 | "DynamicLoaderTestImplSharedPtrConstructor"); 64 | 65 | std::shared_ptr shared_base_cls = 66 | shared_ptr_constructor(); 67 | 68 | test_class(shared_base_cls.get()); 69 | } 70 | 71 | INSTANTIATE_TEST_SUITE_P(CppevTest, TestDynamicLoader, 72 | #ifdef CPPEV_TEST_ENABLE_DLOPEN_ENV_SEARCH 73 | testing::Combine(testing::Values(ld_path, 74 | ld_full_name)) 75 | #else 76 | testing::Combine(testing::Values(ld_path)) 77 | #endif 78 | ); 79 | 80 | } // namespace cppev 81 | 82 | int main(int argc, char **argv) 83 | { 84 | cppev::ld_path = std::string(std::filesystem::path(argv[0]).parent_path()) + 85 | "/" + cppev::ld_full_name; 86 | testing::InitGoogleTest(); 87 | return RUN_ALL_TESTS(); 88 | } 89 | -------------------------------------------------------------------------------- /examples/file_transfer/file_transfer_client.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "config.h" 8 | #include "cppev/cppev.h" 9 | 10 | class fdcache final 11 | { 12 | public: 13 | void setfd(int conn, int file_descriptor) 14 | { 15 | std::unique_lock lock(lock_); 16 | hash_[conn] = std::make_shared(file_descriptor); 17 | } 18 | 19 | std::shared_ptr getfd(int conn) 20 | { 21 | std::unique_lock lock(lock_); 22 | return hash_[conn]; 23 | } 24 | 25 | private: 26 | std::unordered_map> hash_; 27 | 28 | std::mutex lock_; 29 | }; 30 | 31 | cppev::reactor::tcp_event_handler on_connect = 32 | [](const std::shared_ptr &iopt) -> void 33 | { 34 | iopt->wbuffer().put_string(FILENAME); 35 | iopt->wbuffer().put_string("\n"); 36 | cppev::reactor::async_write(iopt); 37 | LOG_INFO << "request file " << FILENAME; 38 | 39 | std::stringstream file_copy_name; 40 | file_copy_name << FILENAME << "." << iopt->fd() << "." << std::showbase 41 | << std::hex << std::this_thread::get_id() << ".copy"; 42 | int fd = open(file_copy_name.str().c_str(), O_WRONLY | O_CREAT | O_APPEND, 43 | S_IRWXU); 44 | if (fd < 0) 45 | { 46 | cppev::throw_system_error("open error"); 47 | } 48 | fdcache *cache = 49 | reinterpret_cast(cppev::reactor::external_data(iopt)); 50 | cache->setfd(iopt->fd(), fd); 51 | LOG_INFO << "create file " << file_copy_name.str(); 52 | }; 53 | 54 | cppev::reactor::tcp_event_handler on_read_complete = 55 | [](const std::shared_ptr &iopt) -> void 56 | { 57 | iopt->read_all(); 58 | fdcache *cache = 59 | reinterpret_cast(cppev::reactor::external_data(iopt)); 60 | auto iops = cache->getfd(iopt->fd()); 61 | iops->wbuffer().put_string(iopt->rbuffer().get_string()); 62 | iops->write_all(); 63 | LOG_INFO << "writing chunk to file complete"; 64 | }; 65 | 66 | cppev::reactor::tcp_event_handler on_closed = 67 | [](const std::shared_ptr &iopt) -> void 68 | { LOG_INFO << "receiving file complete"; }; 69 | 70 | int main(int argc, char **argv) 71 | { 72 | cppev::thread_block_signal(SIGINT); 73 | 74 | fdcache cache; 75 | cppev::reactor::tcp_client client(6, 1, &cache); 76 | 77 | client.set_on_connect(on_connect); 78 | client.set_on_read_complete(on_read_complete); 79 | client.set_on_closed(on_closed); 80 | 81 | client.add("127.0.0.1", PORT, cppev::family::ipv4, CONCURRENCY); 82 | client.run(); 83 | 84 | cppev::thread_wait_for_signal(SIGINT); 85 | 86 | client.shutdown(); 87 | 88 | LOG_INFO << "main thread exited"; 89 | 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /examples/tcp_stress/simple_server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple Server 3 | * 4 | * The simple server just send a message to the client, then echo any message 5 | * back to the client. 6 | */ 7 | 8 | #include "config.h" 9 | #include "cppev/logger.h" 10 | #include "cppev/tcp.h" 11 | 12 | /* 13 | * Define handler 14 | * 15 | * On the socket accepted : Put the message to the write buffer, then trying to 16 | * send it. The async_write here means if the message is very long then would 17 | * register writable event to do asynchrous write. 18 | * 19 | * On the socket read from sys-buffer completed : Retrieve the message from read 20 | * buffer, then put to the write buffer and trying to send it. 21 | * 22 | * On the socket write to sys-buffer completed : Log the info. 23 | * 24 | * On the socket closed : Log the info. 25 | */ 26 | cppev::reactor::tcp_event_handler on_accept = 27 | [](const std::shared_ptr &iopt) -> void 28 | { 29 | iopt->wbuffer().put_string("Cppev is a C++ event driven library"); 30 | cppev::reactor::async_write(iopt); 31 | LOG_DEBUG_FMT("Fd %d on accept finish", iopt->fd()); 32 | }; 33 | 34 | cppev::reactor::tcp_event_handler on_read_complete = 35 | [](const std::shared_ptr &iopt) -> void 36 | { 37 | std::string message = iopt->rbuffer().get_string(); 38 | LOG_INFO_FMT("Received message : %s", message.c_str()); 39 | 40 | iopt->wbuffer().put_string(message); 41 | cppev::reactor::async_write(iopt); 42 | LOG_DEBUG_FMT("Fd %d on read finish", iopt->fd()); 43 | }; 44 | 45 | cppev::reactor::tcp_event_handler on_write_complete = 46 | [](const std::shared_ptr &iopt) -> void 47 | { LOG_DEBUG_FMT("Fd %d on write finish", iopt->fd()); }; 48 | 49 | cppev::reactor::tcp_event_handler on_closed = 50 | [](const std::shared_ptr &iopt) -> void 51 | { LOG_DEBUG_FMT("Fd %d on close finish", iopt->fd()); }; 52 | 53 | /* 54 | * Start Server 55 | * 56 | * Use io-threads to perform the handler and acceptor-threads perform the accept 57 | * operation. Set handlers to the server, start to listen in port with 58 | * ipv4/ipv6/unix network layer protocol. 59 | */ 60 | int main() 61 | { 62 | cppev::logger::get_instance().set_log_level(cppev::log_level::info); 63 | 64 | cppev::thread_block_signal(SIGINT); 65 | 66 | cppev::reactor::tcp_server server(SERVER_WORKER_NUM, SINGLE_ACPT); 67 | server.set_on_accept(on_accept); 68 | server.set_on_read_complete(on_read_complete); 69 | server.set_on_write_complete(on_write_complete); 70 | server.set_on_closed(on_closed); 71 | 72 | // Create listening thread 73 | server.listen(IPV4_PORT, cppev::family::ipv4); 74 | server.listen(IPV6_PORT, cppev::family::ipv6); 75 | server.listen_unix(UNIX_PATH, true); 76 | 77 | server.run(); 78 | 79 | cppev::thread_wait_for_signal(SIGINT); 80 | 81 | server.shutdown(); 82 | 83 | LOG_INFO << "main thread exited"; 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /unittest/test_subprocess.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "cppev/subprocess.h" 7 | 8 | namespace cppev 9 | { 10 | 11 | std::string dirpath; 12 | 13 | TEST(TestSubprocessExecCmd, test_exec_cmd) 14 | { 15 | std::tuple rets; 16 | 17 | rets = subprocess::exec_cmd("printenv test", {"test=TEST"}); 18 | EXPECT_EQ(std::get<0>(rets), 0); 19 | EXPECT_STREQ(std::get<1>(rets).c_str(), "TEST\n"); 20 | EXPECT_STREQ(std::get<2>(rets).c_str(), ""); 21 | 22 | rets = subprocess::exec_cmd("/bin/cat /cppev/test/not/exist"); 23 | EXPECT_NE(std::get<0>(rets), 0); 24 | EXPECT_STREQ(std::get<1>(rets).c_str(), ""); 25 | EXPECT_STRNE(std::get<2>(rets).c_str(), ""); 26 | 27 | rets = subprocess::exec_cmd("not_exist_cmd /cppev/test/not/exist"); 28 | EXPECT_NE(std::get<0>(rets), 0); 29 | EXPECT_STREQ(std::get<1>(rets).c_str(), ""); 30 | EXPECT_STREQ(std::get<2>(rets).c_str(), ""); 31 | } 32 | 33 | TEST(TestSubprocessExecCmd, test_exec_cmd_python) 34 | { 35 | std::tuple rets; 36 | 37 | std::string pyfile = dirpath + "/hello.py"; 38 | 39 | rets = subprocess::exec_cmd(pyfile); 40 | EXPECT_EQ(std::get<0>(rets), 0); 41 | EXPECT_STREQ(std::get<1>(rets).c_str(), "hello\n"); 42 | EXPECT_STREQ(std::get<2>(rets).c_str(), ""); 43 | 44 | rets = subprocess::exec_cmd(std::string("python3 ") + pyfile); 45 | EXPECT_EQ(std::get<0>(rets), 0); 46 | EXPECT_STREQ(std::get<1>(rets).c_str(), "hello\n"); 47 | EXPECT_STREQ(std::get<2>(rets).c_str(), ""); 48 | } 49 | 50 | class TestSubprocess 51 | : public testing::TestWithParam> 52 | { 53 | protected: 54 | void SetUp() override 55 | { 56 | } 57 | 58 | void TearDown() override 59 | { 60 | } 61 | }; 62 | 63 | TEST_P(TestSubprocess, test_subp_popen) 64 | { 65 | auto param = GetParam(); 66 | 67 | subp_open subp("cat", {}); 68 | subp.communicate(std::get<0>(param)); 69 | 70 | subp_open subp1(std::move(subp)); 71 | subp = std::move(subp1); 72 | 73 | // wait for subprocess to process the data 74 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 75 | 76 | // terminate subprocess 77 | subp.send_signal(std::get<1>(param)); 78 | 79 | // fetch output 80 | subp.wait(); 81 | 82 | EXPECT_EQ(subp.stdout(), std::get<0>(param)); 83 | EXPECT_EQ(subp.stderr(), std::string("")); 84 | EXPECT_NE(subp.pid(), getpid()); 85 | } 86 | 87 | INSTANTIATE_TEST_SUITE_P( 88 | CppevTest, TestSubprocess, 89 | testing::Combine(testing::Values("cppev", "event driven"), // input 90 | testing::Values(SIGTERM, SIGKILL, SIGABRT) // signal 91 | )); 92 | 93 | } // namespace cppev 94 | 95 | int main(int argc, char **argv) 96 | { 97 | cppev::dirpath = std::string(std::filesystem::path(argv[0]).parent_path()); 98 | testing::InitGoogleTest(); 99 | return RUN_ALL_TESTS(); 100 | } 101 | -------------------------------------------------------------------------------- /unittest/test_thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "cppev/logger.h" 7 | #include "cppev/runnable.h" 8 | #include "cppev/thread_pool.h" 9 | 10 | namespace cppev 11 | { 12 | 13 | const int delay = 100; 14 | 15 | static std::mutex mtx; 16 | static int var; 17 | 18 | class runnable_tester : public runnable 19 | { 20 | public: 21 | void run_impl() override 22 | { 23 | { 24 | std::unique_lock _(mtx); 25 | var++; 26 | } 27 | LOG_INFO << "thread running"; 28 | std::this_thread::sleep_for(std::chrono::milliseconds(delay)); 29 | } 30 | }; 31 | 32 | class runnable_tester_with_param : public runnable 33 | { 34 | public: 35 | runnable_tester_with_param(const std::string &, std::string, std::string &&, 36 | const char *, int64_t, void *, void const *) 37 | { 38 | } 39 | 40 | void run_impl() override 41 | { 42 | } 43 | }; 44 | 45 | TEST(TestThreadPool, test_thread_pool_by_join) 46 | { 47 | var = 0; 48 | int thr_num = 20; 49 | thread_pool tp(thr_num); 50 | tp.run(); 51 | auto start = std::chrono::high_resolution_clock::now(); 52 | tp.join(); 53 | ASSERT_EQ(var, thr_num); 54 | auto end = std::chrono::high_resolution_clock::now(); 55 | std::chrono::duration d = 56 | std::chrono::duration_cast>(end - start); 57 | ASSERT_GT(d.count(), (delay * 0.99) / 1000.0); 58 | } 59 | 60 | TEST(TestThreadPool, test_thread_pool_by_cancel) 61 | { 62 | thread_pool tp(20); 63 | for (auto &thr : tp) 64 | { 65 | thr.run(); 66 | } 67 | std::this_thread::sleep_for(std::chrono::milliseconds(delay / 2)); 68 | tp.cancel(); 69 | for (auto &thr : tp) 70 | { 71 | thr.join(); 72 | } 73 | } 74 | 75 | TEST(TestThreadPool, test_thread_pool_compile_with_param) 76 | { 77 | std::string str; 78 | using tp_tester = 79 | thread_pool; 82 | std::vector tp_vec; 83 | tp_vec.emplace_back(10, "", "", std::move(str), "", 0, nullptr, nullptr); 84 | thread_pool tp1 = std::move(tp_vec[0]); 85 | thread_pool tp(std::move(tp1)); 86 | tp.run(); 87 | tp.join(); 88 | } 89 | 90 | TEST(TestThreadPool, test_thread_pool_task_queue) 91 | { 92 | int count = 1000; 93 | int sum = 0; 94 | std::mutex lock; 95 | 96 | auto f = [&]() 97 | { 98 | std::unique_lock lk(lock); 99 | sum++; 100 | }; 101 | 102 | thread_pool_task_queue tp(50); 103 | tp.run(); 104 | for (int i = 0; i < count; ++i) 105 | { 106 | tp.add_task(f); 107 | } 108 | tp.stop(); 109 | ASSERT_EQ(sum, count); 110 | } 111 | 112 | } // namespace cppev 113 | 114 | int main(int argc, char **argv) 115 | { 116 | testing::InitGoogleTest(&argc, argv); 117 | return RUN_ALL_TESTS(); 118 | } 119 | -------------------------------------------------------------------------------- /unittest/test_buffer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "cppev/buffer.h" 6 | 7 | namespace cppev 8 | { 9 | 10 | class TestBuffer : public testing::Test 11 | { 12 | protected: 13 | void SetUp() override 14 | { 15 | } 16 | 17 | void TearDown() override 18 | { 19 | } 20 | }; 21 | 22 | TEST_F(TestBuffer, test_put_get) 23 | { 24 | buffer buf; 25 | std::string str = "Cppev is a C++ event driven library"; 26 | buf.put_string(str); 27 | EXPECT_EQ(str.size(), buf.size()); 28 | EXPECT_EQ(str[3], buf[3]); 29 | int offset = 3; 30 | buf.get_string(offset, false); 31 | EXPECT_EQ(str.size(), buf.size()); 32 | buf.get_string(offset, true); 33 | EXPECT_EQ(str.size() - offset, buf.size()); 34 | EXPECT_STREQ(str.substr(offset, str.size() - offset).c_str(), buf.data()); 35 | } 36 | 37 | TEST_F(TestBuffer, test_resize_tiny_null) 38 | { 39 | const char *str = "cppev\0cppev000"; 40 | const int len = 11; 41 | 42 | buffer buf; 43 | buf.put_string(str, len); 44 | EXPECT_EQ(buf.size(), len); 45 | EXPECT_EQ(std::string(buf.data()), "cppev"); 46 | EXPECT_EQ(buf.get_string(5), "cppev"); 47 | EXPECT_EQ(buf.get_string(), std::string("\0cppev", len - 5)); 48 | 49 | buf.put_string(str, len); 50 | buf.get_string(3); 51 | buf.tiny(); 52 | EXPECT_EQ(std::string(buf.data()), "ev"); 53 | buf.resize(16); 54 | EXPECT_EQ(std::string(buf.data()), "ev"); 55 | buf.resize(1); 56 | EXPECT_EQ(std::string(buf.data()), "ev"); 57 | } 58 | 59 | TEST_F(TestBuffer, test_copy_move) 60 | { 61 | std::string str = "cppev"; 62 | 63 | std::vector vec; 64 | vec.emplace_back(1); // move constructor 65 | vec.back().put_string(str); 66 | vec.push_back(vec[0]); // copy constructor 67 | 68 | for (auto &b : vec) 69 | { 70 | b = vec[0]; 71 | } 72 | 73 | for (auto &b : vec) 74 | { 75 | EXPECT_EQ(b.get_string(-1, false), str); 76 | EXPECT_EQ(b.get_string(-1, true), str); 77 | } 78 | 79 | buffer b; 80 | b.put_string(str); 81 | buffer a = std::move(b); // move assignment 82 | EXPECT_EQ(a.get_string(-1, false), str); 83 | EXPECT_EQ(b.data(), nullptr); 84 | } 85 | 86 | TEST_F(TestBuffer, test_compilation) 87 | { 88 | basic_buffer a; 89 | basic_buffer b; 90 | 91 | struct ABC 92 | { 93 | int a; 94 | double b; 95 | long c; 96 | }; 97 | basic_buffer abc; 98 | } 99 | 100 | TEST_F(TestBuffer, test_ref) 101 | { 102 | buffer b; 103 | 104 | EXPECT_EQ(b.get_offset(), 0); 105 | b.get_offset_ref() += 666; 106 | EXPECT_EQ(b.get_offset(), 666); 107 | 108 | b.get_start_ref() = 777; 109 | EXPECT_EQ(b.get_start(), 777); 110 | 111 | b.set_start(888); 112 | EXPECT_EQ(b.get_start(), 888); 113 | 114 | b.set_offset(999); 115 | EXPECT_EQ(b.get_offset(), 999); 116 | } 117 | 118 | TEST_F(TestBuffer, test_ptr_data) 119 | { 120 | buffer b; 121 | b.put_string("cppev"); 122 | EXPECT_EQ(b.ptr() + b.get_start(), b.data()); 123 | 124 | b.clear(); 125 | b.put_string("cppev", 1); 126 | EXPECT_EQ(b.size(), 1); 127 | } 128 | 129 | } // namespace cppev 130 | 131 | int main(int argc, char **argv) 132 | { 133 | testing::InitGoogleTest(); 134 | return RUN_ALL_TESTS(); 135 | } 136 | -------------------------------------------------------------------------------- /unittest/test_scheduler.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "cppev/scheduler.h" 9 | 10 | namespace cppev 11 | { 12 | 13 | class TestTimedScheduler : public testing::Test 14 | { 15 | protected: 16 | void SetUp() override 17 | { 18 | } 19 | 20 | double err_percent = 0.1; 21 | }; 22 | 23 | #define CHECK_UNALIGNED_TRIGGER_COUNT(count, freq, total_time_ms, err_percent) \ 24 | EXPECT_LE(count, static_cast((1 + total_time_ms / 1'000.0 * freq) * \ 25 | (1 + err_percent))); \ 26 | EXPECT_GE(count, static_cast((1 + total_time_ms / 1'000.0 * freq) * \ 27 | (1 - err_percent))) 28 | 29 | bool is_sub_sequence(const std::vector &s, const std::vector &t) 30 | { 31 | size_t p = 0; 32 | size_t r = 0; 33 | assert(!t.empty()); 34 | if (s.size() == 0) 35 | { 36 | return true; 37 | } 38 | while (p != s.size() && r != t.size()) 39 | { 40 | if (s[p] == t[r]) 41 | { 42 | ++p; 43 | } 44 | ++r; 45 | } 46 | return p == s.size(); 47 | } 48 | 49 | TEST_F(TestTimedScheduler, test_timed_scheduler_several_timed_task) 50 | { 51 | std::map> kvmap; 52 | 53 | int total_time_ms = 3000; 54 | 55 | double freq1 = 50; 56 | double freq2 = 20; 57 | double freq3 = 0.5; 58 | double freq4 = 40; 59 | 60 | int count1 = 0; 61 | int count2 = 0; 62 | int count3 = 0; 63 | int count4 = 0; 64 | 65 | auto task1 = [&](const std::chrono::nanoseconds &stamp) 66 | { 67 | ++count1; 68 | kvmap[stamp.count()].push_back(1); 69 | }; 70 | auto task2 = [&](const std::chrono::nanoseconds &stamp) 71 | { 72 | ++count2; 73 | kvmap[stamp.count()].push_back(2); 74 | }; 75 | auto task3 = [&](const std::chrono::nanoseconds &stamp) 76 | { 77 | ++count3; 78 | kvmap[stamp.count()].push_back(3); 79 | }; 80 | auto task4 = [&](const std::chrono::nanoseconds &stamp) 81 | { 82 | ++count4; 83 | kvmap[stamp.count()].push_back(4); 84 | }; 85 | 86 | { 87 | timed_scheduler executor( 88 | { 89 | {freq1, priority::p6, task1}, 90 | {freq2, priority::p0, task2}, 91 | {freq3, priority::p4, task3}, 92 | {freq4, priority::p3, task4}, 93 | }, 94 | {}, {}, false); 95 | 96 | std::this_thread::sleep_for(std::chrono::milliseconds(total_time_ms)); 97 | } 98 | 99 | CHECK_UNALIGNED_TRIGGER_COUNT(count1, freq1, total_time_ms, err_percent); 100 | 101 | CHECK_UNALIGNED_TRIGGER_COUNT(count2, freq2, total_time_ms, err_percent); 102 | 103 | CHECK_UNALIGNED_TRIGGER_COUNT(count3, freq3, total_time_ms, err_percent); 104 | 105 | CHECK_UNALIGNED_TRIGGER_COUNT(count4, freq4, total_time_ms, err_percent); 106 | 107 | std::vector res{2, 4, 3, 1}; 108 | 109 | for (auto iter = kvmap.cbegin(); iter != kvmap.cend(); ++iter) 110 | { 111 | EXPECT_TRUE(is_sub_sequence(iter->second, res)); 112 | } 113 | } 114 | 115 | } // namespace cppev 116 | 117 | int main(int argc, char **argv) 118 | { 119 | testing::InitGoogleTest(); 120 | return RUN_ALL_TESTS(); 121 | } 122 | -------------------------------------------------------------------------------- /unittest/test_io.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "cppev/event_loop.h" 7 | #include "cppev/io.h" 8 | 9 | namespace cppev 10 | { 11 | 12 | const char *file = "./cppev_test_file"; 13 | 14 | const char *fifo = "./cppev_test_fifo"; 15 | 16 | const char *str = "Cppev is a C++ event driven library"; 17 | 18 | TEST(TestIO, test_diskfile) 19 | { 20 | int fd; 21 | 22 | fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); 23 | auto iofw = std::make_shared(fd); 24 | fd = open(file, O_RDONLY); 25 | auto iofr = std::make_shared(fd); 26 | 27 | iofw->wbuffer().put_string(str); 28 | iofw->write_all(); 29 | iofw->close(); 30 | iofr->read_all(); 31 | EXPECT_STREQ(iofr->rbuffer().data(), str); 32 | 33 | unlink(file); 34 | } 35 | 36 | TEST(TestIO, test_pipe) 37 | { 38 | auto pipes = io_factory::get_pipes(); 39 | auto iopr = pipes[0]; 40 | auto iopw = pipes[1]; 41 | 42 | iopw->wbuffer().put_string(str); 43 | iopw->write_all(); 44 | iopr->read_all(); 45 | EXPECT_STREQ(str, iopr->rbuffer().data()); 46 | } 47 | 48 | TEST(TestIO, test_fifo) 49 | { 50 | auto fifos = io_factory::get_fifos(fifo); 51 | auto iofr = fifos[0]; 52 | auto iofw = fifos[1]; 53 | iofw->wbuffer().put_string(str); 54 | iofw->write_all(); 55 | iofr->read_all(); 56 | EXPECT_STREQ(iofr->rbuffer().data(), str); 57 | 58 | unlink(fifo); 59 | } 60 | 61 | class TestIOSocket 62 | : public testing::TestWithParam> 63 | { 64 | }; 65 | 66 | TEST_P(TestIOSocket, test_tcp_socket) 67 | { 68 | auto p = GetParam(); 69 | std::shared_ptr sock = io_factory::get_socktcp(std::get<0>(p)); 70 | sock->set_so_reuseaddr(std::get<1>(p)); 71 | sock->set_so_reuseport(std::get<1>(p)); 72 | sock->set_so_keepalive(std::get<1>(p)); 73 | sock->set_so_linger(std::get<1>(p), std::get<3>(p)); 74 | sock->set_tcp_nodelay(std::get<1>(p)); 75 | EXPECT_EQ(sock->get_so_reuseaddr(), std::get<1>(p)); 76 | EXPECT_EQ(sock->get_so_reuseport(), std::get<1>(p)); 77 | EXPECT_EQ(sock->get_so_keepalive(), std::get<1>(p)); 78 | EXPECT_EQ(sock->get_tcp_nodelay(), std::get<1>(p)); 79 | auto lg = sock->get_so_linger(); 80 | EXPECT_EQ(lg.first, std::get<1>(p)); 81 | if (lg.first) 82 | { 83 | EXPECT_EQ(lg.second, std::get<3>(p)); 84 | } 85 | 86 | sock->set_so_rcvbuf(std::get<2>(p)); 87 | sock->set_so_sndbuf(std::get<2>(p)); 88 | sock->set_so_rcvlowat(std::get<2>(p)); 89 | 90 | #ifndef __linux__ 91 | sock->set_so_sndlowat(std::get<2>(p)); 92 | #endif 93 | 94 | #ifdef __linux__ 95 | int buf_size = std::get<2>(p) * 2; 96 | int sndlowat_size = 1; 97 | #else 98 | int buf_size = std::get<2>(p); 99 | int sndlowat_size = std::get<2>(p); 100 | #endif 101 | EXPECT_EQ(buf_size, sock->get_so_rcvbuf()); 102 | EXPECT_EQ(buf_size, sock->get_so_sndbuf()); 103 | EXPECT_EQ(sock->get_so_rcvlowat(), std::get<2>(p)); 104 | EXPECT_EQ(sock->get_so_sndlowat(), sndlowat_size); 105 | 106 | EXPECT_EQ(sock->get_so_error(), 0); 107 | } 108 | 109 | TEST_P(TestIOSocket, test_udp_socket) 110 | { 111 | auto p = GetParam(); 112 | std::shared_ptr sock = io_factory::get_sockudp(std::get<0>(p)); 113 | sock->set_so_reuseaddr(std::get<1>(p)); 114 | sock->set_so_reuseport(std::get<1>(p)); 115 | sock->set_so_broadcast(std::get<1>(p)); 116 | EXPECT_EQ(sock->get_so_reuseaddr(), std::get<1>(p)); 117 | EXPECT_EQ(sock->get_so_reuseport(), std::get<1>(p)); 118 | EXPECT_EQ(sock->get_so_broadcast(), std::get<1>(p)); 119 | 120 | sock->set_so_rcvbuf(std::get<2>(p)); 121 | sock->set_so_sndbuf(std::get<2>(p)); 122 | sock->set_so_rcvlowat(std::get<2>(p)); 123 | std::unordered_set buf_size{std::get<2>(p), std::get<2>(p) * 2}; 124 | EXPECT_TRUE(buf_size.count(sock->get_so_rcvbuf())); 125 | EXPECT_TRUE(buf_size.count(sock->get_so_sndbuf())); 126 | EXPECT_EQ(sock->get_so_rcvlowat(), std::get<2>(p)); 127 | } 128 | 129 | INSTANTIATE_TEST_SUITE_P( 130 | CppevTest, TestIOSocket, 131 | testing::Combine( 132 | testing::Values(family::ipv4, family::ipv6), // protocol family 133 | testing::Bool(), // enable option 134 | testing::Values(8192, 16384, 32768), // buffer or low water mark 135 | testing::Values(16, 32, 64, 128) // linger-time 136 | )); 137 | 138 | } // namespace cppev 139 | 140 | int main(int argc, char **argv) 141 | { 142 | testing::InitGoogleTest(); 143 | return RUN_ALL_TESTS(); 144 | } 145 | -------------------------------------------------------------------------------- /src/lib/subprocess.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/subprocess.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cppev/io.h" 12 | #include "cppev/utils.h" 13 | 14 | #ifdef __APPLE__ 15 | extern char **environ; 16 | #endif 17 | 18 | namespace cppev 19 | { 20 | 21 | namespace subprocess 22 | { 23 | 24 | std::tuple exec_cmd( 25 | const std::string &cmd, const std::vector &env) 26 | { 27 | subp_open subp(cmd, env); 28 | subp.wait(); 29 | return std::make_tuple(subp.returncode(), subp.stdout(), subp.stderr()); 30 | } 31 | 32 | } // namespace subprocess 33 | 34 | subp_open::subp_open(const std::string &cmd, 35 | const std::vector &env) 36 | : cmd_(cmd), env_(env) 37 | { 38 | int fds[2]; 39 | int zero, one, two; 40 | 41 | // Cannot use io_factory::get_pipes since child process will destruct 42 | // the smart pointer which causes all the fds got closed in its side 43 | // and cannot communicate with parent process. 44 | 45 | if (pipe(fds) < 0) 46 | { 47 | throw_system_error("pipe error"); 48 | } 49 | zero = fds[0]; 50 | stdin_ = std::make_unique(fds[1]); 51 | 52 | if (pipe(fds) < 0) 53 | { 54 | throw_system_error("pipe error"); 55 | } 56 | one = fds[1]; 57 | stdout_ = std::make_unique(fds[0]); 58 | 59 | if (pipe(fds) < 0) 60 | { 61 | throw_system_error("pipe error"); 62 | } 63 | two = fds[1]; 64 | stderr_ = std::make_unique(fds[0]); 65 | 66 | pid_t pid = fork(); 67 | 68 | if (pid == -1) 69 | { 70 | _exit(-1); 71 | } 72 | 73 | if (pid == 0) 74 | { 75 | dup2(zero, STDIN_FILENO); 76 | dup2(one, STDOUT_FILENO); 77 | dup2(two, STDERR_FILENO); 78 | 79 | std::vector cmd_with_args = split(cmd_, " "); 80 | std::string cmd_with_path = cmd_with_args[0]; 81 | cmd_with_args[0] = split(cmd_with_args[0], "/").back(); 82 | 83 | // The argv requires nullptr ending. 84 | char *argv[cmd_with_args.size() + 1]; 85 | memset(argv, 0, sizeof(argv)); 86 | for (size_t i = 0; i < cmd_with_args.size(); ++i) 87 | { 88 | argv[i] = const_cast(cmd_with_args[i].c_str()); 89 | } 90 | 91 | // The envp requires nullptr ending. 92 | char *envp[env_.size() + 1]; 93 | memset(envp, 0, sizeof(envp)); 94 | for (size_t i = 0; i < env_.size(); ++i) 95 | { 96 | envp[i] = const_cast(env_[i].c_str()); 97 | } 98 | environ = envp; 99 | 100 | execvp(cmd_with_path.c_str(), argv); 101 | _exit(127); 102 | } 103 | 104 | pid_ = pid; 105 | } 106 | 107 | subp_open::subp_open(subp_open &&other) = default; 108 | 109 | subp_open &subp_open::operator=(subp_open &&other) = default; 110 | 111 | subp_open::~subp_open() = default; 112 | 113 | bool subp_open::poll() 114 | { 115 | int ret = waitpid(pid_, &returncode_, WNOHANG); 116 | if (ret == -1) 117 | { 118 | throw_system_error("waitpid error"); 119 | } 120 | return ret != 0; 121 | } 122 | 123 | void subp_open::wait() 124 | { 125 | wait(std::chrono::milliseconds(50)); 126 | } 127 | 128 | void subp_open::communicate(const char *input, int len) 129 | { 130 | stdout_->read_all(); 131 | stderr_->read_all(); 132 | 133 | if (input != nullptr && len != 0) 134 | { 135 | stdin_->wbuffer().put_string(input, len); 136 | stdin_->write_all(); 137 | } 138 | } 139 | 140 | void subp_open::communicate() 141 | { 142 | communicate(nullptr, 0); 143 | } 144 | 145 | void subp_open::communicate(const std::string &input) 146 | { 147 | communicate(input.c_str(), input.size()); 148 | } 149 | 150 | void subp_open::send_signal(int sig) 151 | { 152 | if (::kill(pid(), sig) < 0) 153 | { 154 | throw_system_error("kill error"); 155 | } 156 | } 157 | 158 | void subp_open::terminate() 159 | { 160 | send_signal(SIGTERM); 161 | } 162 | 163 | void subp_open::kill() 164 | { 165 | send_signal(SIGKILL); 166 | } 167 | 168 | int subp_open::returncode() const noexcept 169 | { 170 | return returncode_; 171 | } 172 | 173 | const char *subp_open::stdout() const noexcept 174 | { 175 | return stdout_->rbuffer().data(); 176 | } 177 | 178 | const char *subp_open::stderr() const noexcept 179 | { 180 | return stderr_->rbuffer().data(); 181 | } 182 | 183 | pid_t subp_open::pid() const noexcept 184 | { 185 | return pid_; 186 | } 187 | 188 | } // namespace cppev 189 | -------------------------------------------------------------------------------- /unittest/BUILD: -------------------------------------------------------------------------------- 1 | cc_binary( 2 | name = "test_logger", 3 | srcs = [ 4 | "test_logger.cc", 5 | ], 6 | linkopts = select({ 7 | "@platforms//os:linux": [ 8 | "-lstdc++fs", 9 | ], 10 | "@platforms//os:osx": [], 11 | }), 12 | deps = [ 13 | "//src:cppev", 14 | "@googletest//:gtest", 15 | ], 16 | ) 17 | 18 | cc_test( 19 | name = "test_buffer", 20 | srcs = [ 21 | "test_buffer.cc", 22 | ], 23 | deps = [ 24 | "//src:cppev", 25 | "@googletest//:gtest", 26 | ], 27 | ) 28 | 29 | cc_test( 30 | name = "test_utils", 31 | srcs = [ 32 | "test_utils.cc", 33 | ], 34 | deps = [ 35 | "//src:cppev", 36 | "@googletest//:gtest", 37 | ], 38 | ) 39 | 40 | cc_test( 41 | name = "test_ipc", 42 | srcs = [ 43 | "config.h", 44 | "test_ipc.cc", 45 | ], 46 | deps = [ 47 | "//src:cppev", 48 | "@googletest//:gtest", 49 | ], 50 | ) 51 | 52 | cc_test( 53 | name = "test_scheduler", 54 | srcs = [ 55 | "test_scheduler.cc", 56 | ], 57 | deps = [ 58 | "//src:cppev", 59 | "@googletest//:gtest", 60 | ], 61 | ) 62 | 63 | cc_test( 64 | name = "test_io", 65 | srcs = [ 66 | "test_io.cc", 67 | ], 68 | deps = [ 69 | "//src:cppev", 70 | "@googletest//:gtest", 71 | ], 72 | ) 73 | 74 | cc_test( 75 | name = "test_event_loop", 76 | srcs = [ 77 | "test_event_loop.cc", 78 | ], 79 | linkstatic = True, 80 | deps = [ 81 | "//src:cppev", 82 | "@googletest//:gtest", 83 | ], 84 | ) 85 | 86 | cc_test( 87 | name = "test_lock", 88 | srcs = [ 89 | "config.h", 90 | "test_lock.cc", 91 | ], 92 | deps = [ 93 | "//src:cppev", 94 | "@googletest//:gtest", 95 | ], 96 | ) 97 | 98 | cc_test( 99 | name = "test_subprocess", 100 | srcs = [ 101 | "test_subprocess.cc", 102 | ], 103 | data = [ 104 | "hello.py", 105 | ], 106 | linkopts = select({ 107 | "@platforms//os:linux": [ 108 | "-lstdc++fs", 109 | ], 110 | "@platforms//os:osx": [], 111 | }), 112 | linkstatic = True, 113 | visibility = [ 114 | "//examples:__pkg__", 115 | ], 116 | deps = [ 117 | "//src:cppev", 118 | "@googletest//:gtest", 119 | ], 120 | ) 121 | 122 | cc_test( 123 | name = "test_thread_pool", 124 | srcs = [ 125 | "test_thread_pool.cc", 126 | ], 127 | deps = [ 128 | "//src:cppev", 129 | "@googletest//:gtest", 130 | ], 131 | ) 132 | 133 | cc_test( 134 | name = "test_runnable", 135 | srcs = [ 136 | "test_runnable.cc", 137 | ], 138 | deps = [ 139 | "//src:cppev", 140 | "@googletest//:gtest", 141 | ], 142 | ) 143 | 144 | cc_binary( 145 | name = "libDynamicLoaderTestImplFunctions.so", 146 | srcs = [ 147 | "DynamicLoaderTestImpl.cc", 148 | "DynamicLoaderTestImpl.h", 149 | "DynamicLoaderTestImplFunctions.cc", 150 | "DynamicLoaderTestInterface.h", 151 | ], 152 | linkshared = True, 153 | target_compatible_with = [ 154 | "@platforms//os:linux", 155 | ], 156 | ) 157 | 158 | cc_binary( 159 | name = "libDynamicLoaderTestImplFunctions.dylib", 160 | srcs = [ 161 | "DynamicLoaderTestImpl.cc", 162 | "DynamicLoaderTestImpl.h", 163 | "DynamicLoaderTestImplFunctions.cc", 164 | "DynamicLoaderTestInterface.h", 165 | ], 166 | linkshared = True, 167 | target_compatible_with = [ 168 | "@platforms//os:osx", 169 | ], 170 | ) 171 | 172 | # Cannot use //src:cppev_dynamic in ubuntu-20.04 due to 173 | # dlopen related code is in libdl.so but latter merged to 174 | # libc.so, cc_shared_library won't transfer linkopts. 175 | cc_test( 176 | name = "test_dynamic_loader", 177 | srcs = [ 178 | "DynamicLoaderTestInterface.h", 179 | "test_dynamic_loader.cc", 180 | ], 181 | data = select({ 182 | "@platforms//os:linux": [ 183 | ":libDynamicLoaderTestImplFunctions.so", 184 | ], 185 | "@platforms//os:osx": [ 186 | ":libDynamicLoaderTestImplFunctions.dylib", 187 | ], 188 | }), 189 | linkopts = select({ 190 | "@platforms//os:linux": [ 191 | "-lstdc++fs", 192 | ], 193 | "@platforms//os:osx": [], 194 | }), 195 | linkstatic = True, 196 | visibility = [ 197 | "//examples:__pkg__", 198 | ], 199 | deps = [ 200 | "//src:cppev", 201 | "@googletest//:gtest", 202 | ], 203 | ) 204 | -------------------------------------------------------------------------------- /src/include/cppev/thread_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_thread_pool_h_6C0224787A17_ 2 | #define _cppev_thread_pool_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cppev/runnable.h" 13 | #include "cppev/utils.h" 14 | 15 | namespace cppev 16 | { 17 | 18 | template 19 | class CPPEV_PUBLIC thread_pool 20 | { 21 | static_assert(std::is_base_of::value, "Not runnable"); 22 | static_assert(std::is_constructible::value, 23 | "Not constructible"); 24 | 25 | public: 26 | using container_type = std::vector>; 27 | 28 | explicit thread_pool(int thr_num, Args &&...args) 29 | { 30 | for (int i = 0; i < thr_num; ++i) 31 | { 32 | thrs_.push_back( 33 | std::make_unique(std::forward(args)...)); 34 | } 35 | } 36 | 37 | thread_pool(const thread_pool &) = delete; 38 | thread_pool &operator=(const thread_pool &) = delete; 39 | 40 | thread_pool(thread_pool &&other) = default; 41 | thread_pool &operator=(thread_pool &&other) = default; 42 | 43 | virtual ~thread_pool() = default; 44 | 45 | class iterator 46 | { 47 | public: 48 | explicit iterator(container_type &rv, int idx) : rv_(rv), idx_(idx) 49 | { 50 | } 51 | 52 | bool operator!=(const iterator &other) 53 | { 54 | return idx_ != other.idx_; 55 | } 56 | 57 | Runnable &operator*() 58 | { 59 | return *rv_[idx_]; 60 | } 61 | 62 | iterator &operator++() 63 | { 64 | ++idx_; 65 | return *this; 66 | } 67 | 68 | private: 69 | container_type &rv_; 70 | 71 | int idx_; 72 | }; 73 | 74 | iterator begin() 75 | { 76 | return iterator(thrs_, 0); 77 | } 78 | 79 | iterator end() 80 | { 81 | return iterator(thrs_, thrs_.size()); 82 | } 83 | 84 | // Run all threads. 85 | void run() 86 | { 87 | for (auto &thr : thrs_) 88 | { 89 | thr->run(); 90 | } 91 | } 92 | 93 | // Wait for all threads. 94 | void join() 95 | { 96 | for (auto &thr : thrs_) 97 | { 98 | thr->join(); 99 | } 100 | } 101 | 102 | // Cancel all threads. 103 | virtual void cancel() 104 | { 105 | for (auto &thr : thrs_) 106 | { 107 | thr->cancel(); 108 | } 109 | } 110 | 111 | // Specific const one of the threads. 112 | const Runnable &operator[](int i) const noexcept 113 | { 114 | return *(thrs_[i].get()); 115 | } 116 | 117 | // Specific one of the threads. 118 | Runnable &operator[](int i) noexcept 119 | { 120 | return *(thrs_[i].get()); 121 | } 122 | 123 | // Thread pool size. 124 | int size() const noexcept 125 | { 126 | return thrs_.size(); 127 | } 128 | 129 | protected: 130 | container_type thrs_; 131 | }; 132 | 133 | class thread_pool_task_queue; 134 | 135 | class CPPEV_PRIVATE thread_pool_task_queue_runnable final : public runnable 136 | { 137 | public: 138 | thread_pool_task_queue_runnable(thread_pool_task_queue *tptq) noexcept; 139 | 140 | void run_impl() override; 141 | 142 | private: 143 | thread_pool_task_queue *tptq_; 144 | }; 145 | 146 | using thread_pool_task_handler = std::function; 147 | 148 | class CPPEV_PUBLIC thread_pool_task_queue final 149 | : private thread_pool 151 | { 152 | friend class thread_pool_task_queue_runnable; 153 | 154 | using thread_pool_base_type = 155 | thread_pool; 156 | 157 | public: 158 | thread_pool_task_queue(int thr_num); 159 | 160 | thread_pool_task_queue(const thread_pool_task_queue &) = delete; 161 | thread_pool_task_queue &operator=(const thread_pool_task_queue &) = delete; 162 | thread_pool_task_queue(thread_pool_task_queue &&) = delete; 163 | thread_pool_task_queue &operator=(thread_pool_task_queue &&) = delete; 164 | 165 | ~thread_pool_task_queue(); 166 | 167 | using thread_pool_base_type::run; 168 | 169 | using thread_pool_base_type::size; 170 | 171 | void add_task(const thread_pool_task_handler &h) noexcept; 172 | 173 | void add_task(thread_pool_task_handler &&h) noexcept; 174 | 175 | void add_task(const std::vector &vh) noexcept; 176 | 177 | void stop() noexcept; 178 | 179 | private: 180 | std::queue queue_; 181 | 182 | std::mutex lock_; 183 | 184 | std::condition_variable cond_; 185 | 186 | bool stop_; 187 | }; 188 | 189 | } // namespace cppev 190 | 191 | #endif // thread_pool.h 192 | -------------------------------------------------------------------------------- /unittest/test_lock.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "cppev/lock.h" 8 | 9 | namespace cppev 10 | { 11 | 12 | class TestLockByThread : public testing::TestWithParam 13 | { 14 | protected: 15 | void SetUp() override 16 | { 17 | ready_ = false; 18 | } 19 | 20 | mutex lock_{sync_level::thread}; 21 | 22 | cond cond_{sync_level::thread}; 23 | 24 | bool ready_; 25 | }; 26 | 27 | TEST_P(TestLockByThread, test_rwlock_guard_movable) 28 | { 29 | rwlock rwlck(GetParam()); 30 | 31 | { 32 | rdlockguard lg(rwlck); 33 | rdlockguard lg1(std::move(lg)); 34 | lg = std::move(lg1); 35 | } 36 | EXPECT_TRUE(rwlck.try_rdlock()); 37 | rwlck.unlock(); 38 | 39 | { 40 | wrlockguard lg(rwlck); 41 | wrlockguard lg1(std::move(lg)); 42 | lg = std::move(lg1); 43 | } 44 | EXPECT_TRUE(rwlck.try_wrlock()); 45 | rwlck.unlock(); 46 | } 47 | 48 | TEST_P(TestLockByThread, test_rwlock_rdlocked) 49 | { 50 | rwlock rwlck(GetParam()); 51 | 52 | // sub-thread 53 | auto func = [this, &rwlck]() 54 | { 55 | std::unique_lock lock(this->lock_); 56 | this->ready_ = true; 57 | rwlck.rdlock(); 58 | this->cond_.notify_one(); 59 | this->cond_.wait(lock); 60 | rwlck.unlock(); 61 | ASSERT_TRUE(rwlck.try_wrlock()); 62 | rwlck.unlock(); 63 | }; 64 | std::thread thr(func); 65 | 66 | // main-thread 67 | { 68 | std::unique_lock lock(lock_); 69 | if (!ready_) 70 | { 71 | cond_.wait(lock, [this]() -> bool { return this->ready_; }); 72 | } 73 | 74 | ASSERT_TRUE(rwlck.try_rdlock()); 75 | rwlck.unlock(); 76 | ASSERT_FALSE(rwlck.try_wrlock()); 77 | cond_.notify_one(); 78 | } 79 | thr.join(); 80 | } 81 | 82 | TEST_P(TestLockByThread, test_rwlock_wrlocked) 83 | { 84 | rwlock rwlck(GetParam()); 85 | 86 | // sub-thread 87 | auto func = [this, &rwlck]() 88 | { 89 | std::unique_lock lock(this->lock_); 90 | this->ready_ = true; 91 | rwlck.wrlock(); 92 | this->cond_.notify_one(); 93 | this->cond_.wait(lock); 94 | rwlck.unlock(); 95 | }; 96 | std::thread thr(func); 97 | 98 | // main-thread 99 | { 100 | std::unique_lock lock(lock_); 101 | if (!ready_) 102 | { 103 | cond_.wait(lock, [this]() -> bool { return this->ready_; }); 104 | } 105 | ASSERT_FALSE(rwlck.try_rdlock()); 106 | ASSERT_FALSE(rwlck.try_wrlock()); 107 | cond_.notify_one(); 108 | } 109 | thr.join(); 110 | } 111 | 112 | TEST_P(TestLockByThread, test_one_time_fence_wait_first) 113 | { 114 | one_time_fence otf(GetParam()); 115 | auto func = [&]() -> void 116 | { 117 | otf.wait(); 118 | otf.wait(); 119 | otf.wait(); 120 | }; 121 | 122 | std::thread thr(func); 123 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 124 | otf.notify(); 125 | otf.notify(); 126 | thr.join(); 127 | } 128 | 129 | TEST_P(TestLockByThread, test_one_time_fence_notify_first) 130 | { 131 | one_time_fence otf(GetParam()); 132 | auto func = [&]() -> void 133 | { 134 | otf.wait(); 135 | otf.wait(); 136 | }; 137 | 138 | otf.notify(); 139 | std::thread thr(func); 140 | thr.join(); 141 | } 142 | 143 | TEST_P(TestLockByThread, test_barrier_throw) 144 | { 145 | barrier br(GetParam(), 1); 146 | EXPECT_NO_THROW(br.wait()); 147 | EXPECT_THROW(br.wait(), std::logic_error); 148 | } 149 | 150 | TEST_P(TestLockByThread, test_barrier_multithread) 151 | { 152 | const int num = 10; 153 | barrier br(GetParam(), num + 1); 154 | std::vector thrs; 155 | bool shall_throw = true; 156 | 157 | auto func = [&]() -> void 158 | { 159 | br.wait(); 160 | if (shall_throw) 161 | { 162 | throw_runtime_error("test not ok!"); 163 | } 164 | }; 165 | 166 | for (int i = 0; i < num; ++i) 167 | { 168 | thrs.push_back(std::thread(func)); 169 | } 170 | 171 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 172 | shall_throw = false; 173 | EXPECT_NO_THROW(br.wait()); 174 | 175 | for (int i = 0; i < num; ++i) 176 | { 177 | thrs[i].join(); 178 | } 179 | 180 | EXPECT_THROW(br.wait(), std::logic_error); 181 | } 182 | 183 | TEST_P(TestLockByThread, test_mutex_performance) 184 | { 185 | mutex plock(GetParam()); 186 | performance_test(plock); 187 | } 188 | 189 | INSTANTIATE_TEST_SUITE_P(CppevTest, TestLockByThread, 190 | testing::Values(sync_level::thread, 191 | sync_level::process)); 192 | 193 | } // namespace cppev 194 | 195 | int main(int argc, char **argv) 196 | { 197 | testing::InitGoogleTest(); 198 | return RUN_ALL_TESTS(); 199 | } 200 | -------------------------------------------------------------------------------- /src/include/cppev/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_logger_h_6C0224787A17_ 2 | #define _cppev_logger_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cppev/common.h" 17 | 18 | namespace cppev 19 | { 20 | 21 | // Define color macros. 22 | #define RESET_COLOR "\033[0m" // Reset to default color 23 | #define DEBUG_COLOR "\033[34m" // Light blue for DEBUG messages 24 | #define INFO_COLOR "\033[32m" // Green for INFO messages 25 | #define WARNING_COLOR "\033[33m" // Yellow for WARNING messages 26 | #define ERROR_COLOR "\033[31m" // Red for ERROR messages 27 | #define FATAL_COLOR "\033[35m" // Purple for FATAL messages 28 | 29 | // Log severity levels. 30 | enum class CPPEV_PUBLIC log_level 31 | { 32 | debug = 1 << 0, 33 | info = 1 << 1, 34 | warning = 1 << 2, 35 | error = 1 << 3, 36 | fatal = 1 << 4, 37 | }; 38 | 39 | // Thread-safe logger implementation with multiple output support. 40 | class CPPEV_PUBLIC logger 41 | { 42 | public: 43 | // Singleton access point. 44 | static logger &get_instance(); 45 | 46 | // Get current log level. 47 | log_level get_log_level() const; 48 | 49 | // Set minimum log severity level. 50 | void set_log_level(log_level level); 51 | 52 | // Add output stream destination. 53 | void add_output_stream(std::ostream &output); 54 | 55 | // Add output stream destination for specific log_level. 56 | void add_output_stream(log_level level, std::ostream &output); 57 | 58 | // Core logging method. 59 | void write_log(log_level level, const std::string &file, int line, 60 | const std::string &message); 61 | 62 | private: 63 | // Constructor which will configure output streams. 64 | logger(); 65 | 66 | // Convert log level to string representation. 67 | std::string level_to_string(log_level level) const; 68 | 69 | // Add color. 70 | void add_color(std::ostream &os, log_level level); 71 | 72 | // Add timestamp with millisecond precision. 73 | void add_timestamp(std::ostream &os); 74 | 75 | // Add thread ID information. 76 | void add_thread_id(std::ostream &os); 77 | 78 | // Reset color. 79 | void reset_color(std::ostream &os); 80 | 81 | log_level current_level_; 82 | 83 | std::unordered_map> output_streams_; 84 | 85 | std::mutex mtx_; 86 | }; 87 | 88 | // Helper class for constructing log messages. 89 | class CPPEV_INTERNAL log_message 90 | { 91 | public: 92 | // Constructor for stream-style logging. 93 | log_message(log_level level, const char *file, int line); 94 | 95 | // Constructor for printf-style formatting. 96 | log_message(log_level level, const char *file, int line, const char *format, 97 | ...); 98 | 99 | // Destructor which will call write_log. 100 | ~log_message(); 101 | 102 | // Stream interface for chaining operations. 103 | std::ostringstream &stream(); 104 | 105 | private: 106 | // Safe formatted string implementation. 107 | void format_message(const char *format, va_list args); 108 | 109 | log_level message_level_; 110 | 111 | const char *source_file_; 112 | 113 | int line_number_; 114 | 115 | std::ostringstream message_buffer_; 116 | }; 117 | 118 | } // namespace cppev 119 | 120 | // Macro helpers for log interface generation. 121 | #define LOG_BASE(level) \ 122 | if (level < cppev::logger::get_instance().get_log_level()) \ 123 | ; \ 124 | else \ 125 | cppev::log_message(level, __FILE__, __LINE__).stream() 126 | 127 | #define LOG_FMT_BASE(level, format, ...) \ 128 | if (level < cppev::logger::get_instance().get_log_level()) \ 129 | ; \ 130 | else \ 131 | cppev::log_message(level, __FILE__, __LINE__, format, ##__VA_ARGS__) 132 | 133 | // Stream-style logging macros. 134 | #define LOG_DEBUG LOG_BASE(cppev::log_level::debug) 135 | #define LOG_INFO LOG_BASE(cppev::log_level::info) 136 | #define LOG_WARNING LOG_BASE(cppev::log_level::warning) 137 | #define LOG_ERROR LOG_BASE(cppev::log_level::error) 138 | #define LOG_FATAL LOG_BASE(cppev::log_level::fatal) 139 | 140 | // Formatted logging macros. 141 | #define LOG_DEBUG_FMT(format, ...) \ 142 | LOG_FMT_BASE(cppev::log_level::debug, format, ##__VA_ARGS__) 143 | #define LOG_INFO_FMT(format, ...) \ 144 | LOG_FMT_BASE(cppev::log_level::info, format, ##__VA_ARGS__) 145 | #define LOG_WARNING_FMT(format, ...) \ 146 | LOG_FMT_BASE(cppev::log_level::warning, format, ##__VA_ARGS__) 147 | #define LOG_ERROR_FMT(format, ...) \ 148 | LOG_FMT_BASE(cppev::log_level::error, format, ##__VA_ARGS__) 149 | #define LOG_FATAL_FMT(format, ...) \ 150 | LOG_FMT_BASE(cppev::log_level::fatal, format, ##__VA_ARGS__) 151 | 152 | #endif // logger.h 153 | -------------------------------------------------------------------------------- /src/lib/event_loop_kqueue.cc: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "cppev/common.h" 14 | #include "cppev/event_loop.h" 15 | #include "cppev/utils.h" 16 | 17 | namespace cppev 18 | { 19 | 20 | using ev_type_of_kqueue = decltype(kevent::filter); 21 | using ev_mode_of_kqueue = decltype(kevent::flags); 22 | 23 | static ev_type_of_kqueue fd_event_map_wrapper_to_sys(fd_event ev) 24 | { 25 | // EVFILT_READ and EVFILT_WRITE are exclusive!!! 26 | ev_type_of_kqueue flags = 0; 27 | if (static_cast(ev & fd_event::fd_readable)) 28 | { 29 | flags = EVFILT_READ; 30 | } 31 | else if (static_cast(ev & fd_event::fd_writable)) 32 | { 33 | flags = EVFILT_WRITE; 34 | } 35 | return flags; 36 | } 37 | 38 | static fd_event fd_event_map_sys_to_wrapper(ev_type_of_kqueue ev) 39 | { 40 | // EVFILT_READ and EVFILT_WRITE are exclusive!!! 41 | fd_event flags = static_cast(0); 42 | if (ev == EVFILT_READ) 43 | { 44 | flags = fd_event::fd_readable; 45 | } 46 | else if (ev == EVFILT_WRITE) 47 | { 48 | flags = fd_event::fd_writable; 49 | } 50 | return flags; 51 | } 52 | 53 | static ev_mode_of_kqueue fd_mode_map_wrapper_to_sys(fd_event_mode mode) 54 | { 55 | switch (static_cast(mode)) 56 | { 57 | case static_cast(fd_event_mode::level_trigger): 58 | return 0; 59 | case static_cast(fd_event_mode::edge_trigger): 60 | return EV_CLEAR; 61 | case static_cast(fd_event_mode::oneshot): 62 | return EV_ONESHOT; 63 | default: 64 | throw_logic_error("Unknown mode"); 65 | } 66 | } 67 | 68 | void event_loop::fd_io_multiplexing_create_nts() 69 | { 70 | ev_fd_ = kqueue(); 71 | if (ev_fd_ < 0) 72 | { 73 | throw_system_error("kqueue error"); 74 | } 75 | } 76 | 77 | void event_loop::fd_io_multiplexing_add_nts(const std::shared_ptr &iop, 78 | fd_event ev_type) 79 | { 80 | LOG_DEBUG_FMT("Activate fd %d %s event", iop->fd(), 81 | fd_event_to_string.at(ev_type)); 82 | if (fd_event_masks_.count(iop->fd()) && 83 | static_cast(fd_event_masks_[iop->fd()] & ev_type)) 84 | { 85 | throw_logic_error("add existent event for fd ", iop->fd()); 86 | } 87 | fd_event_masks_[iop->fd()] |= ev_type; 88 | struct kevent ev; 89 | ev_mode_of_kqueue ev_add_mode = 90 | EV_ADD | fd_mode_map_wrapper_to_sys(fd_event_modes_[iop->fd()]); 91 | // &kev, ident, filter, flags, fflags, data, udata 92 | EV_SET(&ev, iop->fd(), fd_event_map_wrapper_to_sys(ev_type), ev_add_mode, 0, 93 | 0, nullptr); 94 | if (kevent(ev_fd_, &ev, 1, nullptr, 0, nullptr) < 0) 95 | { 96 | throw_system_error("kevent add error for fd ", iop->fd()); 97 | } 98 | } 99 | 100 | void event_loop::fd_io_multiplexing_del_nts(const std::shared_ptr &iop, 101 | fd_event ev_type) 102 | { 103 | LOG_DEBUG_FMT("Deactivate fd %d %s event", iop->fd(), 104 | fd_event_to_string.at(ev_type)); 105 | if (!(fd_event_masks_.count(iop->fd()) && 106 | static_cast(fd_event_masks_[iop->fd()] & ev_type))) 107 | { 108 | throw_logic_error("delete nonexistent event for fd ", iop->fd()); 109 | } 110 | fd_event_masks_[iop->fd()] ^= ev_type; 111 | if (!static_cast(fd_event_masks_[iop->fd()])) 112 | { 113 | fd_event_masks_.erase(iop->fd()); 114 | } 115 | struct kevent ev; 116 | // &kev, ident, filter, flags, fflags, data, udata 117 | EV_SET(&ev, iop->fd(), fd_event_map_wrapper_to_sys(ev_type), EV_DELETE, 0, 118 | 0, nullptr); 119 | if (kevent(ev_fd_, &ev, 1, nullptr, 0, nullptr) < 0) 120 | { 121 | throw_system_error("kevent del error for fd ", iop->fd()); 122 | } 123 | } 124 | 125 | std::vector> event_loop::fd_io_multiplexing_wait_ts( 126 | int timeout) 127 | { 128 | int nums; 129 | struct kevent evs[sysconfig::event_number]; 130 | if (timeout < 0) 131 | { 132 | nums = 133 | kevent(ev_fd_, nullptr, 0, evs, sysconfig::event_number, nullptr); 134 | } 135 | else 136 | { 137 | struct timespec ts; 138 | ts.tv_sec = timeout / 1000; 139 | ts.tv_nsec = (timeout % 1000) * 1000 * 1000; 140 | nums = kevent(ev_fd_, nullptr, 0, evs, sysconfig::event_number, &ts); 141 | } 142 | std::vector> fd_events; 143 | for (int i = 0; i < nums; ++i) 144 | { 145 | int fd = evs[i].ident; 146 | bool succeed = false; 147 | fd_event ev = fd_event_map_sys_to_wrapper(evs[i].filter); 148 | for (auto event : {fd_event::fd_readable, fd_event::fd_writable}) 149 | { 150 | if (static_cast(ev & event)) 151 | { 152 | succeed = true; 153 | fd_events.emplace_back(fd, event); 154 | } 155 | } 156 | if (!succeed) 157 | { 158 | LOG_ERROR_FMT("Kqueue event fd %d %d is invalid", fd, ev); 159 | } 160 | } 161 | return fd_events; 162 | } 163 | 164 | } // namespace cppev 165 | 166 | #endif // event loop for macOS 167 | -------------------------------------------------------------------------------- /src/lib/event_loop_epoll.cc: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cppev/common.h" 13 | #include "cppev/event_loop.h" 14 | #include "cppev/utils.h" 15 | 16 | namespace cppev 17 | { 18 | 19 | using ev_type_of_epoll = decltype(epoll_event::events); 20 | using ev_mode_of_epoll = ev_type_of_epoll; 21 | 22 | static ev_type_of_epoll fd_event_map_wrapper_to_sys(fd_event ev) 23 | { 24 | ev_type_of_epoll flags = 0; 25 | if (static_cast(ev & fd_event::fd_readable)) 26 | { 27 | flags |= EPOLLIN; 28 | } 29 | if (static_cast(ev & fd_event::fd_writable)) 30 | { 31 | flags |= EPOLLOUT; 32 | } 33 | return flags; 34 | } 35 | 36 | static fd_event fd_event_map_sys_to_wrapper(ev_type_of_epoll ev) 37 | { 38 | fd_event flags = static_cast(0); 39 | if (ev & EPOLLIN) 40 | { 41 | flags = flags | fd_event::fd_readable; 42 | } 43 | if (ev & EPOLLOUT) 44 | { 45 | flags = flags | fd_event::fd_writable; 46 | } 47 | return flags; 48 | } 49 | 50 | static ev_mode_of_epoll fd_mode_map_wrapper_to_sys(fd_event_mode mode) 51 | { 52 | switch (static_cast(mode)) 53 | { 54 | case static_cast(fd_event_mode::level_trigger): 55 | return 0; 56 | case static_cast(fd_event_mode::edge_trigger): 57 | return EPOLLET; 58 | case static_cast(fd_event_mode::oneshot): 59 | return EPOLLONESHOT; 60 | default: 61 | throw_logic_error("Unknown mode"); 62 | } 63 | } 64 | 65 | void event_loop::fd_io_multiplexing_create_nts() 66 | { 67 | ev_fd_ = epoll_create(sysconfig::event_number); 68 | if (ev_fd_ < 0) 69 | { 70 | throw_system_error("epoll_create error"); 71 | } 72 | } 73 | 74 | void event_loop::fd_io_multiplexing_add_nts(const std::shared_ptr &iop, 75 | fd_event ev_type) 76 | { 77 | LOG_DEBUG_FMT("Activate fd %d %s event", iop->fd(), 78 | fd_event_to_string.at(ev_type)); 79 | int ep_ctl; 80 | if (fd_event_masks_.count(iop->fd())) 81 | { 82 | if (static_cast(fd_event_masks_[iop->fd()] & ev_type)) 83 | { 84 | throw_logic_error("add existent event for fd ", iop->fd()); 85 | } 86 | ep_ctl = EPOLL_CTL_MOD; 87 | } 88 | else 89 | { 90 | ep_ctl = EPOLL_CTL_ADD; 91 | } 92 | fd_event_masks_[iop->fd()] |= ev_type; 93 | struct epoll_event ev; 94 | ev.data.fd = iop->fd(); 95 | ev.events = fd_event_map_wrapper_to_sys(fd_event_masks_[iop->fd()]) | 96 | fd_mode_map_wrapper_to_sys(fd_event_modes_[iop->fd()]); 97 | if (epoll_ctl(ev_fd_, ep_ctl, iop->fd(), &ev) < 0) 98 | { 99 | std::unordered_map ep_ctl_to_string = { 100 | {EPOLL_CTL_ADD, "EPOLL_CTL_ADD"}, 101 | {EPOLL_CTL_MOD, "EPOLL_CTL_MOD"}, 102 | }; 103 | throw_system_error(ep_ctl_to_string[ep_ctl], " error for fd ", 104 | iop->fd()); 105 | } 106 | } 107 | 108 | void event_loop::fd_io_multiplexing_del_nts(const std::shared_ptr &iop, 109 | fd_event ev_type) 110 | { 111 | LOG_DEBUG_FMT("Deactivate fd %d %s event", iop->fd(), 112 | fd_event_to_string.at(ev_type)); 113 | if (!(fd_event_masks_.count(iop->fd()) && 114 | static_cast(fd_event_masks_[iop->fd()] & ev_type))) 115 | { 116 | throw_logic_error("delete nonexistent event for fd ", iop->fd()); 117 | } 118 | fd_event_masks_[iop->fd()] ^= ev_type; 119 | if (!static_cast(fd_event_masks_[iop->fd()])) 120 | { 121 | fd_event_masks_.erase(iop->fd()); 122 | } 123 | if (fd_event_masks_.count(iop->fd())) 124 | { 125 | struct epoll_event ev; 126 | ev.data.fd = iop->fd(); 127 | ev.events = fd_event_map_wrapper_to_sys(fd_event_masks_[iop->fd()]) | 128 | fd_mode_map_wrapper_to_sys(fd_event_modes_[iop->fd()]); 129 | if (epoll_ctl(ev_fd_, EPOLL_CTL_MOD, iop->fd(), &ev) < 0) 130 | { 131 | throw_system_error("EPOLL_CTL_MOD error for fd ", iop->fd()); 132 | } 133 | } 134 | else 135 | { 136 | if (epoll_ctl(ev_fd_, EPOLL_CTL_DEL, iop->fd(), nullptr) < 0) 137 | { 138 | throw_system_error("EPOLL_CTL_DEL error for fd ", iop->fd()); 139 | } 140 | } 141 | } 142 | 143 | std::vector> event_loop::fd_io_multiplexing_wait_ts( 144 | int timeout) 145 | { 146 | epoll_event evs[sysconfig::event_number]; 147 | int nums = epoll_wait(ev_fd_, evs, sysconfig::event_number, timeout); 148 | if (nums < 0 && errno != EINTR) 149 | { 150 | throw_system_error("epoll_wait error"); 151 | } 152 | std::vector> fd_events; 153 | for (int i = 0; i < nums; ++i) 154 | { 155 | int fd = evs[i].data.fd; 156 | bool succeed = false; 157 | fd_event ev = fd_event_map_sys_to_wrapper(evs[i].events); 158 | for (auto event : {fd_event::fd_readable, fd_event::fd_writable}) 159 | { 160 | if (static_cast(ev & event)) 161 | { 162 | succeed = true; 163 | fd_events.emplace_back(fd, event); 164 | } 165 | } 166 | if (!succeed) 167 | { 168 | LOG_ERROR_FMT("Epoll event fd %d %d is invalid", fd, ev); 169 | } 170 | } 171 | return fd_events; 172 | } 173 | 174 | } // namespace cppev 175 | 176 | #endif // event loop for linux 177 | -------------------------------------------------------------------------------- /src/lib/logger.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/logger.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | static const std::vector all_log_levels = { 7 | log_level::debug, log_level::info, log_level::warning, log_level::error, 8 | log_level::fatal}; 9 | 10 | logger &logger::get_instance() 11 | { 12 | static logger instance; 13 | return instance; 14 | } 15 | 16 | void logger::set_log_level(log_level level) 17 | { 18 | std::unique_lock lock(mtx_); 19 | current_level_ = level; 20 | } 21 | 22 | void logger::add_output_stream(std::ostream &output) 23 | { 24 | std::unique_lock lock(mtx_); 25 | for (log_level level : all_log_levels) 26 | { 27 | output_streams_[level].push_back(&output); 28 | } 29 | } 30 | 31 | void logger::add_output_stream(log_level level, std::ostream &output) 32 | { 33 | std::unique_lock lock(mtx_); 34 | output_streams_[level].push_back(&output); 35 | } 36 | 37 | log_level logger::get_log_level() const 38 | { 39 | return current_level_; 40 | } 41 | 42 | void logger::write_log(log_level level, const std::string &file, int line, 43 | const std::string &message) 44 | { 45 | if (level < current_level_) 46 | { 47 | return; 48 | } 49 | std::stringstream log_entry; 50 | add_color(log_entry, level); 51 | add_timestamp(log_entry); 52 | log_entry << " "; 53 | add_thread_id(log_entry); 54 | log_entry << " " << "[" << level_to_string(level) << "]"; 55 | log_entry << " " << "[" << file << ":" << line << "]"; 56 | log_entry << " "; 57 | log_entry << message << std::endl; 58 | reset_color(log_entry); 59 | 60 | std::unique_lock lock(mtx_); 61 | for (auto *stream : output_streams_[level]) 62 | { 63 | if (stream) 64 | { 65 | *stream << log_entry.str(); 66 | stream->flush(); 67 | } 68 | } 69 | } 70 | 71 | logger::logger() : current_level_(log_level::info) 72 | { 73 | for (log_level level : all_log_levels) 74 | { 75 | if (level >= log_level::error) 76 | { 77 | output_streams_[level].push_back(&std::cerr); 78 | } 79 | else 80 | { 81 | output_streams_[level].push_back(&std::cout); 82 | } 83 | } 84 | } 85 | 86 | std::string logger::level_to_string(log_level level) const 87 | { 88 | switch (level) 89 | { 90 | case log_level::debug: 91 | return "DEBUG"; 92 | case log_level::info: 93 | return "INFO"; 94 | case log_level::warning: 95 | return "WARNING"; 96 | case log_level::error: 97 | return "ERROR"; 98 | case log_level::fatal: 99 | return "FATAL"; 100 | default: 101 | return "UNKNOWN"; 102 | } 103 | } 104 | 105 | void logger::add_color(std::ostream &os, log_level level) 106 | { 107 | switch (level) 108 | { 109 | case log_level::debug: 110 | os << DEBUG_COLOR; 111 | break; 112 | case log_level::info: 113 | os << INFO_COLOR; 114 | break; 115 | case log_level::warning: 116 | os << WARNING_COLOR; 117 | break; 118 | case log_level::error: 119 | os << ERROR_COLOR; 120 | break; 121 | case log_level::fatal: 122 | os << FATAL_COLOR; 123 | break; 124 | default: 125 | break; 126 | } 127 | } 128 | 129 | void logger::add_timestamp(std::ostream &os) 130 | { 131 | auto now = std::chrono::system_clock::now(); 132 | auto time = std::chrono::system_clock::to_time_t(now); 133 | auto ms = std::chrono::duration_cast( 134 | now.time_since_epoch()) % 135 | 1000; 136 | tm tm_buf; 137 | localtime_r(&time, &tm_buf); 138 | int offset_hours = tm_buf.tm_gmtoff / 3600; 139 | int offset_mins = (tm_buf.tm_gmtoff % 3600) / 60; 140 | os << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S") << '.' 141 | << std::setfill('0') << std::setw(3) << ms.count() << " " << "UTC+" 142 | << std::setw(2) << offset_hours << ":" << std::setw(2) << offset_mins; 143 | } 144 | 145 | void logger::add_thread_id(std::ostream &os) 146 | { 147 | os << "[Thread:" << std::showbase << std::hex << std::this_thread::get_id() 148 | << std::noshowbase << std::dec << "]"; 149 | } 150 | 151 | void logger::reset_color(std::ostream &os) 152 | { 153 | os << RESET_COLOR; 154 | } 155 | 156 | log_message::log_message(log_level level, const char *file, int line) 157 | : message_level_(level), source_file_(file), line_number_(line) 158 | { 159 | } 160 | 161 | log_message::log_message(log_level level, const char *file, int line, 162 | const char *format, ...) 163 | : message_level_(level), source_file_(file), line_number_(line) 164 | { 165 | va_list args; 166 | va_start(args, format); 167 | format_message(format, args); 168 | va_end(args); 169 | } 170 | 171 | log_message::~log_message() 172 | { 173 | logger::get_instance().write_log(message_level_, source_file_, line_number_, 174 | message_buffer_.str()); 175 | } 176 | 177 | std::ostringstream &log_message::stream() 178 | { 179 | return message_buffer_; 180 | } 181 | 182 | void log_message::format_message(const char *format, va_list args) 183 | { 184 | va_list args_copy; 185 | va_copy(args_copy, args); 186 | 187 | // Determine required buffer size 188 | int length = vsnprintf(nullptr, 0, format, args_copy); 189 | va_end(args_copy); 190 | 191 | if (length <= 0) 192 | { 193 | return; 194 | } 195 | 196 | // Create buffer and format message 197 | std::vector buffer(length + 1); 198 | vsnprintf(buffer.data(), buffer.size(), format, args); 199 | message_buffer_ << buffer.data(); 200 | } 201 | 202 | } // namespace cppev 203 | -------------------------------------------------------------------------------- /bztools/package.bzl: -------------------------------------------------------------------------------- 1 | CollectedFileInfo = provider( 2 | doc = "Collect executables and dynamic libraries.", 3 | fields = { 4 | "run_files": "depset[File]; The collected runtime files.", 5 | "dev_files": "depset[File]; The collected development files.", 6 | }, 7 | ) 8 | 9 | ATTR_ASPECTS = [ 10 | "data", 11 | "srcs", 12 | "deps", 13 | "dynamic_deps", 14 | ] 15 | 16 | def _collect_files_aspect_impl(target, ctx): 17 | run_files_direct = [] 18 | run_files_transitive = [] 19 | 20 | dev_files_direct = [] 21 | dev_files_transitive = [] 22 | 23 | if DefaultInfo not in target: 24 | fail("{} doesn't have DefaultInfo!".format(target.label)) 25 | if ctx.rule.kind == "cc_binary": 26 | run_files_transitive.append(target[DefaultInfo].files) 27 | 28 | # The dynamic library in runfiles is introduced by two symbolic links pointing to 29 | # the same file, which causes the file duplication issue but it's OK. 30 | run_files_transitive.append(target[DefaultInfo].default_runfiles.files) 31 | elif ctx.rule.kind == "cc_library" and ctx.attr.dev: 32 | dev_files_direct += ctx.rule.files.hdrs 33 | dev_files_transitive.append(target[DefaultInfo].files) 34 | elif ctx.rule.kind == "cc_shared_library" and ctx.attr.dev: 35 | dev_files_transitive.append(target[DefaultInfo].files) 36 | 37 | for attr in ATTR_ASPECTS: 38 | if not hasattr(ctx.rule.attr, attr): 39 | continue 40 | for dep in getattr(ctx.rule.attr, attr): 41 | if CollectedFileInfo in dep: 42 | run_files_transitive.append(dep[CollectedFileInfo].run_files) 43 | dev_files_transitive.append(dep[CollectedFileInfo].dev_files) 44 | 45 | return CollectedFileInfo( 46 | run_files = depset(direct = run_files_direct, transitive = run_files_transitive), 47 | dev_files = depset(direct = dev_files_direct, transitive = dev_files_transitive), 48 | ) 49 | 50 | collect_files_aspect = aspect( 51 | doc = "Collect file info.", 52 | implementation = _collect_files_aspect_impl, 53 | attr_aspects = ATTR_ASPECTS, 54 | attrs = { 55 | "dev": attr.bool( 56 | doc = "Whether package development files.", 57 | mandatory = True, 58 | ), 59 | }, 60 | provides = [ 61 | CollectedFileInfo, 62 | ], 63 | ) 64 | 65 | def _package_files_impl(ctx): 66 | toolchain_info = ctx.toolchains["//bztools/shell:toolchain_type"].shell_command_info 67 | cp = toolchain_info.cp 68 | tar = toolchain_info.tar 69 | 70 | run_files_transitive = [] 71 | dev_files_transitive = [] 72 | for file in ctx.attr.files: 73 | if CollectedFileInfo not in file: 74 | fail("{} doesn't have CollectedFileInfo!".format(file.label)) 75 | run_files_transitive.append(file[CollectedFileInfo].run_files) 76 | dev_files_transitive.append(file[CollectedFileInfo].dev_files) 77 | 78 | files_hash = { 79 | "run": run_files_transitive, 80 | "dev": dev_files_transitive, 81 | } 82 | 83 | files_to_package = [] 84 | 85 | excludes = {target.label: "" for target in ctx.attr.excludes} 86 | 87 | for prefix in ["run", "dev"]: 88 | file_origins = sorted(depset(transitive = files_hash[prefix]).to_list()) 89 | for file_origin in file_origins: 90 | if file_origin.owner in excludes: 91 | continue 92 | file_origin_dir = file_origin.dirname.removeprefix(ctx.bin_dir.path + "/") 93 | file_target_path = "{}/{}/{}/{}".format(ctx.label.name, prefix, file_origin_dir, file_origin.basename) 94 | file_target = ctx.actions.declare_file(file_target_path) 95 | args = ctx.actions.args() 96 | args.add("-p") 97 | args.add("-L") 98 | args.add(file_origin.path) 99 | args.add(file_target.dirname) 100 | ctx.actions.run( 101 | mnemonic = "CopyFile", 102 | progress_message = "Copying file for package", 103 | executable = cp, 104 | arguments = [args], 105 | inputs = [file_origin], 106 | outputs = [file_target], 107 | ) 108 | files_to_package.append(file_target) 109 | 110 | manifest_of_files = ctx.actions.declare_file("{}.manifest".format(ctx.label.name)) 111 | ctx.actions.write( 112 | output = manifest_of_files, 113 | content = "{}\n".format("\n".join([file.path for file in files_to_package])), 114 | ) 115 | 116 | tarball = ctx.actions.declare_file("{}.tar.gz".format(ctx.label.name)) 117 | args = ctx.actions.args() 118 | args.add("-h") 119 | args.add("-czf") 120 | args.add(tarball.path) 121 | args.add("--files-from") 122 | args.add(manifest_of_files.path) 123 | ctx.actions.run( 124 | mnemonic = "PackageFiles", 125 | progress_message = "Packaging files to generate the tarball", 126 | executable = tar, 127 | arguments = [args], 128 | inputs = [manifest_of_files] + files_to_package, 129 | outputs = [tarball], 130 | ) 131 | 132 | return DefaultInfo(files = depset([tarball])) 133 | 134 | package_files = rule( 135 | doc = "Package files using tar.", 136 | implementation = _package_files_impl, 137 | attrs = { 138 | "files": attr.label_list( 139 | doc = "Sources and binaries to package.", 140 | aspects = [ 141 | collect_files_aspect, 142 | ], 143 | mandatory = True, 144 | ), 145 | "dev": attr.bool( 146 | doc = "For collect_files_aspect.", 147 | default = False, 148 | ), 149 | "excludes": attr.label_list( 150 | doc = "Targets not to be packaged.", 151 | default = [], 152 | allow_files = True, 153 | ), 154 | }, 155 | provides = [ 156 | DefaultInfo, 157 | ], 158 | toolchains = [ 159 | "//bztools/shell:toolchain_type", 160 | ], 161 | ) 162 | -------------------------------------------------------------------------------- /src/include/cppev/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_buffer_h_6C0224787A17_ 2 | #define _cppev_buffer_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cppev/common.h" 10 | #include "cppev/utils.h" 11 | 12 | namespace cppev 13 | { 14 | 15 | template 16 | class CPPEV_PUBLIC basic_buffer final 17 | { 18 | public: 19 | basic_buffer() noexcept : basic_buffer(1) 20 | { 21 | } 22 | 23 | explicit basic_buffer(int cap) noexcept : cap_(cap), start_(0), offset_(0) 24 | { 25 | if (cap_ < 1) 26 | { 27 | cap_ = 1; 28 | } 29 | buffer_ = std::make_unique(cap_); 30 | memset(buffer_.get(), 0, cap_); 31 | } 32 | 33 | basic_buffer(const basic_buffer &other) noexcept 34 | { 35 | copy(other); 36 | } 37 | 38 | basic_buffer &operator=(const basic_buffer &other) noexcept 39 | { 40 | copy(other); 41 | return *this; 42 | } 43 | 44 | basic_buffer(basic_buffer &&other) noexcept = default; 45 | 46 | basic_buffer &operator=(basic_buffer &&other) = default; 47 | 48 | ~basic_buffer() = default; 49 | 50 | Char &operator[](int i) noexcept 51 | { 52 | return buffer_[start_ + i]; 53 | } 54 | 55 | Char at(int i) const noexcept 56 | { 57 | return buffer_[start_ + i]; 58 | } 59 | 60 | int waste() const noexcept 61 | { 62 | return start_; 63 | } 64 | 65 | int size() const noexcept 66 | { 67 | return offset_ - start_; 68 | } 69 | 70 | int capacity() const noexcept 71 | { 72 | return cap_; 73 | } 74 | 75 | int get_start() const noexcept 76 | { 77 | return start_; 78 | } 79 | 80 | void set_start(int start) noexcept 81 | { 82 | start_ = start; 83 | } 84 | 85 | int &get_start_ref() noexcept 86 | { 87 | return start_; 88 | } 89 | 90 | int get_offset() const noexcept 91 | { 92 | return offset_; 93 | } 94 | 95 | void set_offset(int offset) noexcept 96 | { 97 | offset_ = offset; 98 | } 99 | 100 | int &get_offset_ref() noexcept 101 | { 102 | return offset_; 103 | } 104 | 105 | const Char *ptr() const noexcept 106 | { 107 | return buffer_.get(); 108 | } 109 | 110 | Char *ptr() noexcept 111 | { 112 | return buffer_.get(); 113 | } 114 | 115 | const Char *data() const noexcept 116 | { 117 | return buffer_.get() + start_; 118 | } 119 | 120 | Char *data() noexcept 121 | { 122 | return buffer_.get() + start_; 123 | } 124 | 125 | // Expand buffer which will never shrink and the real capacity 126 | // is always 2^n (n>=0). 127 | // @param cap Capacity buffer aims to expand to. 128 | void resize(int cap) noexcept 129 | { 130 | if (cap_ >= cap) 131 | { 132 | return; 133 | } 134 | while (cap_ < cap) 135 | { 136 | cap_ *= 2; 137 | } 138 | std::unique_ptr nbuffer = std::make_unique(cap_); 139 | memset(nbuffer.get(), 0, cap_); 140 | for (int i = start_; i < offset_; ++i) 141 | { 142 | nbuffer[i] = buffer_[i]; 143 | } 144 | buffer_ = std::move(nbuffer); 145 | } 146 | 147 | // Move unconsumed buffer to the start. 148 | void tiny() noexcept 149 | { 150 | if (start_ == 0) 151 | { 152 | return; 153 | } 154 | int len = offset_ - start_; 155 | for (int i = 0; i < len; ++i) 156 | { 157 | buffer_[i] = buffer_[i + start_]; 158 | } 159 | memset(buffer_.get() + len, 0, start_); 160 | start_ = 0; 161 | offset_ = len; 162 | } 163 | 164 | // Clear buffer. 165 | void clear() noexcept 166 | { 167 | memset(buffer_.get(), 0, cap_); 168 | start_ = 0; 169 | offset_ = 0; 170 | } 171 | 172 | // Produce Chars to buffer. 173 | // @param ptr : Pointer to Char array. 174 | // @param len : Char array length that copies to buffer. 175 | void put_string(const Char *ptr, int len) noexcept 176 | { 177 | resize(offset_ + len); 178 | for (int i = 0; i < len; ++i) 179 | { 180 | buffer_[offset_++] = ptr[i]; 181 | } 182 | } 183 | 184 | // Produce Chars to buffer. 185 | // @param str String to put. 186 | void put_string(const std::basic_string &str) noexcept 187 | { 188 | put_string(str.c_str(), str.size()); 189 | } 190 | 191 | // Get Chars from buffer. 192 | // @param len Char array length that consumes, -1 means all. 193 | // @param consume Whether consumes the Char array. 194 | std::basic_string get_string(int len = -1, 195 | bool consume = true) noexcept 196 | { 197 | if (len == -1) 198 | { 199 | len = size(); 200 | } 201 | std::basic_string str(buffer_.get() + start_, len); 202 | if (consume) 203 | { 204 | start_ += len; 205 | } 206 | return str; 207 | } 208 | 209 | private: 210 | // Capacity, heap size 211 | int cap_; 212 | 213 | // Start of the buffer, this byte is included 214 | int start_; 215 | 216 | // End of the buffer, this byte is not included 217 | int offset_; 218 | 219 | // Heap buffer 220 | std::unique_ptr buffer_; 221 | 222 | // Copy function for copy contructor and copy assignment 223 | void copy(const basic_buffer &other) noexcept 224 | { 225 | if (&other != this) 226 | { 227 | this->cap_ = other.cap_; 228 | this->start_ = other.start_; 229 | this->offset_ = other.offset_; 230 | this->buffer_ = std::make_unique(cap_); 231 | memcpy(this->buffer_.get(), other.buffer_.get(), cap_); 232 | } 233 | } 234 | }; 235 | 236 | using buffer = basic_buffer; 237 | 238 | } // namespace cppev 239 | 240 | #endif // buffer.h 241 | -------------------------------------------------------------------------------- /src/include/cppev/scheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_scheduler_h_6C0224787A17_ 2 | #define _cppev_scheduler_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cppev/utils.h" 17 | 18 | namespace cppev 19 | { 20 | 21 | // Triggered once when thread starts 22 | using init_task_handler = std::function; 23 | 24 | // Triggered once when thread exits 25 | using exit_task_handler = std::function; 26 | 27 | // Triggered periodically by timed_scheduler 28 | // @param curr_timestamp : current trigger timestamp 29 | using timed_task_handler = 30 | std::function; 31 | 32 | template 33 | class CPPEV_PUBLIC timed_scheduler 34 | { 35 | public: 36 | // Create backend thread to execute tasks 37 | // @param timer_tasks : tasks that will be triggered regularly according to 38 | // frequency. 39 | // tuple : 40 | // @param init_tasks : tasks that will be executed once only when thread 41 | // starts 42 | // @param exit_tasks : tasks that will be executed once only when thread 43 | // exits 44 | // @param align : whether start time align to 1s. 45 | timed_scheduler( 46 | const std::vector> 47 | &timer_tasks, 48 | const std::vector &init_tasks = {}, 49 | const std::vector &exit_tasks = {}, 50 | const bool align = true) 51 | : stop_(false) 52 | { 53 | std::priority_queue, 54 | std::vector>, 55 | tuple_less, 0>> 56 | timer_tasks_heap; 57 | for (size_t i = 0; i < timer_tasks.size(); ++i) 58 | { 59 | timer_tasks_heap.push({std::get<1>(timer_tasks[i]), i}); 60 | } 61 | 62 | std::vector intervals; 63 | for (size_t i = 0; i < timer_tasks.size(); ++i) 64 | { 65 | intervals.push_back(1'000'000'000 / std::get<0>(timer_tasks[i])); 66 | } 67 | int64_t lcm = intervals[0]; 68 | int64_t gcd = intervals[0]; 69 | if (intervals.size() >= 2) 70 | { 71 | lcm = least_common_multiple(intervals); 72 | gcd = greatest_common_divisor(intervals); 73 | } 74 | interval_ = lcm; 75 | 76 | while (!timer_tasks_heap.empty()) 77 | { 78 | size_t idx = std::get<1>(timer_tasks_heap.top()); 79 | timer_tasks_heap.pop(); 80 | timer_handlers_.push_back(std::get<2>(timer_tasks[idx])); 81 | for (int64_t stamp = 0; stamp < lcm; stamp += gcd) 82 | { 83 | if (stamp % intervals[idx] == 0) 84 | { 85 | tasks_[stamp].push_back(timer_handlers_.size() - 1); 86 | } 87 | } 88 | } 89 | 90 | thr_ = std::thread( 91 | [this, init_tasks, exit_tasks, align]() 92 | { 93 | for (const auto &task : init_tasks) 94 | { 95 | task(); 96 | } 97 | auto tp_curr = Clock::now(); 98 | if (align) 99 | { 100 | tp_curr = ceil_time_point(tp_curr); 101 | std::this_thread::sleep_until(tp_curr); 102 | } 103 | while (!stop_) 104 | { 105 | for (auto iter = tasks_.cbegin(); 106 | !stop_ && (iter != tasks_.cend());) 107 | { 108 | auto curr = iter; 109 | auto next = ++iter; 110 | 111 | int64_t span; 112 | if (next == tasks_.cend()) 113 | { 114 | span = interval_ - curr->first; 115 | } 116 | else 117 | { 118 | span = next->first - curr->first; 119 | } 120 | 121 | for (auto idx : curr->second) 122 | { 123 | timer_handlers_[idx](tp_curr.time_since_epoch()); 124 | } 125 | 126 | auto tp_next = tp_curr + std::chrono::nanoseconds(span); 127 | 128 | std::this_thread::sleep_until(tp_next); 129 | 130 | tp_curr = std::chrono::time_point_cast< 131 | typename decltype(tp_curr)::duration>(tp_next); 132 | } 133 | } 134 | for (const auto &task : exit_tasks) 135 | { 136 | task(); 137 | } 138 | }); 139 | } 140 | 141 | timed_scheduler(const double freq, const timed_task_handler &handler, 142 | const bool align = true) 143 | : timed_scheduler({{freq, priority::p0, handler}}, {}, {}, align) 144 | { 145 | } 146 | 147 | timed_scheduler(const timed_scheduler &) = delete; 148 | timed_scheduler &operator=(const timed_scheduler &) = delete; 149 | timed_scheduler(timed_scheduler &&) = delete; 150 | timed_scheduler &operator=(timed_scheduler &&) = delete; 151 | 152 | ~timed_scheduler() 153 | { 154 | stop_ = true; 155 | thr_.join(); 156 | } 157 | 158 | private: 159 | // whether thread shall stop 160 | bool stop_; 161 | 162 | // time interval in nanoseconds 163 | int64_t interval_; 164 | 165 | // timed task handlers, discending priority order 166 | std::vector timer_handlers_; 167 | 168 | // timepoint -> timed handler index 169 | std::map> tasks_; 170 | 171 | // backend thread executing tasks 172 | std::thread thr_; 173 | }; 174 | 175 | } // namespace cppev 176 | 177 | #endif // scheduler.h 178 | -------------------------------------------------------------------------------- /src/lib/ipc.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/ipc.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace cppev 10 | { 11 | 12 | shared_memory::shared_memory(const std::string &name, int size, mode_t mode) 13 | : name_(name), size_(size), ptr_(nullptr), creator_(false) 14 | { 15 | int fd = shm_open(name_.c_str(), O_RDWR, mode); 16 | if (fd < 0) 17 | { 18 | if (errno == ENOENT) 19 | { 20 | fd = shm_open(name_.c_str(), O_RDWR | O_CREAT | O_EXCL, mode); 21 | if (fd < 0) 22 | { 23 | if (errno == EEXIST) 24 | { 25 | fd = shm_open(name_.c_str(), O_RDWR, mode); 26 | if (fd < 0) 27 | { 28 | throw_system_error("shm_open error"); 29 | } 30 | } 31 | else 32 | { 33 | throw_system_error("shm_open error"); 34 | } 35 | } 36 | else 37 | { 38 | creator_ = true; 39 | } 40 | } 41 | else 42 | { 43 | throw_system_error("shm_open error"); 44 | } 45 | } 46 | 47 | if (creator_) 48 | { 49 | int ret = ftruncate(fd, size_); 50 | if (ret == -1) 51 | { 52 | throw_system_error("ftruncate error"); 53 | } 54 | } 55 | ptr_ = mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 56 | if (ptr_ == MAP_FAILED) 57 | { 58 | throw_system_error("mmap error"); 59 | } 60 | close(fd); 61 | if (creator_) 62 | { 63 | memset(ptr_, 0, size_); 64 | } 65 | } 66 | 67 | shared_memory::shared_memory(shared_memory &&other) noexcept 68 | { 69 | if (&other == this) 70 | { 71 | return; 72 | } 73 | move(std::forward(other)); 74 | } 75 | 76 | shared_memory &shared_memory::operator=(shared_memory &&other) noexcept 77 | { 78 | if (&other == this) 79 | { 80 | return *this; 81 | } 82 | move(std::forward(other)); 83 | return *this; 84 | } 85 | 86 | shared_memory::~shared_memory() noexcept 87 | { 88 | if (ptr_ != nullptr && size_ != 0) 89 | { 90 | munmap(ptr_, size_); 91 | } 92 | } 93 | 94 | void shared_memory::unlink() 95 | { 96 | if (name_.size() && (shm_unlink(name_.c_str()) == -1)) 97 | { 98 | throw_system_error("shm_unlink error"); 99 | } 100 | } 101 | 102 | void *shared_memory::ptr() const noexcept 103 | { 104 | return ptr_; 105 | } 106 | 107 | int shared_memory::size() const noexcept 108 | { 109 | return size_; 110 | } 111 | 112 | bool shared_memory::creator() const noexcept 113 | { 114 | return creator_; 115 | } 116 | 117 | void shared_memory::move(shared_memory &&other) noexcept 118 | { 119 | this->name_ = other.name_; 120 | this->size_ = other.size_; 121 | this->ptr_ = other.ptr_; 122 | this->creator_ = other.creator_; 123 | 124 | other.name_ = ""; 125 | other.size_ = 0; 126 | other.ptr_ = nullptr; 127 | other.creator_ = false; 128 | } 129 | 130 | semaphore::semaphore(const std::string &name, mode_t mode) 131 | : name_(name), sem_(nullptr), creator_(false) 132 | { 133 | sem_ = sem_open(name_.c_str(), 0); 134 | if (sem_ == SEM_FAILED) 135 | { 136 | if (errno == ENOENT) 137 | { 138 | sem_ = sem_open(name_.c_str(), O_CREAT | O_EXCL, mode, 0); 139 | if (sem_ == SEM_FAILED) 140 | { 141 | if (errno == EEXIST) 142 | { 143 | sem_ = sem_open(name_.c_str(), 0); 144 | if (sem_ == SEM_FAILED) 145 | { 146 | throw_system_error("sem_open error"); 147 | } 148 | } 149 | else 150 | { 151 | throw_system_error("sem_open error"); 152 | } 153 | } 154 | else 155 | { 156 | creator_ = true; 157 | } 158 | } 159 | else 160 | { 161 | throw_system_error("sem_open error"); 162 | } 163 | } 164 | } 165 | 166 | semaphore::semaphore(semaphore &&other) noexcept 167 | { 168 | if (&other == this) 169 | { 170 | return; 171 | } 172 | move(std::forward(other)); 173 | } 174 | 175 | semaphore &semaphore::operator=(semaphore &&other) noexcept 176 | { 177 | if (&other == this) 178 | { 179 | return *this; 180 | } 181 | move(std::forward(other)); 182 | return *this; 183 | } 184 | 185 | semaphore::~semaphore() noexcept 186 | { 187 | if (sem_ != SEM_FAILED) 188 | { 189 | sem_close(sem_); 190 | } 191 | } 192 | 193 | bool semaphore::try_acquire() 194 | { 195 | if (sem_trywait(sem_) == -1) 196 | { 197 | if (errno == EINTR || errno == EAGAIN) 198 | { 199 | return false; 200 | } 201 | else 202 | { 203 | throw_system_error("sem_trywait error"); 204 | } 205 | } 206 | return true; 207 | } 208 | 209 | void semaphore::acquire(int count) 210 | { 211 | for (int i = 0; i < count; ++i) 212 | { 213 | if (sem_wait(sem_) == -1) 214 | { 215 | throw_system_error("sem_wait error"); 216 | } 217 | } 218 | } 219 | 220 | void semaphore::release(int count) 221 | { 222 | for (int i = 0; i < count; ++i) 223 | { 224 | if (sem_post(sem_) == -1) 225 | { 226 | throw_system_error("sem_post error"); 227 | } 228 | } 229 | } 230 | 231 | void semaphore::unlink() 232 | { 233 | if (name_.size() && (sem_unlink(name_.c_str()) == -1)) 234 | { 235 | throw_system_error("sem_unlink error"); 236 | } 237 | } 238 | 239 | bool semaphore::creator() const noexcept 240 | { 241 | return creator_; 242 | } 243 | 244 | void semaphore::move(semaphore &&other) noexcept 245 | { 246 | this->name_ = other.name_; 247 | this->sem_ = other.sem_; 248 | this->creator_ = other.creator_; 249 | 250 | other.name_ = ""; 251 | other.sem_ = SEM_FAILED; 252 | other.creator_ = false; 253 | } 254 | 255 | } // namespace cppev 256 | -------------------------------------------------------------------------------- /src/include/cppev/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_utils_h_6C0224787A17_ 2 | #define _cppev_utils_h_6C0224787A17_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "cppev/common.h" 17 | 18 | namespace cppev 19 | { 20 | 21 | enum class CPPEV_PUBLIC priority 22 | { 23 | highest = 100, // Internally reserved, please DONOT use! 24 | p0 = 20, 25 | p1 = 19, 26 | p2 = 18, 27 | p3 = 17, 28 | p4 = 16, 29 | p5 = 15, 30 | p6 = 14, 31 | lowest = 1, // Internally reserved, please DONOT use! 32 | }; 33 | 34 | // clang-format off 35 | 36 | struct CPPEV_PRIVATE enum_hash 37 | { 38 | template std::size_t operator()(const T &t) 39 | const noexcept 40 | { 41 | return std::hash()(static_cast(t)); 42 | } 43 | }; 44 | 45 | template 46 | struct CPPEV_PRIVATE tuple_less 47 | { 48 | bool operator()(const T &lhs, const T &rhs) const noexcept 49 | { 50 | return std::get(lhs) < std::get(rhs); 51 | } 52 | }; 53 | 54 | template 55 | struct CPPEV_PRIVATE tuple_greater 56 | { 57 | bool operator()(const T &lhs, const T &rhs) const noexcept 58 | { 59 | return std::get(lhs) > std::get(rhs); 60 | } 61 | }; 62 | 63 | // clang-format on 64 | 65 | /* 66 | * Chrono 67 | */ 68 | CPPEV_PRIVATE std::string timestamp(time_t t = -1, 69 | const char *format = nullptr); 70 | 71 | template 72 | CPPEV_PRIVATE void sleep_until(const std::chrono::nanoseconds &stamp) 73 | { 74 | std::this_thread::sleep_until( 75 | std::chrono::duration_cast(stamp)); 76 | } 77 | 78 | template 79 | CPPEV_PRIVATE typename Clock::time_point ceil_time_point( 80 | const typename Clock::time_point &point) 81 | { 82 | auto stamp = std::chrono::nanoseconds(point.time_since_epoch()).count(); 83 | int64_t ceil_stamp_nsec = (stamp / 1'000'000'000 + 1) * 1'000'000'000; 84 | auto ceil_stamp = std::chrono::duration_cast( 85 | std::chrono::nanoseconds(ceil_stamp_nsec)); 86 | return typename Clock::time_point(ceil_stamp); 87 | } 88 | 89 | /* 90 | * Algorithm 91 | */ 92 | CPPEV_PUBLIC int64_t least_common_multiple(int64_t p, int64_t r); 93 | 94 | CPPEV_PUBLIC int64_t least_common_multiple(const std::vector &nums); 95 | 96 | CPPEV_PUBLIC int64_t greatest_common_divisor(int64_t p, int64_t r); 97 | 98 | CPPEV_PUBLIC int64_t greatest_common_divisor(const std::vector &nums); 99 | 100 | /* 101 | * Exception handling 102 | */ 103 | using errno_type = std::remove_reference::type; 104 | 105 | // Template function with only one parameter shall be placed former!!! 106 | 107 | template 108 | CPPEV_PRIVATE std::ostringstream oss_writer(T err_code) 109 | { 110 | std::ostringstream oss; 111 | oss << " : errno " << err_code << " "; 112 | return oss; 113 | } 114 | 115 | template 116 | CPPEV_PRIVATE std::ostringstream oss_writer(Prev prev, Args... args) 117 | { 118 | std::ostringstream oss; 119 | oss << prev; 120 | oss << oss_writer(args...).str(); 121 | return oss; 122 | } 123 | 124 | template 125 | CPPEV_PRIVATE errno_type errno_getter(T err_code) 126 | { 127 | return err_code; 128 | } 129 | 130 | template 131 | CPPEV_PRIVATE errno_type errno_getter(Prev, Args... args) 132 | { 133 | return errno_getter(args...); 134 | } 135 | 136 | template 137 | CPPEV_PUBLIC void throw_system_error_with_specific_errno(Args... args) 138 | { 139 | std::ostringstream oss = oss_writer(args...); 140 | errno_type err_code = errno_getter(args...); 141 | throw std::system_error(std::error_code(err_code, std::system_category()), 142 | oss.str()); 143 | } 144 | 145 | template 146 | CPPEV_PUBLIC void throw_system_error(Args... args) 147 | { 148 | throw_system_error_with_specific_errno(args..., errno); 149 | } 150 | 151 | template 152 | CPPEV_PUBLIC void throw_logic_error(Args... args) 153 | { 154 | std::ostringstream oss; 155 | (oss << ... << args); 156 | throw std::logic_error(oss.str()); 157 | } 158 | 159 | template 160 | CPPEV_PUBLIC void throw_runtime_error(Args... args) 161 | { 162 | std::ostringstream oss; 163 | (oss << ... << args); 164 | throw std::runtime_error(oss.str()); 165 | } 166 | 167 | bool CPPEV_PUBLIC exception_guard(const std::function &func); 168 | 169 | /* 170 | * Process level signal handling 171 | */ 172 | CPPEV_PUBLIC void ignore_signal(int sig); 173 | 174 | CPPEV_PUBLIC void reset_signal(int sig); 175 | 176 | CPPEV_PUBLIC void handle_signal(int sig, sig_t handler = [](int) {}); 177 | 178 | CPPEV_PUBLIC void send_signal(pid_t pid, int sig); 179 | 180 | CPPEV_PUBLIC bool check_process(pid_t pid); 181 | 182 | CPPEV_PUBLIC bool check_process_group(pid_t pgid); 183 | 184 | /* 185 | * Thread level signal handling 186 | */ 187 | CPPEV_PUBLIC void thread_raise_signal(int sig); 188 | 189 | CPPEV_PUBLIC void thread_block_signal(int sig); 190 | 191 | CPPEV_PUBLIC void thread_block_signal(const std::vector &sigs); 192 | 193 | CPPEV_PUBLIC void thread_unblock_signal(int sig); 194 | 195 | CPPEV_PUBLIC void thread_unblock_signal(const std::vector &sigs); 196 | 197 | CPPEV_PUBLIC void thread_suspend_for_signal(int sig); 198 | 199 | CPPEV_PUBLIC void thread_suspend_for_signal(const std::vector &sigs); 200 | 201 | CPPEV_PUBLIC void thread_wait_for_signal(int sig); 202 | 203 | CPPEV_PUBLIC int thread_wait_for_signal(const std::vector &sigs); 204 | 205 | CPPEV_PUBLIC bool thread_check_signal_mask(int sig); 206 | 207 | CPPEV_PUBLIC bool thread_check_signal_pending(int sig); 208 | 209 | /* 210 | * String operation 211 | */ 212 | CPPEV_PUBLIC std::string join(const std::vector &str_arr, 213 | const std::string &sep) noexcept; 214 | 215 | CPPEV_PUBLIC std::string strip(const std::string &str, 216 | const std::string &chars); 217 | 218 | CPPEV_PUBLIC std::string lstrip(const std::string &str, 219 | const std::string &chars); 220 | 221 | CPPEV_PUBLIC std::string rstrip(const std::string &str, 222 | const std::string &chars); 223 | 224 | CPPEV_PUBLIC std::vector split(const std::string &str, 225 | const std::string &sep); 226 | 227 | } // namespace cppev 228 | 229 | #endif // utils.h 230 | -------------------------------------------------------------------------------- /src/include/cppev/lock.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_lock_h_6C0224787A17_ 2 | #define _cppev_lock_h_6C0224787A17_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cppev/common.h" 12 | #include "cppev/utils.h" 13 | 14 | namespace cppev 15 | { 16 | 17 | enum class sync_level 18 | { 19 | thread, 20 | process, 21 | }; 22 | 23 | class CPPEV_PUBLIC mutex final 24 | { 25 | friend class cond; 26 | 27 | public: 28 | mutex(sync_level sl); 29 | 30 | mutex(const mutex &) = delete; 31 | mutex &operator=(const mutex &) = delete; 32 | mutex(mutex &&) = delete; 33 | mutex &operator=(mutex &&) = delete; 34 | 35 | ~mutex() noexcept; 36 | 37 | void lock(); 38 | 39 | bool try_lock(); 40 | 41 | void unlock(); 42 | 43 | private: 44 | pthread_mutex_t lock_; 45 | }; 46 | 47 | class CPPEV_PUBLIC cond final 48 | { 49 | public: 50 | using predicate = std::function; 51 | 52 | cond(sync_level sl); 53 | 54 | cond(const cond &) = delete; 55 | cond &operator=(const cond &) = delete; 56 | cond(cond &&) = delete; 57 | cond &operator=(cond &&) = delete; 58 | 59 | ~cond() noexcept; 60 | 61 | void wait(std::unique_lock &lock); 62 | 63 | void wait(std::unique_lock &lock, const predicate &pred); 64 | 65 | template 66 | std::cv_status wait_for(std::unique_lock &lock, 67 | const std::chrono::duration &rel_time) 68 | { 69 | return wait_until(lock, std::chrono::steady_clock::now() + rel_time); 70 | } 71 | 72 | template 73 | bool wait_for(std::unique_lock &lock, 74 | const std::chrono::duration &rel_time, 75 | const predicate &pred) 76 | { 77 | return wait_until(lock, std::chrono::steady_clock::now() + rel_time, 78 | pred); 79 | } 80 | 81 | template 82 | std::cv_status wait_until( 83 | std::unique_lock &lock, 84 | const std::chrono::time_point 85 | &abs_time) 86 | { 87 | auto n_abs_time = std::chrono::duration_cast( 88 | abs_time.time_since_epoch()) 89 | .count(); 90 | timespec ts; 91 | ts.tv_sec = n_abs_time / 1'000'000'000; 92 | ts.tv_nsec = n_abs_time % 1'000'000'000; 93 | 94 | // The implementation uses system clock to align with the standard 95 | // library. 96 | int ret = pthread_cond_timedwait(&cond_, &lock.mutex()->lock_, &ts); 97 | std::cv_status status = std::cv_status::no_timeout; 98 | if (ret != 0) 99 | { 100 | if (ret == EINVAL) 101 | { 102 | throw_system_error_with_specific_errno( 103 | "pthread_cond_wait error", ret); 104 | } 105 | else if (ret == ETIMEDOUT) 106 | { 107 | status = std::cv_status::timeout; 108 | } 109 | } 110 | return status; 111 | } 112 | 113 | template 114 | std::cv_status wait_until( 115 | std::unique_lock &lock, 116 | const std::chrono::time_point &abs_time) 117 | { 118 | auto sys_abs_time = 119 | std::chrono::system_clock::now() + (abs_time - Clock::now()); 120 | return wait_until(lock, sys_abs_time); 121 | } 122 | 123 | template 124 | bool wait_until(std::unique_lock &lock, 125 | const std::chrono::time_point &abs_time, 126 | predicate pred) 127 | { 128 | while (!pred()) 129 | { 130 | if (wait_until(lock, abs_time) == std::cv_status::timeout) 131 | { 132 | return pred(); 133 | } 134 | } 135 | return true; 136 | } 137 | 138 | void notify_one(); 139 | 140 | void notify_all(); 141 | 142 | private: 143 | pthread_cond_t cond_; 144 | }; 145 | 146 | class CPPEV_PUBLIC one_time_fence final 147 | { 148 | public: 149 | one_time_fence(sync_level sl); 150 | 151 | one_time_fence(const one_time_fence &) = delete; 152 | one_time_fence &operator=(const one_time_fence &) = delete; 153 | one_time_fence(one_time_fence &&) = delete; 154 | one_time_fence &operator=(one_time_fence &&) = delete; 155 | 156 | ~one_time_fence(); 157 | 158 | void wait(); 159 | 160 | void notify(); 161 | 162 | private: 163 | bool ok_; 164 | 165 | mutex lock_; 166 | 167 | cond cond_; 168 | }; 169 | 170 | class CPPEV_PUBLIC barrier final 171 | { 172 | public: 173 | barrier(sync_level sl, int count); 174 | 175 | barrier(const barrier &) = delete; 176 | barrier &operator=(const barrier &) = delete; 177 | barrier(barrier &&) = delete; 178 | barrier &operator=(barrier &&) = delete; 179 | 180 | ~barrier(); 181 | 182 | void wait(); 183 | 184 | private: 185 | int count_; 186 | 187 | mutex lock_; 188 | 189 | cond cond_; 190 | }; 191 | 192 | class CPPEV_PUBLIC rwlock final 193 | { 194 | public: 195 | rwlock(sync_level sl); 196 | 197 | rwlock(const rwlock &) = delete; 198 | rwlock &operator=(const rwlock &) = delete; 199 | rwlock(rwlock &&) = delete; 200 | rwlock &operator=(rwlock &&) = delete; 201 | 202 | ~rwlock() noexcept; 203 | 204 | void unlock(); 205 | 206 | void rdlock(); 207 | 208 | void wrlock(); 209 | 210 | bool try_rdlock(); 211 | 212 | bool try_wrlock(); 213 | 214 | private: 215 | pthread_rwlock_t lock_; 216 | }; 217 | 218 | class CPPEV_PUBLIC rdlockguard final 219 | { 220 | public: 221 | explicit rdlockguard(rwlock &lock); 222 | 223 | rdlockguard(const rdlockguard &) = delete; 224 | rdlockguard &operator=(const rdlockguard &) = delete; 225 | rdlockguard(rdlockguard &&other) noexcept; 226 | rdlockguard &operator=(rdlockguard &&other) noexcept; 227 | 228 | ~rdlockguard() noexcept; 229 | 230 | void lock(); 231 | 232 | void unlock(); 233 | 234 | private: 235 | rwlock *rwlock_; 236 | }; 237 | 238 | class CPPEV_PUBLIC wrlockguard final 239 | { 240 | public: 241 | explicit wrlockguard(rwlock &lock); 242 | 243 | wrlockguard(const wrlockguard &) = delete; 244 | wrlockguard &operator=(const wrlockguard &) = delete; 245 | wrlockguard(wrlockguard &&other) noexcept; 246 | wrlockguard &operator=(wrlockguard &&other) noexcept; 247 | 248 | ~wrlockguard() noexcept; 249 | 250 | void lock(); 251 | 252 | void unlock(); 253 | 254 | private: 255 | rwlock *rwlock_; 256 | }; 257 | 258 | } // namespace cppev 259 | 260 | #endif // lock.h 261 | -------------------------------------------------------------------------------- /src/include/cppev/event_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _cppev_event_loop_h_6C0224787A17_ 2 | #define _cppev_event_loop_h_6C0224787A17_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "cppev/common.h" 15 | #include "cppev/io.h" 16 | #include "cppev/logger.h" 17 | #include "cppev/utils.h" 18 | 19 | namespace cppev 20 | { 21 | 22 | enum class CPPEV_PUBLIC fd_event 23 | { 24 | fd_readable = 1 << 0, 25 | fd_writable = 1 << 1, 26 | }; 27 | 28 | /* 29 | About Edge Trigger: 30 | 1) Readable and reading not complete from sys-buffer: 31 | Same for epoll / kqueue, won't trigger again, unless comes the next 32 | message from opposite. 33 | 2) Writable and writing not fulfill the sys-buffer: 34 | Different, epoll won't trigger again, kqueue will keep triggering. 35 | 36 | Suggest using io::read_all / io::write_all. 37 | */ 38 | enum class CPPEV_PUBLIC fd_event_mode 39 | { 40 | level_trigger = 1 << 0, 41 | edge_trigger = 1 << 1, 42 | oneshot = 1 << 2, 43 | }; 44 | 45 | CPPEV_PRIVATE fd_event operator&(fd_event lhs, fd_event rhs); 46 | 47 | CPPEV_PRIVATE fd_event operator|(fd_event lhs, fd_event rhs); 48 | 49 | CPPEV_PRIVATE fd_event operator^(fd_event lhs, fd_event rhs); 50 | 51 | CPPEV_PRIVATE void operator&=(fd_event &lhs, fd_event rhs); 52 | 53 | CPPEV_PRIVATE void operator|=(fd_event &lhs, fd_event rhs); 54 | 55 | CPPEV_PRIVATE void operator^=(fd_event &lhs, fd_event rhs); 56 | 57 | CPPEV_PRIVATE extern const std::unordered_map 58 | fd_event_to_string; 59 | 60 | using fd_event_handler = std::function &)>; 61 | 62 | struct CPPEV_PRIVATE fd_event_hash 63 | { 64 | std::size_t operator()(const std::tuple &ev) const noexcept; 65 | }; 66 | 67 | class CPPEV_PUBLIC event_loop 68 | { 69 | public: 70 | explicit event_loop(void *data = nullptr, void *owner = nullptr); 71 | 72 | event_loop(const event_loop &) = delete; 73 | event_loop &operator=(const event_loop &) = delete; 74 | event_loop(event_loop &&) = delete; 75 | event_loop &operator=(event_loop &&) = delete; 76 | 77 | virtual ~event_loop() noexcept; 78 | 79 | // External data for eventloop. 80 | void *data() noexcept; 81 | 82 | // External data for eventloop. 83 | const void *data() const noexcept; 84 | 85 | // External class owns eventloop. 86 | void *owner() noexcept; 87 | 88 | // External class owns eventloop. 89 | const void *owner() const noexcept; 90 | 91 | // Workloads of the event loop fd. 92 | int ev_loads() const noexcept; 93 | 94 | // Set fd event mode, shall be called before activate, or default mode will 95 | // be used. 96 | // Caution: DONOT try to set different modes for the same fd's 97 | // events. It's different for epoll / kqueue. 98 | // Note: If user wants to atomicly set mode and activate, shall implement 99 | // it themselves by mutex. 100 | // @param iop io smart pointer. 101 | // @param ev_mode event mode. 102 | void fd_set_mode(const std::shared_ptr &iop, fd_event_mode ev_mode); 103 | 104 | // Register fd event to event pollor but not activate in 105 | // sys-io-multiplexing. 106 | // @param iop io smart pointer. 107 | // @param ev_type event type. 108 | // @param handler fd event handler. 109 | // @param prio event priority. 110 | void fd_register(const std::shared_ptr &iop, fd_event ev_type, 111 | const fd_event_handler &handler = fd_event_handler(), 112 | priority prio = priority::p0); 113 | 114 | // Activate fd event. 115 | // @param iop io smart pointer. 116 | // @param ev_type event type. 117 | void fd_activate(const std::shared_ptr &iop, fd_event ev_type); 118 | 119 | // Register fd event to event pollor and activate in sys-io-multiplexing. 120 | // @param iop io smart pointer. 121 | // @param ev_type event type. 122 | // @param handler fd event handler. 123 | // @param prio event priority. 124 | void fd_register_and_activate( 125 | const std::shared_ptr &iop, fd_event ev_type, 126 | const fd_event_handler &handler = fd_event_handler(), 127 | priority prio = priority::p0); 128 | 129 | // Remove fd event from event pollor but not deactivate in 130 | // sys-io-multiplexing. 131 | // @param iop io smart pointer. 132 | // @param ev_type event type. 133 | void fd_remove(const std::shared_ptr &iop, fd_event ev_type); 134 | 135 | // Deactivate fd event. 136 | // @param iop io smart pointer. 137 | // @param ev_type event type. 138 | void fd_deactivate(const std::shared_ptr &iop, fd_event ev_type); 139 | 140 | // Remove fd event from event pollor and deactivate in sys-io-multiplexing. 141 | // @param iop io smart pointer. 142 | // @param ev_type event type. 143 | void fd_remove_and_deactivate(const std::shared_ptr &iop, 144 | fd_event ev_type); 145 | 146 | // Delete and deactivate all events of the fd, clean all related data. 147 | // @param iop io smart pointer. 148 | void fd_clean(const std::shared_ptr &iop); 149 | 150 | // Wait for events, only loop once. 151 | // @param timeout timeout in millisecond, -1 means infinite. 152 | void loop_once(int timeout = -1); 153 | 154 | // Wait for events, loop infinitely. 155 | // @param timeout timeout in millisecond, -1 means infinite. 156 | void loop_forever(int timeout = -1); 157 | 158 | // Stop loop. 159 | void stop_loop(); 160 | 161 | // Stop loop with timeout. 162 | // @param timeout timeout in millisecond. 163 | // @return true: other thread loop stopped; false: timeout. 164 | bool stop_loop(int timeout); 165 | 166 | private: 167 | // Helper function to register fd event to event pollor. 168 | // @param iop io smart pointer. 169 | // @param ev_type event type. 170 | // @param handler fd event handler. 171 | // @param prio event priority. 172 | void fd_register_nts(const std::shared_ptr &iop, fd_event ev_type, 173 | const fd_event_handler &handler, priority prio); 174 | 175 | // Helper function to remove fd event from event pollor. 176 | // @param iop io smart pointer. 177 | void fd_remove_nts(const std::shared_ptr &iop, fd_event ev_type); 178 | 179 | // Helper function to create io multiplexing fd. 180 | // Implementation specific. 181 | void fd_io_multiplexing_create_nts(); 182 | 183 | // Helper function to add fd event listening. 184 | // Implementation specific. 185 | // @param iop io smart pointer. 186 | // @param ev_type event type. 187 | void fd_io_multiplexing_add_nts(const std::shared_ptr &iop, 188 | fd_event ev_type); 189 | 190 | // Helper function to delete fd event listening. 191 | // Implementation specific. 192 | // @param iop io smart pointer. 193 | // @param ev_type event type. 194 | void fd_io_multiplexing_del_nts(const std::shared_ptr &iop, 195 | fd_event ev_type); 196 | 197 | // Helper function to wait for event(s) trigger. 198 | // Implementation specific. 199 | // @param timeout timeout in millisecond, -1 means infinite. 200 | // @return list of fd with an event, events of one fd are 201 | // seperated. 202 | std::vector> fd_io_multiplexing_wait_ts( 203 | int timeout); 204 | 205 | using waiter_type = std::function &)>; 206 | 207 | // Helper function to stop event loop. 208 | // @param waiter waiter function. 209 | // @return true: other thread loop stopped; false: timeout. 210 | bool stop_loop_ts_wl(waiter_type waiter); 211 | 212 | // Protect the internal data structures to guarantee thread safety of 213 | // "register / remove / loop". 214 | std::mutex lock_; 215 | 216 | // For thread sychronization in stopping loop. One possible way is using 217 | // blocking io, but author witnessed read a block io in osx causing cpu 218 | // 100%. 219 | std::condition_variable cond_; 220 | 221 | // Event watcher fd. 222 | int ev_fd_; 223 | 224 | // External data for eventloop. 225 | void *data_; 226 | 227 | // External class which owns eventloop. 228 | void *owner_; 229 | 230 | // Hash: (fd, event) --> (priority, io, callback). 231 | std::unordered_map, 232 | std::tuple, 233 | std::shared_ptr>, 234 | fd_event_hash> 235 | fd_event_datas_; 236 | 237 | // Hash: fd --> fd_event. 238 | std::unordered_map fd_event_masks_; 239 | 240 | // Hash: fd --> fd_event_mode. 241 | // From system API, epoll requires same event mode for one fd, kqueue seems 242 | // not. 243 | std::unordered_map fd_event_modes_; 244 | 245 | // Whether loop shall be stopped. 246 | bool stop_; 247 | 248 | // Default fd event mode. 249 | static const fd_event_mode fd_event_mode_default_; 250 | }; 251 | 252 | } // namespace cppev 253 | 254 | #endif // event_loop.h 255 | -------------------------------------------------------------------------------- /src/lib/event_loop_common.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/event_loop.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | fd_event operator&(fd_event lhs, fd_event rhs) 7 | { 8 | return static_cast(static_cast(lhs) & static_cast(rhs)); 9 | } 10 | 11 | fd_event operator|(fd_event lhs, fd_event rhs) 12 | { 13 | return static_cast(static_cast(lhs) | static_cast(rhs)); 14 | } 15 | 16 | fd_event operator^(fd_event lhs, fd_event rhs) 17 | { 18 | return static_cast(static_cast(lhs) ^ static_cast(rhs)); 19 | } 20 | 21 | void operator&=(fd_event &lhs, fd_event rhs) 22 | { 23 | lhs = static_cast(static_cast(lhs) & static_cast(rhs)); 24 | } 25 | 26 | void operator|=(fd_event &lhs, fd_event rhs) 27 | { 28 | lhs = static_cast(static_cast(lhs) | static_cast(rhs)); 29 | } 30 | 31 | void operator^=(fd_event &lhs, fd_event rhs) 32 | { 33 | lhs = static_cast(static_cast(lhs) ^ static_cast(rhs)); 34 | } 35 | 36 | const std::unordered_map fd_event_to_string = { 37 | {fd_event::fd_readable, "fd_readable"}, 38 | {fd_event::fd_writable, "fd_writable"}, 39 | }; 40 | 41 | std::size_t fd_event_hash::operator()( 42 | const std::tuple &ev) const noexcept 43 | { 44 | return std::hash()((std::get<0>(ev) << 2) + 45 | static_cast(std::get<1>(ev))); 46 | } 47 | 48 | const fd_event_mode event_loop::fd_event_mode_default_ = 49 | fd_event_mode::level_trigger; 50 | 51 | event_loop::event_loop(void *data, void *owner) 52 | : data_(data), owner_(owner), stop_(false) 53 | { 54 | fd_io_multiplexing_create_nts(); 55 | } 56 | 57 | event_loop::~event_loop() noexcept 58 | { 59 | close(ev_fd_); 60 | } 61 | 62 | void *event_loop::data() noexcept 63 | { 64 | return data_; 65 | } 66 | 67 | const void *event_loop::data() const noexcept 68 | { 69 | return data_; 70 | } 71 | 72 | void *event_loop::owner() noexcept 73 | { 74 | return owner_; 75 | } 76 | 77 | const void *event_loop::owner() const noexcept 78 | { 79 | return owner_; 80 | } 81 | 82 | int event_loop::ev_loads() const noexcept 83 | { 84 | return fd_event_datas_.size(); 85 | } 86 | 87 | void event_loop::fd_set_mode(const std::shared_ptr &iop, 88 | fd_event_mode ev_mode) 89 | { 90 | std::unique_lock lock(lock_); 91 | fd_event_modes_[iop->fd()] = ev_mode; 92 | } 93 | 94 | void event_loop::fd_register(const std::shared_ptr &iop, fd_event ev_type, 95 | const fd_event_handler &handler, priority prio) 96 | { 97 | std::unique_lock lock(lock_); 98 | fd_register_nts(iop, ev_type, handler, prio); 99 | } 100 | 101 | void event_loop::fd_activate(const std::shared_ptr &iop, fd_event ev_type) 102 | { 103 | std::unique_lock lock(lock_); 104 | fd_io_multiplexing_add_nts(iop, ev_type); 105 | } 106 | 107 | void event_loop::fd_register_and_activate(const std::shared_ptr &iop, 108 | fd_event ev_type, 109 | const fd_event_handler &handler, 110 | priority prio) 111 | { 112 | std::unique_lock lock(lock_); 113 | fd_register_nts(iop, ev_type, handler, prio); 114 | fd_io_multiplexing_add_nts(iop, ev_type); 115 | } 116 | 117 | void event_loop::fd_remove(const std::shared_ptr &iop, fd_event ev_type) 118 | { 119 | std::unique_lock lock(lock_); 120 | fd_remove_nts(iop, ev_type); 121 | } 122 | 123 | void event_loop::fd_deactivate(const std::shared_ptr &iop, fd_event ev_type) 124 | { 125 | std::unique_lock lock(lock_); 126 | fd_io_multiplexing_del_nts(iop, ev_type); 127 | } 128 | 129 | void event_loop::fd_remove_and_deactivate(const std::shared_ptr &iop, 130 | fd_event ev_type) 131 | { 132 | std::unique_lock lock(lock_); 133 | fd_io_multiplexing_del_nts(iop, ev_type); 134 | fd_remove_nts(iop, ev_type); 135 | } 136 | 137 | void event_loop::fd_clean(const std::shared_ptr &iop) 138 | { 139 | std::unique_lock lock(lock_); 140 | for (auto ev : {fd_event::fd_readable, fd_event::fd_writable}) 141 | { 142 | if (fd_event_masks_.count(iop->fd()) && 143 | static_cast(ev & fd_event_masks_[iop->fd()])) 144 | { 145 | fd_io_multiplexing_del_nts(iop, ev); 146 | } 147 | if (fd_event_datas_.count(std::make_tuple(iop->fd(), ev))) 148 | { 149 | fd_remove_nts(iop, ev); 150 | } 151 | } 152 | fd_event_modes_.erase(iop->fd()); 153 | iop->set_evlp(nullptr); 154 | } 155 | 156 | void event_loop::loop_once(int timeout) 157 | { 158 | auto fd_events = fd_io_multiplexing_wait_ts(timeout); 159 | for (const auto &fd_ev_tp : fd_events) 160 | { 161 | LOG_DEBUG_FMT("About to trigger fd %d %s event", std::get<0>(fd_ev_tp), 162 | fd_event_to_string.at(std::get<1>(fd_ev_tp))); 163 | } 164 | std::priority_queue, 165 | std::shared_ptr>> 166 | fd_callbacks; 167 | { 168 | std::unique_lock lock(lock_); 169 | for (const auto &fd_ev_tp : fd_events) 170 | { 171 | int fd = std::get<0>(fd_ev_tp); 172 | fd_event ev = std::get<1>(fd_ev_tp); 173 | if (fd_event_datas_.count(fd_ev_tp)) 174 | { 175 | if (fd_event_masks_.count(fd) && 176 | static_cast(fd_event_masks_[fd] & ev)) 177 | { 178 | const auto &value = fd_event_datas_[fd_ev_tp]; 179 | fd_callbacks.emplace(std::get<0>(value), std::get<1>(value), 180 | std::get<2>(value)); 181 | } 182 | else 183 | { 184 | LOG_WARNING_FMT( 185 | "Trying to proceed fd %d %s event but it's not " 186 | "activate", 187 | fd, fd_event_to_string.at(ev)); 188 | } 189 | } 190 | else 191 | { 192 | LOG_WARNING_FMT( 193 | "Trying to proceed fd %d %s event but callback data not " 194 | "found", 195 | fd, fd_event_to_string.at(ev)); 196 | } 197 | } 198 | } 199 | 200 | while (fd_callbacks.size()) 201 | { 202 | auto ev = fd_callbacks.top(); 203 | fd_callbacks.pop(); 204 | (*std::get<2>(ev))(std::get<1>(ev)); 205 | } 206 | } 207 | 208 | bool event_loop::stop_loop_ts_wl(waiter_type waiter) 209 | { 210 | auto iopps = io_factory::get_pipes(); 211 | iopps[1]->set_evlp(this); 212 | LOG_DEBUG_FMT("Use fd %d fd_writable event for event loop stop", 213 | iopps[1]->fd()); 214 | fd_event_handler handler = [](const std::shared_ptr &iop) 215 | { 216 | event_loop &evlp = iop->evlp(); 217 | evlp.fd_remove_and_deactivate(iop, fd_event::fd_writable); 218 | LOG_DEBUG_FMT("Remove fd %d fd_writable event for event loop stop", 219 | iop->fd()); 220 | { 221 | std::unique_lock lock(evlp.lock_); 222 | evlp.stop_ = true; 223 | evlp.cond_.notify_all(); 224 | } 225 | }; 226 | this->fd_register_and_activate(std::dynamic_pointer_cast(iopps[1]), 227 | fd_event::fd_writable, handler, 228 | priority::lowest); 229 | 230 | std::unique_lock lock(lock_); 231 | return waiter(lock); 232 | } 233 | 234 | void event_loop::stop_loop() 235 | { 236 | waiter_type waiter = [this](std::unique_lock &lock) -> bool 237 | { 238 | this->cond_.wait(lock, [this] { return this->stop_; }); 239 | return true; 240 | }; 241 | stop_loop_ts_wl(std::move(waiter)); 242 | } 243 | 244 | bool event_loop::stop_loop(int timeout) 245 | { 246 | waiter_type waiter = [this, 247 | timeout](std::unique_lock &lock) -> bool 248 | { 249 | return this->cond_.wait_for(lock, std::chrono::milliseconds(timeout), 250 | [this] { return this->stop_; }); 251 | }; 252 | return stop_loop_ts_wl(std::move(waiter)); 253 | } 254 | 255 | void event_loop::loop_forever(int timeout) 256 | { 257 | stop_ = false; 258 | while (!stop_) 259 | { 260 | loop_once(timeout); 261 | } 262 | } 263 | 264 | void event_loop::fd_register_nts(const std::shared_ptr &iop, 265 | fd_event ev_type, 266 | const fd_event_handler &handler, priority prio) 267 | { 268 | iop->set_evlp(this); 269 | auto fd_ev_tp = std::make_tuple(iop->fd(), ev_type); 270 | fd_event_datas_.emplace( 271 | fd_ev_tp, std::make_tuple(prio, iop, 272 | std::make_shared(handler))); 273 | if (!fd_event_modes_.count(iop->fd())) 274 | { 275 | fd_event_modes_[iop->fd()] = fd_event_mode_default_; 276 | } 277 | } 278 | 279 | void event_loop::fd_remove_nts(const std::shared_ptr &iop, fd_event ev_type) 280 | { 281 | auto fd_ev_tp = std::make_tuple(iop->fd(), ev_type); 282 | fd_event_datas_.erase(fd_ev_tp); 283 | } 284 | 285 | } // namespace cppev 286 | -------------------------------------------------------------------------------- /unittest/test_event_loop.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "cppev/event_loop.h" 7 | #include "cppev/io.h" 8 | #include "cppev/logger.h" 9 | 10 | namespace cppev 11 | { 12 | 13 | TEST(TestEvlpEnumClass, test_enum_class_operator_or) 14 | { 15 | fd_event fd1 = fd_event::fd_readable; 16 | fd_event fd2 = fd_event::fd_writable; 17 | fd_event fd3 = fd1 | fd2; 18 | fd2 |= fd1; 19 | ASSERT_EQ(fd3, fd2); 20 | ASSERT_NE(fd3, fd1); 21 | } 22 | 23 | TEST(TestEvlpEnumClass, test_enum_class_operator_and) 24 | { 25 | fd_event fd1 = fd_event::fd_readable; 26 | fd_event fd2 = fd_event::fd_writable; 27 | fd_event fd3 = fd1 & fd2; 28 | fd2 &= fd1; 29 | ASSERT_EQ(fd3, fd2); 30 | } 31 | 32 | TEST(TestEvlpEnumClass, test_enum_class_operator_xor) 33 | { 34 | fd_event fd1 = fd_event::fd_readable; 35 | fd_event fd2 = fd_event::fd_writable; 36 | fd_event fd3 = fd1 ^ fd2; 37 | fd_event fd4 = fd1 | fd2; 38 | fd2 ^= fd1; 39 | ASSERT_EQ(fd3, fd4); 40 | ASSERT_EQ(fd3, fd2); 41 | fd2 ^= fd1; 42 | ASSERT_EQ(fd1 ^ fd2, fd4); 43 | ASSERT_EQ(fd4 ^ fd2, fd1); 44 | ASSERT_EQ(fd4 ^ fd1, fd2); 45 | } 46 | 47 | const char *str = "Cppev is a C++ event driven library"; 48 | 49 | class TestEventLoop : public testing::TestWithParam 50 | { 51 | }; 52 | 53 | TEST_P(TestEventLoop, test_tcp_connect_with_evlp_first) 54 | { 55 | cppev::logger::get_instance().set_log_level(cppev::log_level::info); 56 | 57 | std::vector> vec = { 58 | {family::ipv4, 8884, "127.0.0.1"}, 59 | {family::ipv6, 8886, "::1"}, 60 | }; 61 | 62 | int acpt_count = 0; 63 | event_loop acpt_evlp(&acpt_count); 64 | 65 | int cont_count = 0; 66 | event_loop cont_evlp(&cont_count); 67 | 68 | auto p = GetParam(); 69 | 70 | fd_event_handler acpt_writable_callback = 71 | [p](const std::shared_ptr &iop) 72 | { 73 | auto dp = reinterpret_cast(iop->evlp().data()); 74 | dp ? ++(*dp) : 0; 75 | auto conns = std::dynamic_pointer_cast(iop)->accept(); 76 | for (auto conn : conns) 77 | { 78 | iop->evlp().fd_set_mode(conn, p); 79 | iop->evlp().fd_register_and_activate( 80 | std::static_pointer_cast(conn), fd_event::fd_writable, 81 | [](const std::shared_ptr &iop) 82 | { 83 | LOG_INFO_FMT( 84 | "Server side connected socket %d writable event " 85 | "triggered", 86 | iop->fd()); 87 | auto iopt = std::dynamic_pointer_cast(iop); 88 | iopt->wbuffer().put_string(str); 89 | iopt->write_all(); 90 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); 91 | }); 92 | } 93 | }; 94 | 95 | for (size_t i = 0; i < vec.size(); ++i) 96 | { 97 | // Test Event Loop API 98 | auto listesock = io_factory::get_socktcp(std::get<0>(vec[i])); 99 | listesock->bind(std::get<1>(vec[i])); 100 | listesock->listen(); 101 | auto acpt_iop = std::dynamic_pointer_cast(listesock); 102 | acpt_evlp.fd_register_and_activate(acpt_iop, fd_event::fd_readable, 103 | acpt_writable_callback); 104 | acpt_evlp.fd_clean(acpt_iop); 105 | acpt_evlp.fd_register_and_activate(acpt_iop, fd_event::fd_readable, 106 | acpt_writable_callback); 107 | } 108 | 109 | std::thread thr_cont( 110 | [&]() 111 | { 112 | for (size_t i = 0; i < vec.size(); ++i) 113 | { 114 | auto consock = io_factory::get_socktcp(std::get<0>(vec[i])); 115 | EXPECT_TRUE( 116 | consock->connect(std::get<2>(vec[i]), std::get<1>(vec[i]))); 117 | auto conn_iop = std::dynamic_pointer_cast(consock); 118 | cont_evlp.fd_register( 119 | conn_iop, fd_event::fd_writable, 120 | [](const std::shared_ptr &iop) 121 | { 122 | LOG_INFO_FMT( 123 | "Client side connected socket %d writable event " 124 | "triggered", 125 | iop->fd()); 126 | auto dp = reinterpret_cast(iop->evlp().data()); 127 | dp ? ++(*dp) : 0; 128 | }); 129 | cont_evlp.fd_activate(conn_iop, fd_event::fd_writable); 130 | cont_evlp.fd_deactivate(conn_iop, fd_event::fd_writable); 131 | cont_evlp.fd_activate(conn_iop, fd_event::fd_writable); 132 | } 133 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 134 | cont_evlp.loop_once(); 135 | }); 136 | thr_cont.join(); 137 | 138 | std::thread thr_stop( 139 | [&]() 140 | { 141 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 142 | acpt_evlp.stop_loop(); 143 | }); 144 | 145 | acpt_evlp.loop_forever(); 146 | thr_stop.join(); 147 | 148 | EXPECT_EQ(acpt_count, vec.size()); 149 | EXPECT_EQ(cont_count, vec.size()); 150 | 151 | LOG_INFO << "server client test ended"; 152 | 153 | std::thread thr1([&]() { acpt_evlp.loop_once(); }); 154 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 155 | acpt_evlp.stop_loop(); 156 | thr1.join(); 157 | LOG_INFO << "loop once stopped"; 158 | 159 | std::thread thr2([&]() { acpt_evlp.loop_forever(); }); 160 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 161 | acpt_evlp.stop_loop(); 162 | thr2.join(); 163 | LOG_INFO << "loop forever stopped"; 164 | } 165 | 166 | TEST_P(TestEventLoop, test_tcp_connect_with_evlp_second) 167 | { 168 | cppev::logger::get_instance().set_log_level(cppev::log_level::info); 169 | 170 | const std::string path = "/tmp/unittest_cppev_tcp_unix_6C0224787A17.sock"; 171 | const int port = 8889; 172 | 173 | event_loop client_evlp; 174 | 175 | auto p = GetParam(); 176 | 177 | fd_event_handler conn_callback = [p](const std::shared_ptr &iop) 178 | { 179 | LOG_INFO << "Executing fd " << iop->fd() << " fd_writable callback"; 180 | iop->evlp().fd_set_mode(iop, p); 181 | iop->evlp().fd_deactivate(iop, fd_event::fd_writable); 182 | iop->evlp().fd_register_and_activate( 183 | std::dynamic_pointer_cast(iop), fd_event::fd_readable, 184 | [](const std::shared_ptr &iop) 185 | { 186 | LOG_INFO << "Executing fd " << iop->fd() 187 | << " fd_readable callback"; 188 | auto iopt = std::dynamic_pointer_cast(iop); 189 | iopt->read_chunk(8); 190 | if (iop->rbuffer().size() != strlen(str)) 191 | { 192 | return; 193 | } 194 | LOG_INFO << "Executing fd " << iop->fd() 195 | << " received message : " 196 | << iop->rbuffer().get_string(); 197 | }); 198 | }; 199 | 200 | auto listesock1 = io_factory::get_socktcp(family::local); 201 | listesock1->bind_unix(path, true); 202 | listesock1->listen(); 203 | 204 | auto listesock2 = io_factory::get_socktcp(family::ipv6); 205 | listesock2->bind(port); 206 | listesock2->listen(); 207 | 208 | std::thread sub_thr( 209 | [&]() 210 | { 211 | auto consock1 = io_factory::get_socktcp(family::local); 212 | bool succeed = consock1->connect_unix(path); 213 | ASSERT_TRUE(succeed); 214 | 215 | auto consock2 = io_factory::get_socktcp(family::ipv6); 216 | succeed = consock2->connect("::1", port); 217 | ASSERT_TRUE(succeed); 218 | 219 | client_evlp.fd_register_and_activate( 220 | std::dynamic_pointer_cast(consock1), fd_event::fd_writable, 221 | conn_callback); 222 | client_evlp.fd_register_and_activate( 223 | std::dynamic_pointer_cast(consock2), fd_event::fd_writable, 224 | conn_callback); 225 | client_evlp.loop_forever(); 226 | }); 227 | 228 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 229 | 230 | auto conns1 = listesock1->accept(); 231 | EXPECT_TRUE(conns1.size() == 1); 232 | auto consock1 = conns1[0]; 233 | 234 | auto conns2 = listesock2->accept(); 235 | EXPECT_TRUE(conns2.size() == 1); 236 | auto consock2 = conns2[0]; 237 | 238 | for (int i = 0; i < 2; ++i) 239 | { 240 | consock1->wbuffer().put_string(str); 241 | consock1->write_all(); 242 | consock2->wbuffer().put_string(str); 243 | consock2->write_all(); 244 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 245 | } 246 | LOG_INFO << "To leave"; 247 | 248 | client_evlp.stop_loop(); 249 | sub_thr.join(); 250 | } 251 | 252 | INSTANTIATE_TEST_SUITE_P(CppevTest, TestEventLoop, 253 | testing::Values(fd_event_mode::level_trigger, 254 | fd_event_mode::edge_trigger, 255 | fd_event_mode::oneshot)); 256 | 257 | } // namespace cppev 258 | 259 | int main(int argc, char **argv) 260 | { 261 | testing::InitGoogleTest(); 262 | return RUN_ALL_TESTS(); 263 | } 264 | -------------------------------------------------------------------------------- /src/lib/lock.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/lock.h" 2 | 3 | namespace cppev 4 | { 5 | 6 | static const std::unordered_map sync_level_map = { 7 | {sync_level::thread, PTHREAD_PROCESS_PRIVATE}, 8 | {sync_level::process, PTHREAD_PROCESS_SHARED}, 9 | }; 10 | 11 | mutex::mutex(sync_level sl) 12 | { 13 | int ret = 0; 14 | pthread_mutexattr_t attr; 15 | ret = pthread_mutexattr_init(&attr); 16 | if (ret != 0) 17 | { 18 | throw_system_error_with_specific_errno("pthread_mutexattr_init error", 19 | ret); 20 | } 21 | ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); 22 | if (ret != 0) 23 | { 24 | throw_system_error_with_specific_errno( 25 | "pthread_mutexattr_settype error", ret); 26 | } 27 | ret = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_NONE); 28 | if (ret != 0) 29 | { 30 | throw_system_error_with_specific_errno( 31 | "pthread_mutexattr_setprotocol error", ret); 32 | } 33 | ret = pthread_mutexattr_setpshared(&attr, sync_level_map.at(sl)); 34 | if (ret != 0) 35 | { 36 | throw_system_error_with_specific_errno( 37 | "pthread_mutexattr_setpshared error", ret); 38 | } 39 | ret = pthread_mutex_init(&lock_, &attr); 40 | if (ret != 0) 41 | { 42 | throw_system_error_with_specific_errno("pthread_mutex_init error", ret); 43 | } 44 | ret = pthread_mutexattr_destroy(&attr); 45 | if (ret != 0) 46 | { 47 | throw_system_error_with_specific_errno( 48 | "pthread_mutexattr_destroy error", ret); 49 | } 50 | } 51 | 52 | mutex::~mutex() noexcept 53 | { 54 | pthread_mutex_destroy(&lock_); 55 | } 56 | 57 | void mutex::lock() 58 | { 59 | int ret = pthread_mutex_lock(&lock_); 60 | if (ret != 0) 61 | { 62 | throw_system_error_with_specific_errno("pthread_mutex_lock error", ret); 63 | } 64 | } 65 | 66 | bool mutex::try_lock() 67 | { 68 | int ret = pthread_mutex_trylock(&lock_); 69 | if (ret != 0) 70 | { 71 | if (ret == EBUSY) 72 | { 73 | return false; 74 | } 75 | throw_system_error_with_specific_errno("pthread_mutex_trylock error", 76 | ret); 77 | } 78 | return true; 79 | } 80 | 81 | void mutex::unlock() 82 | { 83 | int ret = pthread_mutex_unlock(&lock_); 84 | if (ret != 0) 85 | { 86 | throw_system_error_with_specific_errno("pthread_mutex_unlock error", 87 | ret); 88 | } 89 | } 90 | 91 | cond::cond(sync_level sl) 92 | { 93 | int ret = 0; 94 | pthread_condattr_t attr; 95 | ret = pthread_condattr_init(&attr); 96 | if (ret != 0) 97 | { 98 | throw_system_error_with_specific_errno("pthread_condattr_init error", 99 | ret); 100 | } 101 | ret = pthread_condattr_setpshared(&attr, sync_level_map.at(sl)); 102 | if (ret != 0) 103 | { 104 | throw_system_error_with_specific_errno( 105 | "pthread_condattr_setpshared error", ret); 106 | } 107 | ret = pthread_cond_init(&cond_, &attr); 108 | if (ret != 0) 109 | { 110 | throw_system_error_with_specific_errno("pthread_cond_init error", ret); 111 | } 112 | ret = pthread_condattr_destroy(&attr); 113 | if (ret != 0) 114 | { 115 | throw_system_error_with_specific_errno("pthread_condattr_destroy error", 116 | ret); 117 | } 118 | } 119 | 120 | cond::~cond() noexcept 121 | { 122 | pthread_cond_destroy(&cond_); 123 | } 124 | 125 | void cond::wait(std::unique_lock &lock) 126 | { 127 | int ret = pthread_cond_wait(&cond_, &lock.mutex()->lock_); 128 | if (ret != 0) 129 | { 130 | throw_system_error_with_specific_errno("pthread_cond_wait error", ret); 131 | } 132 | } 133 | 134 | void cond::wait(std::unique_lock &lock, const predicate &pred) 135 | { 136 | while (!pred()) 137 | { 138 | wait(lock); 139 | } 140 | } 141 | 142 | void cond::notify_one() 143 | { 144 | int ret = pthread_cond_signal(&cond_); 145 | if (ret != 0) 146 | { 147 | throw_system_error_with_specific_errno("pthread_cond_signal error", 148 | ret); 149 | } 150 | } 151 | 152 | void cond::notify_all() 153 | { 154 | int ret = pthread_cond_broadcast(&cond_); 155 | if (ret != 0) 156 | { 157 | throw_system_error_with_specific_errno("pthread_cond_broadcast error", 158 | ret); 159 | } 160 | } 161 | 162 | one_time_fence::one_time_fence(sync_level sl) : ok_(false), lock_(sl), cond_(sl) 163 | { 164 | } 165 | 166 | one_time_fence::~one_time_fence() = default; 167 | 168 | void one_time_fence::wait() 169 | { 170 | if (!ok_) 171 | { 172 | std::unique_lock lock(lock_); 173 | if (!ok_) 174 | { 175 | cond_.wait(lock, [this]() { return ok_; }); 176 | } 177 | } 178 | } 179 | 180 | void one_time_fence::notify() 181 | { 182 | if (!ok_) 183 | { 184 | std::unique_lock lock(lock_); 185 | ok_ = true; 186 | cond_.notify_one(); 187 | } 188 | } 189 | 190 | barrier::barrier(sync_level sl, int count) : count_(count), lock_(sl), cond_(sl) 191 | { 192 | } 193 | 194 | barrier::~barrier() = default; 195 | 196 | void barrier::wait() 197 | { 198 | std::unique_lock lock(lock_); 199 | --count_; 200 | if (count_ == 0) 201 | { 202 | cond_.notify_all(); 203 | } 204 | else if (count_ > 0) 205 | { 206 | cond_.wait(lock, [this]() { return count_ == 0; }); 207 | } 208 | else 209 | { 210 | throw_logic_error("too many threads waiting in the barrier"); 211 | } 212 | } 213 | 214 | rwlock::rwlock(sync_level sl) 215 | { 216 | int ret = 0; 217 | pthread_rwlockattr_t attr; 218 | ret = pthread_rwlockattr_init(&attr); 219 | if (ret != 0) 220 | { 221 | throw_system_error_with_specific_errno("pthread_rwlockattr_init error", 222 | ret); 223 | } 224 | ret = pthread_rwlockattr_setpshared(&attr, sync_level_map.at(sl)); 225 | if (ret != 0) 226 | { 227 | throw_system_error_with_specific_errno( 228 | "pthread_rwlockattr_setpshared error", ret); 229 | } 230 | ret = pthread_rwlock_init(&lock_, &attr); 231 | if (ret != 0) 232 | { 233 | throw_system_error_with_specific_errno("pthread_rwlock_init error", 234 | ret); 235 | } 236 | ret = pthread_rwlockattr_destroy(&attr); 237 | if (ret != 0) 238 | { 239 | throw_system_error_with_specific_errno( 240 | "pthread_rwlockattr_destroy error", ret); 241 | } 242 | } 243 | 244 | rwlock::~rwlock() noexcept 245 | { 246 | pthread_rwlock_destroy(&lock_); 247 | } 248 | 249 | void rwlock::unlock() 250 | { 251 | int ret = pthread_rwlock_unlock(&lock_); 252 | if (ret != 0) 253 | { 254 | throw_system_error_with_specific_errno("pthread_rwlock_unlock error", 255 | ret); 256 | } 257 | } 258 | 259 | void rwlock::rdlock() 260 | { 261 | int ret = pthread_rwlock_rdlock(&lock_); 262 | if (ret != 0) 263 | { 264 | throw_system_error_with_specific_errno("pthread_rwlock_rdlock error", 265 | ret); 266 | } 267 | } 268 | 269 | void rwlock::wrlock() 270 | { 271 | int ret = pthread_rwlock_wrlock(&lock_); 272 | if (ret != 0) 273 | { 274 | throw_system_error_with_specific_errno("pthread_rwlock_wrlock error", 275 | ret); 276 | } 277 | } 278 | 279 | bool rwlock::try_rdlock() 280 | { 281 | int ret = pthread_rwlock_tryrdlock(&lock_); 282 | if (ret == 0) 283 | { 284 | return true; 285 | } 286 | else if (ret == EBUSY || ret == EAGAIN) 287 | { 288 | return false; 289 | } 290 | throw_system_error_with_specific_errno("pthread_rwlock_tryrdlock error", 291 | ret); 292 | return ret; 293 | } 294 | 295 | bool rwlock::try_wrlock() 296 | { 297 | int ret = pthread_rwlock_trywrlock(&lock_); 298 | if (ret == 0) 299 | { 300 | return true; 301 | } 302 | else if (ret == EBUSY) 303 | { 304 | return false; 305 | } 306 | throw_system_error_with_specific_errno("pthread_rwlock_trywrlock error", 307 | ret); 308 | return ret; 309 | } 310 | 311 | rdlockguard::rdlockguard(rwlock &lock) : rwlock_(&lock) 312 | { 313 | rwlock_->rdlock(); 314 | } 315 | 316 | rdlockguard::rdlockguard(rdlockguard &&other) noexcept 317 | { 318 | this->rwlock_ = other.rwlock_; 319 | other.rwlock_ = nullptr; 320 | } 321 | 322 | rdlockguard &rdlockguard::operator=(rdlockguard &&other) noexcept 323 | { 324 | this->rwlock_ = other.rwlock_; 325 | other.rwlock_ = nullptr; 326 | 327 | return *this; 328 | } 329 | 330 | rdlockguard::~rdlockguard() noexcept 331 | { 332 | if (rwlock_ != nullptr) 333 | { 334 | try 335 | { 336 | rwlock_->unlock(); 337 | } 338 | catch (...) 339 | { 340 | } 341 | } 342 | } 343 | 344 | void rdlockguard::lock() 345 | { 346 | rwlock_->rdlock(); 347 | } 348 | 349 | void rdlockguard::unlock() 350 | { 351 | rwlock_->unlock(); 352 | } 353 | 354 | wrlockguard::wrlockguard(rwlock &lock) : rwlock_(&lock) 355 | { 356 | rwlock_->wrlock(); 357 | } 358 | 359 | wrlockguard::wrlockguard(wrlockguard &&other) noexcept 360 | { 361 | this->rwlock_ = other.rwlock_; 362 | other.rwlock_ = nullptr; 363 | } 364 | 365 | wrlockguard &wrlockguard::operator=(wrlockguard &&other) noexcept 366 | { 367 | this->rwlock_ = other.rwlock_; 368 | other.rwlock_ = nullptr; 369 | 370 | return *this; 371 | } 372 | 373 | wrlockguard::~wrlockguard() noexcept 374 | { 375 | if (rwlock_ != nullptr) 376 | { 377 | try 378 | { 379 | rwlock_->unlock(); 380 | } 381 | catch (...) 382 | { 383 | } 384 | } 385 | } 386 | 387 | void wrlockguard::lock() 388 | { 389 | rwlock_->wrlock(); 390 | } 391 | 392 | void wrlockguard::unlock() 393 | { 394 | rwlock_->unlock(); 395 | } 396 | 397 | } // namespace cppev 398 | -------------------------------------------------------------------------------- /src/lib/utils.cc: -------------------------------------------------------------------------------- 1 | #include "cppev/utils.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace cppev 11 | { 12 | 13 | std::string timestamp(time_t t, const char *format) 14 | { 15 | static_assert(std::is_signed::value, "time_t is not signed!"); 16 | if (t < 0) 17 | { 18 | t = time(nullptr); 19 | if (t == -1) 20 | { 21 | throw_system_error("time error"); 22 | } 23 | } 24 | if (format == nullptr) 25 | { 26 | format = "%F %T %Z"; 27 | } 28 | tm s_tm; 29 | localtime_r(&t, &s_tm); 30 | char buf[1024]; 31 | memset(buf, 0, 1024); 32 | if (0 == strftime(buf, 1024, format, &s_tm)) 33 | { 34 | throw_system_error("strftime error"); 35 | } 36 | return buf; 37 | } 38 | 39 | int64_t least_common_multiple(int64_t p, int64_t r) 40 | { 41 | assert(p != 0 && r != 0); 42 | return (p / greatest_common_divisor(p, r)) * r; 43 | } 44 | 45 | int64_t least_common_multiple(const std::vector &nums) 46 | { 47 | assert(nums.size() >= 2); 48 | int64_t prev = nums[0]; 49 | for (size_t i = 1; i < nums.size(); ++i) 50 | { 51 | prev = least_common_multiple(prev, nums[i]); 52 | } 53 | return prev; 54 | } 55 | 56 | int64_t greatest_common_divisor(int64_t p, int64_t r) 57 | { 58 | assert(p != 0 && r != 0); 59 | int64_t remain; 60 | while (true) 61 | { 62 | remain = p % r; 63 | p = r; 64 | r = remain; 65 | if (remain == 0) 66 | { 67 | break; 68 | } 69 | } 70 | return p; 71 | } 72 | 73 | int64_t greatest_common_divisor(const std::vector &nums) 74 | { 75 | assert(nums.size() >= 2); 76 | int64_t prev = nums[0]; 77 | for (size_t i = 1; i < nums.size(); ++i) 78 | { 79 | prev = greatest_common_divisor(prev, nums[i]); 80 | } 81 | return prev; 82 | } 83 | 84 | bool CPPEV_PUBLIC exception_guard(const std::function &func) 85 | { 86 | try 87 | { 88 | func(); 89 | } 90 | catch (const std::exception &e) 91 | { 92 | return false; 93 | } 94 | return true; 95 | } 96 | 97 | void ignore_signal(int sig) 98 | { 99 | handle_signal(sig, SIG_IGN); 100 | } 101 | 102 | void reset_signal(int sig) 103 | { 104 | handle_signal(sig, SIG_DFL); 105 | } 106 | 107 | void handle_signal(int sig, sig_t handler) 108 | { 109 | struct sigaction sigact; 110 | memset(&sigact, 0, sizeof(sigact)); 111 | sigact.sa_handler = handler; 112 | if (sigaction(sig, &sigact, nullptr) == -1) 113 | { 114 | throw_system_error("sigaction error"); 115 | } 116 | } 117 | 118 | void send_signal(pid_t pid, int sig) 119 | { 120 | if (sig == 0) 121 | { 122 | throw_logic_error("pid or pgid check is not supported"); 123 | } 124 | if (kill(pid, sig) != 0) 125 | { 126 | throw_system_error("kill error"); 127 | } 128 | } 129 | 130 | bool check_process(pid_t pid) 131 | { 132 | if (kill(pid, 0) == 0) 133 | { 134 | return true; 135 | } 136 | else 137 | { 138 | if (errno == ESRCH) 139 | { 140 | return false; 141 | } 142 | throw_system_error("kill error"); 143 | } 144 | return true; 145 | } 146 | 147 | bool check_process_group(pid_t pgid) 148 | { 149 | return check_process(-1 * pgid); 150 | } 151 | 152 | void thread_raise_signal(int sig) 153 | { 154 | if (raise(sig) != 0) 155 | { 156 | throw_system_error("raise error"); 157 | } 158 | } 159 | 160 | void thread_wait_for_signal(int sig) 161 | { 162 | sigset_t set; 163 | sigemptyset(&set); 164 | sigaddset(&set, sig); 165 | 166 | // linux requires non-null 167 | int ret_sig; 168 | if (sigwait(&set, &ret_sig) != 0) 169 | { 170 | throw_system_error("sigwait error"); 171 | } 172 | } 173 | 174 | int thread_wait_for_signal(const std::vector &sigs) 175 | { 176 | sigset_t set; 177 | sigemptyset(&set); 178 | for (auto sig : sigs) 179 | { 180 | sigaddset(&set, sig); 181 | } 182 | 183 | // linux requires non-null 184 | int ret_sig; 185 | if (sigwait(&set, &ret_sig) != 0) 186 | { 187 | throw_system_error("sigwait error"); 188 | } 189 | return ret_sig; 190 | } 191 | 192 | void thread_suspend_for_signal(int sig) 193 | { 194 | sigset_t set; 195 | sigfillset(&set); 196 | sigdelset(&set, sig); 197 | // sigsuspend always returns -1 198 | sigsuspend(&set); 199 | } 200 | 201 | void thread_suspend_for_signal(const std::vector &sigs) 202 | { 203 | sigset_t set; 204 | sigfillset(&set); 205 | for (auto sig : sigs) 206 | { 207 | sigdelset(&set, sig); 208 | } 209 | // sigsuspend always returns -1 210 | sigsuspend(&set); 211 | } 212 | 213 | void thread_block_signal(int sig) 214 | { 215 | sigset_t set; 216 | sigemptyset(&set); 217 | sigaddset(&set, sig); 218 | int ret = pthread_sigmask(SIG_BLOCK, &set, nullptr); 219 | if (ret != 0) 220 | { 221 | throw_system_error("pthread_sigmask error", ret); 222 | } 223 | } 224 | 225 | void thread_block_signal(const std::vector &sigs) 226 | { 227 | sigset_t set; 228 | sigemptyset(&set); 229 | for (auto sig : sigs) 230 | { 231 | sigaddset(&set, sig); 232 | } 233 | int ret = pthread_sigmask(SIG_BLOCK, &set, nullptr); 234 | if (ret != 0) 235 | { 236 | throw_system_error("pthread_sigmask error", ret); 237 | } 238 | } 239 | 240 | void thread_unblock_signal(int sig) 241 | { 242 | sigset_t set; 243 | sigemptyset(&set); 244 | sigaddset(&set, sig); 245 | int ret = pthread_sigmask(SIG_UNBLOCK, &set, nullptr); 246 | if (ret != 0) 247 | { 248 | throw_system_error("pthread_sigmask error", ret); 249 | } 250 | } 251 | 252 | void thread_unblock_signal(const std::vector &sigs) 253 | { 254 | sigset_t set; 255 | sigemptyset(&set); 256 | for (auto sig : sigs) 257 | { 258 | sigaddset(&set, sig); 259 | } 260 | int ret = pthread_sigmask(SIG_UNBLOCK, &set, nullptr); 261 | if (ret != 0) 262 | { 263 | throw_system_error("pthread_sigmask error", ret); 264 | } 265 | } 266 | 267 | bool thread_check_signal_mask(int sig) 268 | { 269 | sigset_t set; 270 | sigemptyset(&set); 271 | int ret = pthread_sigmask(SIG_SETMASK, nullptr, &set); 272 | if (ret != 0) 273 | { 274 | throw_system_error("pthread_sigmask error", ret); 275 | } 276 | return sigismember(&set, sig) == 1; 277 | } 278 | 279 | bool thread_check_signal_pending(int sig) 280 | { 281 | sigset_t set; 282 | sigemptyset(&set); 283 | sigaddset(&set, sig); 284 | if (sigpending(&set) != 0) 285 | { 286 | throw_system_error("sigpending error"); 287 | } 288 | return sigismember(&set, sig) == 1; 289 | } 290 | 291 | std::vector split(const std::string &str, const std::string &sep) 292 | { 293 | if (sep.empty()) 294 | { 295 | throw_runtime_error("cannot split string with empty seperator"); 296 | } 297 | 298 | if (sep.size() > str.size()) 299 | { 300 | return {str}; 301 | } 302 | 303 | std::vector sep_index; 304 | 305 | sep_index.push_back(0 - sep.size()); 306 | 307 | for (size_t i = 0; i <= str.size() - sep.size();) 308 | { 309 | bool is_sep = true; 310 | for (size_t j = i; j < i + sep.size(); ++j) 311 | { 312 | if (str[j] != sep[j - i]) 313 | { 314 | is_sep = false; 315 | break; 316 | } 317 | } 318 | if (is_sep) 319 | { 320 | sep_index.push_back(static_cast(i)); 321 | i += sep.size(); 322 | } 323 | else 324 | { 325 | ++i; 326 | } 327 | } 328 | 329 | sep_index.push_back(str.size()); 330 | 331 | if (sep_index.size() == 2) 332 | { 333 | return {str}; 334 | } 335 | 336 | std::vector substrs; 337 | 338 | for (size_t i = 1; i < sep_index.size(); ++i) 339 | { 340 | int begin = sep_index[i - 1] + sep.size(); 341 | int end = sep_index[i]; 342 | 343 | substrs.push_back(std::string(str, begin, end - begin)); 344 | } 345 | return substrs; 346 | } 347 | 348 | std::string join(const std::vector &str_arr, 349 | const std::string &sep) noexcept 350 | { 351 | std::string ret = ""; 352 | size_t size = 0; 353 | for (size_t i = 0; i < str_arr.size(); ++i) 354 | { 355 | size += str_arr[i].size(); 356 | } 357 | size += sep.size() * (str_arr.size() - 1); 358 | ret.reserve(size); 359 | for (size_t i = 0; i < str_arr.size(); ++i) 360 | { 361 | ret += str_arr[i]; 362 | if (i != str_arr.size() - 1) 363 | { 364 | ret += sep; 365 | } 366 | } 367 | return ret; 368 | } 369 | 370 | static constexpr int STRIP_LEFT = 0x01; 371 | static constexpr int STRIP_RIGHT = 0x10; 372 | 373 | static std::string do_strip(const std::string &str, const std::string &chars, 374 | const int type) 375 | { 376 | if (chars.empty()) 377 | { 378 | throw_runtime_error("cannot strip string with empty strip chars"); 379 | } 380 | 381 | size_t p = 0; 382 | size_t r = str.size(); 383 | 384 | if (type & STRIP_LEFT) 385 | { 386 | while (r - p >= chars.size()) 387 | { 388 | bool eq = true; 389 | size_t j = 0; 390 | for (size_t i = p; i < p + chars.size(); ++i) 391 | { 392 | if (str[i] != chars[j++]) 393 | { 394 | eq = false; 395 | break; 396 | } 397 | } 398 | if (eq) 399 | { 400 | p += chars.size(); 401 | } 402 | else 403 | { 404 | break; 405 | } 406 | } 407 | } 408 | 409 | if (type & STRIP_RIGHT) 410 | { 411 | while (r - p >= chars.size()) 412 | { 413 | bool eq = true; 414 | size_t j = chars.size() - 1; 415 | for (size_t i = r - 1; i > r - chars.size() - 1; --i) 416 | { 417 | if (str[i] != chars[j--]) 418 | { 419 | eq = false; 420 | break; 421 | } 422 | } 423 | if (eq) 424 | { 425 | r -= chars.size(); 426 | } 427 | else 428 | { 429 | break; 430 | } 431 | } 432 | } 433 | 434 | return str.substr(p, r - p); 435 | } 436 | 437 | std::string strip(const std::string &str, const std::string &chars) 438 | { 439 | return do_strip(str, chars, STRIP_LEFT | STRIP_RIGHT); 440 | } 441 | 442 | std::string lstrip(const std::string &str, const std::string &chars) 443 | { 444 | return do_strip(str, chars, STRIP_LEFT); 445 | } 446 | 447 | std::string rstrip(const std::string &str, const std::string &chars) 448 | { 449 | return do_strip(str, chars, STRIP_RIGHT); 450 | } 451 | 452 | } // namespace cppev 453 | --------------------------------------------------------------------------------