├── resource ├── tinycoro.png ├── exp_perf_svg.png ├── benchmark_wsl2.png ├── tinycoro_arch.png ├── tinycoro_core.png ├── tinycoro_intro.jpg └── benchmark_ubuntu.png ├── scripts ├── print_list.sh ├── memcheck.sh ├── perf_svg.sh ├── analysis_valgrind.py ├── perf_svg.py ├── README.MD ├── CITests.py └── CITests.yml ├── include └── coro │ ├── detail │ ├── void_value.hpp │ ├── atomic_helper.hpp │ └── types.hpp │ ├── io │ ├── net │ │ ├── http │ │ │ └── http.hpp │ │ └── tcp │ │ │ └── tcp.hpp │ ├── base_awaiter.hpp │ ├── io_info.hpp │ ├── base_io_type.hpp │ └── io_awaiter.hpp │ ├── concepts │ ├── lock.hpp │ ├── range_of.hpp │ ├── promise.hpp │ ├── common.hpp │ ├── function_traits.hpp │ └── awaitable.hpp │ ├── coro.hpp │ ├── comp │ ├── mutex_guard.hpp │ ├── latch.hpp │ ├── wait_group.hpp │ ├── condition_variable.hpp │ ├── mutex.hpp │ └── event.hpp │ ├── atomic_que.hpp │ ├── attribute.hpp │ ├── allocator │ └── memory.hpp │ ├── meta_info.hpp │ ├── spinlock.hpp │ ├── marked_buffer.hpp │ ├── dispatcher.hpp │ ├── parallel │ └── parallel.hpp │ ├── scheduler.hpp │ ├── log.hpp │ ├── utils.hpp │ ├── context.hpp │ └── timer.hpp ├── src ├── CMakeLists.txt ├── utils.cpp ├── comp │ ├── wait_group.cpp │ ├── event.cpp │ ├── mutex.cpp │ └── condition_variable.cpp ├── scheduler.cpp ├── io │ ├── net │ │ └── tcp │ │ │ └── tcp.cpp │ └── io_awaiter.cpp ├── context.cpp └── engine.cpp ├── third_party └── CMakeLists.txt ├── benchmark ├── bench.sh ├── tinycoro_model │ ├── tinycoro_1k_bench.cpp │ ├── tinycoro_16k_bench.cpp │ └── tinycoro_128_bench.cpp ├── README.MD ├── CMakeLists.txt ├── bench_results.txt └── base_model │ ├── epoll_server_bench.cpp │ └── uring_server_bench.cpp ├── tinycoro.pc.in ├── examples ├── timer.cpp ├── stack_call.cpp ├── when_all.cpp ├── wait_group.cpp ├── mutex.cpp ├── mutex_guard.cpp ├── when_all_value.cpp ├── latch.cpp ├── event.cpp ├── latch_guard.cpp ├── cv_notify_all.cpp ├── event_guard.cpp ├── event_value.cpp ├── channel.cpp ├── parallel_calc.cpp ├── tcp_echo_client.cpp ├── tcp_echo_server.cpp ├── buffer_channel.cpp ├── CMakeLists.txt ├── cv_notify_one.cpp └── stdin_client.cpp ├── .gitignore ├── benchtests ├── example_bench.cpp ├── CMakeLists.txt ├── lab4d_bench.cpp ├── lab4b_bench.cpp ├── lab4c_bench.cpp ├── lab4a_bench.cpp ├── bench_helper.hpp └── lab5b_bench.cpp ├── cmake └── env.cmake ├── .gitmodules ├── TODO.MD ├── LICENSE ├── tests ├── lab3.cpp ├── lab4c_test.cpp ├── lab4b_test.cpp ├── CMakeLists.txt ├── lab3_test.py └── lab4a_test.cpp ├── .clang-format ├── .githooks └── pre-commit ├── .github └── workflows │ └── ci-ubuntu.yml └── CMakeLists.txt /resource/tinycoro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/tinycoro.png -------------------------------------------------------------------------------- /scripts/print_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for element in "$@" 3 | do 4 | echo "$element" 5 | done -------------------------------------------------------------------------------- /resource/exp_perf_svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/exp_perf_svg.png -------------------------------------------------------------------------------- /resource/benchmark_wsl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/benchmark_wsl2.png -------------------------------------------------------------------------------- /resource/tinycoro_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/tinycoro_arch.png -------------------------------------------------------------------------------- /resource/tinycoro_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/tinycoro_core.png -------------------------------------------------------------------------------- /resource/tinycoro_intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/tinycoro_intro.jpg -------------------------------------------------------------------------------- /resource/benchmark_ubuntu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakurs2/tinyCoro/HEAD/resource/benchmark_ubuntu.png -------------------------------------------------------------------------------- /include/coro/detail/void_value.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace coro::detail 4 | { 5 | struct void_value 6 | { 7 | }; 8 | 9 | }; // namespace coro::detail 10 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE SOURCES "*.cpp") 2 | list(APPEND TINYCORO_SOURCE_FILES ${SOURCES}) 3 | set(TINYCORO_SOURCE_FILES ${TINYCORO_SOURCE_FILES} PARENT_SCOPE) 4 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(BENCHMARK_ENABLE_TESTING NO CACHE BOOL "Disable Google Benchmark tests") 2 | 3 | add_subdirectory(spdlog) 4 | add_subdirectory(googletest) 5 | add_subdirectory(benchmark) 6 | -------------------------------------------------------------------------------- /benchmark/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$#" -ne 3 ]; then 3 | echo "Error! need two paras" 4 | echo "Usage: " 5 | exit 1 6 | fi 7 | 8 | ../third_party/rust_echo_bench/target/release/echo_bench --address "127.0.0.1:$1" --number $2 --duration 30 --length $3 -------------------------------------------------------------------------------- /tinycoro.pc.in: -------------------------------------------------------------------------------- 1 | prefix="@CMAKE_INSTALL_PREFIX@" 2 | libdir="${prefix}/lib" 3 | includedir="${prefix}/include" 4 | 5 | Name: @PROJECT_NAME@ 6 | Description: @CMAKE_PROJECT_DESCRIPTION@ 7 | Version: @PROJECT_VERSION@ 8 | Cflags: -I${includedir} 9 | Libs: -L${libdir} -l@target1@ 10 | -------------------------------------------------------------------------------- /include/coro/io/net/http/http.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO: Add http support 4 | namespace coro::io::net::http 5 | { 6 | // Avoid the introduction of symbols 7 | namespace 8 | { 9 | }; // namespace 10 | 11 | namespace detail 12 | { 13 | }; // namespace detail 14 | }; // namespace coro::io::net::http 15 | -------------------------------------------------------------------------------- /include/coro/concepts/lock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace coro::concepts 6 | { 7 | template 8 | concept lockype = requires(T mtx) { 9 | { mtx.lock() } -> std::same_as; 10 | { mtx.unlock() } -> std::same_as; 11 | }; 12 | }; // namespace coro::concepts -------------------------------------------------------------------------------- /scripts/memcheck.sh: -------------------------------------------------------------------------------- 1 | if [ ! -d "$3" ]; then 2 | mkdir -p "$3" 3 | if [ $? -ne 0 ]; then 4 | echo "create directory $3 failed" 5 | exit 1 6 | fi 7 | fi 8 | 9 | valgrind --tool=memcheck --xml=yes --xml-file=$1 --gen-suppressions=all --leak-check=full --leak-resolution=med --track-origins=yes --vgdb=no $2 -------------------------------------------------------------------------------- /include/coro/detail/atomic_helper.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.h" 4 | 5 | namespace coro::detail 6 | { 7 | /** 8 | * @brief std::vector> will cause compile error, so use atomic_ref_wrapper 9 | * 10 | * @tparam T 11 | */ 12 | template 13 | struct alignas(config::kCacheLineSize) atomic_ref_wrapper 14 | { 15 | alignas(std::atomic_ref::required_alignment) T val; 16 | }; 17 | 18 | }; // namespace coro::detail -------------------------------------------------------------------------------- /include/coro/coro.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coro/comp/channel.hpp" 4 | #include "coro/comp/condition_variable.hpp" 5 | #include "coro/comp/latch.hpp" 6 | #include "coro/comp/mutex.hpp" 7 | #include "coro/comp/wait_group.hpp" 8 | #include "coro/comp/when_all.hpp" 9 | #include "coro/io/net/tcp/tcp.hpp" 10 | #include "coro/log.hpp" 11 | #include "coro/parallel/parallel.hpp" 12 | #include "coro/scheduler.hpp" 13 | #include "coro/timer.hpp" 14 | #include "coro/utils.hpp" -------------------------------------------------------------------------------- /scripts/perf_svg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$#" -ne 2 ]; then 3 | echo "Error! need two paras" 4 | echo "Usage: " 5 | exit 1 6 | fi 7 | 8 | perf record --freq=997 --call-graph dwarf -q -o $2/temp/perf.data $1 9 | perf script -i $2/temp/perf.data &> $2/temp/perf.unfold 10 | $2/third_party/FlameGraph/stackcollapse-perf.pl $2/temp/perf.unfold &> $2/temp/perf.folded 11 | $2/third_party/FlameGraph/flamegraph.pl $2/temp/perf.folded > $2/temp/perf.svg -------------------------------------------------------------------------------- /examples/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | using coro::time::timer; 6 | 7 | task<> timer_func() 8 | { 9 | log::info("timer begin"); 10 | auto result = co_await timer{}.add_seconds(3).add_mseconds(500); 11 | log::info("timer end, result: {}", result); 12 | } 13 | 14 | int main(int argc, char const* argv[]) 15 | { 16 | /* code */ 17 | scheduler::init(); 18 | submit_to_scheduler(timer_func()); 19 | scheduler::loop(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /examples/stack_call.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | task add(int x, int y) 6 | { 7 | co_return x + y; 8 | } 9 | 10 | task<> func(int x, int y) 11 | { 12 | auto result = co_await add(x, y); 13 | log::info("func result: {}", result); 14 | co_return; 15 | } 16 | 17 | int main(int argc, char const* argv[]) 18 | { 19 | /* code */ 20 | scheduler::init(); 21 | 22 | submit_to_scheduler(func(3, 4)); 23 | 24 | scheduler::loop(); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | .vscode 34 | .idea 35 | logs 36 | build 37 | build_debug 38 | build_release 39 | Testing 40 | temp 41 | config/config.h 42 | examples/coro_debug.cpp 43 | .cache -------------------------------------------------------------------------------- /include/coro/detail/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace coro::detail 6 | { 7 | 8 | // TODO: Add lifo and fifo strategy support 9 | enum class schedule_strategy : uint8_t 10 | { 11 | fifo, // default 12 | lifo, 13 | none 14 | }; 15 | 16 | enum class dispatch_strategy : uint8_t 17 | { 18 | round_robin, 19 | none 20 | }; 21 | 22 | // TODO: Add awaiter base support 23 | using awaiter_ptr = void*; 24 | 25 | enum class memory_allocator : uint8_t 26 | { 27 | std_allocator, 28 | none 29 | }; 30 | 31 | }; // namespace coro::detail -------------------------------------------------------------------------------- /examples/when_all.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | task<> sub_func(int i) 6 | { 7 | log::info("sub func {} running...", i); 8 | co_return; 9 | } 10 | 11 | task<> func(int i) 12 | { 13 | log::info("ready to wait all sub task"); 14 | co_await when_all(sub_func(i + 1), sub_func(i + 2), sub_func(i + 3)); 15 | log::info("wait all sub task finish"); 16 | co_return; 17 | } 18 | 19 | int main(int argc, char const* argv[]) 20 | { 21 | /* code */ 22 | scheduler::init(); 23 | 24 | submit_to_scheduler(func(0)); 25 | 26 | scheduler::loop(); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /include/coro/concepts/range_of.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace coro::concepts 7 | { 8 | /** 9 | * Concept to require that the range contains a specific type of value. 10 | */ 11 | template 12 | concept range_of = std::ranges::range && std::is_same_v>; 13 | 14 | /** 15 | * Concept to require that a sized range contains a specific type of value. 16 | */ 17 | template 18 | concept sized_range_of = std::ranges::sized_range && std::is_same_v>; 19 | 20 | }; // namespace coro::concepts -------------------------------------------------------------------------------- /benchtests/example_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "benchmark/benchmark.h" 4 | 5 | static void BM_StringCreation(benchmark::State& state) 6 | { 7 | for (auto _ : state) 8 | std::string empty_string; 9 | } 10 | // Register the function as a benchmark 11 | BENCHMARK(BM_StringCreation)->MeasureProcessCPUTime()->UseRealTime(); 12 | 13 | // Define another benchmark 14 | static void BM_StringCopy(benchmark::State& state) 15 | { 16 | std::string x = "hello"; 17 | for (auto _ : state) 18 | std::string copy(x); 19 | } 20 | BENCHMARK(BM_StringCopy)->MeasureProcessCPUTime()->UseRealTime(); 21 | 22 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /examples/wait_group.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | wait_group wg{0}; 6 | 7 | task<> wait() 8 | { 9 | co_await wg.wait(); 10 | log::info("wait finish"); 11 | } 12 | 13 | task<> done() 14 | { 15 | utils::sleep(2); 16 | wg.done(); 17 | log::info("done..."); 18 | co_return; 19 | } 20 | 21 | int main(int argc, char const* argv[]) 22 | { 23 | /* code */ 24 | scheduler::init(); 25 | wg.add(3); 26 | 27 | submit_to_scheduler(wait()); 28 | submit_to_scheduler(done()); 29 | submit_to_scheduler(done()); 30 | submit_to_scheduler(done()); 31 | 32 | scheduler::loop(); 33 | 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /examples/mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 100 6 | 7 | mutex mtx; 8 | int data = 0; 9 | 10 | task<> add_task(int i) 11 | { 12 | co_await mtx.lock(); 13 | log::info("task {} fetch lock", i); 14 | for (int i = 0; i < 10000; i++) 15 | { 16 | data += 1; 17 | } 18 | mtx.unlock(); 19 | co_return; 20 | } 21 | 22 | int main(int argc, char const* argv[]) 23 | { 24 | /* code */ 25 | scheduler::init(); 26 | 27 | for (int i = 0; i < TASK_NUM; i++) 28 | { 29 | submit_to_scheduler(add_task(i)); 30 | } 31 | 32 | scheduler::loop(); 33 | assert(data == 1000000); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /examples/mutex_guard.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 100 6 | 7 | mutex mtx; 8 | int data = 0; 9 | 10 | task<> add_task(int i) 11 | { 12 | auto guard = co_await mtx.lock_guard(); 13 | log::info("task {} fetch lock", i); 14 | for (int i = 0; i < 10000; i++) 15 | { 16 | data += 1; 17 | } 18 | co_return; 19 | } 20 | 21 | int main(int argc, char const* argv[]) 22 | { 23 | /* code */ 24 | scheduler::init(); 25 | 26 | for (int i = 0; i < TASK_NUM; i++) 27 | { 28 | submit_to_scheduler(add_task(i)); 29 | } 30 | 31 | scheduler::loop(); 32 | assert(data == 1000000); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /include/coro/comp/mutex_guard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coro/concepts/lock.hpp" 4 | 5 | namespace coro::detail 6 | { 7 | 8 | // FIXME: Why locktype report error? 9 | // template 10 | template 11 | class lock_guard 12 | { 13 | public: 14 | explicit lock_guard(T& mtx) noexcept : m_mtx(mtx) {} 15 | ~lock_guard() noexcept { m_mtx.unlock(); } 16 | 17 | lock_guard(lock_guard&& other) noexcept : m_mtx(other.m_mtx) {} 18 | 19 | lock_guard(const lock_guard&) = delete; 20 | lock_guard& operator=(const lock_guard&) = delete; 21 | lock_guard& operator=(lock_guard&&) = delete; 22 | 23 | private: 24 | T& m_mtx; 25 | }; 26 | 27 | }; // namespace coro::detail -------------------------------------------------------------------------------- /include/coro/atomic_que.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.h" 4 | 5 | #include "atomic_queue/atomic_queue.h" 6 | 7 | namespace coro::detail 8 | { 9 | // lib AtomicQueue 10 | template 11 | struct CapacityToConstructor : Queue 12 | { 13 | CapacityToConstructor() : Queue(Capacity) {} 14 | }; 15 | 16 | /** 17 | * @brief mpmc atomic_queue 18 | * 19 | * @tparam T storage_type 20 | * 21 | * @note true, false, false: MAXIMIZE_THROUGHPUT = true, TOTAL_ORDER = false, bool SPSC = false 22 | */ 23 | template 24 | using AtomicQueue = 25 | CapacityToConstructor, true, false, false>, config::kQueCap>; 26 | }; // namespace coro::detail -------------------------------------------------------------------------------- /examples/when_all_value.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | task sub_func(int i) 6 | { 7 | log::info("sub func {} running...", i); 8 | co_return i; 9 | } 10 | 11 | task<> func(int i) 12 | { 13 | log::info("ready to wait all sub task"); 14 | auto data = co_await when_all(sub_func(i + 1), sub_func(i + 2), sub_func(i + 3)); 15 | for (auto& it : data) 16 | { 17 | log::info("receive data: {}", it); 18 | } 19 | log::info("wait all sub task finish"); 20 | co_return; 21 | } 22 | 23 | int main(int argc, char const* argv[]) 24 | { 25 | /* code */ 26 | scheduler::init(); 27 | 28 | submit_to_scheduler(func(0)); 29 | 30 | scheduler::loop(); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /cmake/env.cmake: -------------------------------------------------------------------------------- 1 | function(check_python_command) 2 | set(available_command "python" "python3") 3 | 4 | foreach(py_command IN LISTS available_command) 5 | set(python_temp_command ${py_command}) 6 | execute_process(COMMAND ${python_temp_command} -c "import sys; sys.exit(0 if sys.version_info >= (3, 7) else 1)" 7 | RESULT_VARIABLE PYTHON_COMMAND_CHECK) 8 | if(PYTHON_COMMAND_CHECK STREQUAL "0") 9 | set(PYTHON_COMMAND_AVAILABLE TRUE PARENT_SCOPE) 10 | set(python_command ${python_temp_command} PARENT_SCOPE) 11 | return() 12 | else() 13 | set(PYTHON_COMMAND_AVAILABLE FALSE PARENT_SCOPE) 14 | endif() 15 | endforeach() 16 | set(PYTHON_COMMAND_AVAILABLE FALSE PARENT_SCOPE) 17 | endfunction() -------------------------------------------------------------------------------- /examples/latch.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 5 6 | 7 | latch l(TASK_NUM - 1); 8 | 9 | task<> set_task(int i) 10 | { 11 | log::info("set task ready to sleep"); 12 | utils::sleep(2); 13 | l.count_down(); 14 | co_return; 15 | } 16 | 17 | task<> wait_task(int i) 18 | { 19 | co_await l.wait(); 20 | log::info("task {} wake up by latch", i); 21 | co_return; 22 | } 23 | 24 | int main(int argc, char const* argv[]) 25 | { 26 | /* code */ 27 | scheduler::init(); 28 | 29 | submit_to_scheduler(wait_task(TASK_NUM - 1)); 30 | for (int i = 0; i < TASK_NUM - 1; i++) 31 | { 32 | submit_to_scheduler(set_task(i)); 33 | } 34 | 35 | scheduler::loop(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "coro/utils.hpp" 5 | 6 | namespace coro::utils 7 | { 8 | auto set_fd_noblock(int fd) noexcept -> void 9 | { 10 | int flags = fcntl(fd, F_GETFL, 0); 11 | assert(flags >= 0); 12 | 13 | flags |= O_NONBLOCK; 14 | assert(fcntl(fd, F_SETFL, flags) >= 0); 15 | } 16 | 17 | auto get_null_fd() noexcept -> int 18 | { 19 | auto fd = open("/dev/null", O_RDWR); 20 | return fd; 21 | } 22 | 23 | auto trim(std::string& s, const char* to_trim) noexcept -> std::string& 24 | { 25 | if (s.empty()) 26 | { 27 | return s; 28 | } 29 | 30 | s.erase(0, s.find_first_not_of(to_trim)); 31 | s.erase(s.find_last_not_of(to_trim) + 1); 32 | return s; 33 | } 34 | }; // namespace coro::utils -------------------------------------------------------------------------------- /examples/event.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 5 6 | 7 | event<> ev; 8 | 9 | task<> set_task() 10 | { 11 | log::info("set task ready to sleep"); 12 | utils::sleep(3); 13 | log::info("ready to set event"); 14 | ev.set(); 15 | co_return; 16 | } 17 | 18 | task<> wait_task(int i) 19 | { 20 | co_await ev.wait(); 21 | log::info("task {} wake up by event", i); 22 | co_return; 23 | } 24 | 25 | int main(int argc, char const* argv[]) 26 | { 27 | /* code */ 28 | scheduler::init(); 29 | 30 | for (int i = 0; i < TASK_NUM - 1; i++) 31 | { 32 | submit_to_scheduler(wait_task(i)); 33 | } 34 | submit_to_scheduler(set_task()); 35 | 36 | scheduler::loop(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/latch_guard.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 5 6 | 7 | latch l(TASK_NUM - 1); 8 | 9 | task<> set_task(int i) 10 | { 11 | latch_guard guard(l); 12 | log::info("set task ready to sleep"); 13 | utils::sleep(2); 14 | co_return; 15 | } 16 | 17 | task<> wait_task(int i) 18 | { 19 | co_await l.wait(); 20 | log::info("task {} wake up by latch", i); 21 | co_return; 22 | } 23 | 24 | int main(int argc, char const* argv[]) 25 | { 26 | /* code */ 27 | scheduler::init(); 28 | 29 | submit_to_scheduler(wait_task(TASK_NUM - 1)); 30 | for (int i = 0; i < TASK_NUM - 1; i++) 31 | { 32 | submit_to_scheduler(set_task(i)); 33 | } 34 | 35 | scheduler::loop(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /scripts/analysis_valgrind.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import xml.etree.ElementTree as ET 3 | 4 | RED = "\033[31m" 5 | GREEN = "\033[32m" 6 | RESET = "\033[0m" 7 | 8 | if __name__ == "__main__": 9 | try: 10 | tree = ET.parse(sys.argv[1]) 11 | root = tree.getroot() 12 | for element in root: 13 | if element.tag == "error": 14 | print( 15 | f"{RED}[ERROR] exist memleak, you can see the detail report path in {sys.argv[1]}{RESET}" 16 | ) 17 | sys.exit(1) 18 | print(f"{GREEN}[PASS] pass memcheck test{RESET}") 19 | except Exception as e: 20 | print( 21 | f"{RED}[EXCEPTION] exception occur when parse valgrind output, detail: {e}{RESET}" 22 | ) 23 | sys.exit(1) 24 | -------------------------------------------------------------------------------- /examples/cv_notify_all.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 100 6 | 7 | cond_var cv; 8 | mutex mtx; 9 | uint64_t data = 0; 10 | int id = 0; 11 | 12 | task<> run(int i) 13 | { 14 | auto lock = co_await mtx.lock_guard(); 15 | co_await cv.wait(mtx, [&]() { return i == id; }); 16 | for (int i = 0; i < 100000; i++) 17 | { 18 | data++; 19 | } 20 | log::info("task {} run finish, result: {}", i, data); 21 | id++; 22 | cv.notify_all(); 23 | } 24 | 25 | int main(int argc, char const* argv[]) 26 | { 27 | /* code */ 28 | scheduler::init(); 29 | 30 | for (int i = 0; i < TASK_NUM; i++) 31 | { 32 | submit_to_scheduler(run(i)); 33 | } 34 | 35 | scheduler::loop(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/event_guard.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 5 6 | 7 | event<> ev; 8 | 9 | task<> set_task() 10 | { 11 | event_guard guard(ev); 12 | log::info("set task ready to sleep"); 13 | utils::sleep(3); 14 | log::info("ready to set event"); 15 | co_return; 16 | } 17 | 18 | task<> wait_task(int i) 19 | { 20 | co_await ev.wait(); 21 | log::info("task {} wake up by event", i); 22 | co_return; 23 | } 24 | 25 | int main(int argc, char const* argv[]) 26 | { 27 | /* code */ 28 | scheduler::init(); 29 | 30 | for (int i = 0; i < TASK_NUM - 1; i++) 31 | { 32 | submit_to_scheduler(wait_task(i)); 33 | } 34 | submit_to_scheduler(set_task()); 35 | 36 | scheduler::loop(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /include/coro/concepts/promise.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coro/concepts/awaitable.hpp" 4 | 5 | #include 6 | 7 | namespace coro::concepts 8 | { 9 | // clang-format off 10 | template 11 | concept promise = requires(type t) 12 | { 13 | { t.get_return_object() } -> std::convertible_to>; 14 | { t.initial_suspend() } -> awaiter; 15 | { t.final_suspend() } -> awaiter; 16 | { t.yield_value() } -> awaitable; 17 | } 18 | && requires(type t, return_type return_value) 19 | { 20 | requires std::same_as || 21 | std::same_as || 22 | requires { t.yield_value(return_value); }; 23 | }; 24 | // clang-format on 25 | 26 | } // namespace coro::concepts 27 | -------------------------------------------------------------------------------- /examples/event_value.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define TASK_NUM 5 6 | 7 | event ev; 8 | 9 | task<> set_task(int i) 10 | { 11 | log::info("set task ready to sleep"); 12 | utils::sleep(3); 13 | log::info("ready to set event"); 14 | ev.set(i); 15 | co_return; 16 | } 17 | 18 | task<> wait_task(int i) 19 | { 20 | auto val = co_await ev.wait(); 21 | log::info("task {} wake up by event, get value: {}", i, val); 22 | co_return; 23 | } 24 | 25 | int main(int argc, char const* argv[]) 26 | { 27 | /* code */ 28 | scheduler::init(); 29 | 30 | for (int i = 0; i < TASK_NUM - 1; i++) 31 | { 32 | submit_to_scheduler(wait_task(i)); 33 | } 34 | submit_to_scheduler(set_task(TASK_NUM - 1)); 35 | 36 | scheduler::loop(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/channel.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | channel ch; 6 | 7 | task<> producer(int i) 8 | { 9 | for (int i = 0; i < 10; i++) 10 | { 11 | co_await ch.send(i); 12 | } 13 | 14 | ch.close(); 15 | co_return; 16 | } 17 | 18 | task<> consumer(int i) 19 | { 20 | while (true) 21 | { 22 | auto data = co_await ch.recv(); 23 | if (data) 24 | { 25 | log::info("consumer {} receive data: {}", i, *data); 26 | } 27 | else 28 | { 29 | break; 30 | } 31 | } 32 | } 33 | 34 | int main(int argc, char const* argv[]) 35 | { 36 | /* code */ 37 | scheduler::init(); 38 | 39 | submit_to_scheduler(producer(0)); 40 | submit_to_scheduler(consumer(1)); 41 | 42 | scheduler::loop(); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/spdlog"] 2 | path = third_party/spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | [submodule "third_party/googletest"] 5 | path = third_party/googletest 6 | url = https://github.com/google/googletest.git 7 | [submodule "third_party/atomic_queue"] 8 | path = third_party/atomic_queue 9 | url = https://github.com/max0x7ba/atomic_queue.git 10 | [submodule "third_party/liburing"] 11 | path = third_party/liburing 12 | url = https://github.com/axboe/liburing.git 13 | [submodule "third_party/benchmark"] 14 | path = third_party/benchmark 15 | url = https://github.com/google/benchmark.git 16 | [submodule "third_party/rust_echo_bench"] 17 | path = third_party/rust_echo_bench 18 | url = https://github.com/haraldh/rust_echo_bench.git 19 | [submodule "third_party/FlameGraph"] 20 | path = third_party/FlameGraph 21 | url = https://github.com/brendangregg/FlameGraph.git 22 | -------------------------------------------------------------------------------- /TODO.MD: -------------------------------------------------------------------------------- 1 | # Todo list 2 | 3 | ### 正在解决中 4 | 5 | - [ ] 为协程同步组件中susend awaiter的恢复提供跨context且引用计数安全的能力 6 | - [ ] 修复内存invalid read的问题,该问题导致memtest不通过 7 | - [ ] 解决io_uring sqe消费过快问题 8 | - [ ] 添加io_uring链式调用的功能 9 | - [ ] 为uring添加polling模式 10 | - [ ] 添加clang-tidy,clang-lint等代码检查功能 11 | - [ ] 添加git提交模板 12 | 13 | ### 已完成 14 | 15 | - [x] 修复高QPS下部分响应失败 16 | - [x] 添加perf火焰图生成脚本 17 | - [x] 更改所有同步组件register和resume的方式 18 | - [x] 添加负载均衡模块使得任务支持跨线程 19 | - [x] 解决context stop过慢的问题 20 | - [x] 配置文件重新设计 21 | - [x] 更改context的stop为notify_stop,并修改测试用例 22 | - [x] 测试数值改为配置项 23 | - [x] 添加版本号信息 24 | - [x] 为benchtests添加threadpool版本 25 | - [x] 去除运行模式概念,scheduler::loop不需要参数 26 | 27 | ### 未来待完成 28 | 29 | - [ ] 添加更细致的注释 30 | - [ ] 拓展新功能 31 | - [ ] 重构优化 32 | - [ ] 添加任务取消机制 33 | - [ ] 解决项目内标记的TODO 34 | - [ ] 为awaiter添加基类 35 | - [ ] 动态链接库接口可见性 36 | - [ ] 添加co_yield功能 37 | - [ ] perf火焰图性能优化 38 | - [ ] 为cmake添加各项环境检查 39 | - [ ] README里添加更新日志 40 | - [ ] 添加更详细的注释 41 | - [ ] 测试clang编译器 42 | -------------------------------------------------------------------------------- /examples/parallel_calc.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | task square(int i) 6 | { 7 | co_return i* i; 8 | } 9 | 10 | task<> parallel_compute() 11 | { 12 | std::vector> vec; 13 | for (int i = 1; i <= 5; i++) 14 | { 15 | vec.push_back(square(i)); 16 | } 17 | auto answer = co_await parallel::parallel_func( 18 | vec, 19 | parallel::make_parallel_reduce_func( 20 | [](std::vector& vec) 21 | { 22 | int sum = 0; 23 | for (auto it : vec) 24 | { 25 | sum += it; 26 | } 27 | return sum; 28 | })); 29 | log::info("final answer: {}", answer); 30 | } 31 | 32 | int main(int argc, char const* argv[]) 33 | { 34 | /* code */ 35 | scheduler::init(); 36 | submit_to_scheduler(parallel_compute()); 37 | scheduler::loop(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /include/coro/io/base_awaiter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "coro/context.hpp" 6 | #include "coro/io/io_info.hpp" 7 | #include "coro/uring_proxy.hpp" 8 | 9 | namespace coro::io::detail 10 | { 11 | 12 | class base_io_awaiter 13 | { 14 | public: 15 | base_io_awaiter() noexcept : m_urs(coro::detail::local_engine().get_free_urs()) 16 | { 17 | // TODO: you should no-block wait when get_free_urs return nullptr, 18 | // this means io submit rate is too high. 19 | assert(m_urs != nullptr && "io submit rate is too high"); 20 | } 21 | 22 | constexpr auto await_ready() noexcept -> bool { return false; } 23 | 24 | auto await_suspend(std::coroutine_handle<> handle) noexcept -> void { m_info.handle = handle; } 25 | 26 | auto await_resume() noexcept -> int32_t { return m_info.result; } 27 | 28 | protected: 29 | io_info m_info; 30 | coro::uring::ursptr m_urs; 31 | }; 32 | 33 | }; // namespace coro::io::detail -------------------------------------------------------------------------------- /scripts/perf_svg.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | RED = "\033[31m" 6 | GREEN = "\033[32m" 7 | RESET = "\033[0m" 8 | 9 | if __name__ == "__main__": 10 | if len(sys.argv) != 2: 11 | print("usage: ") 12 | sys.exit(1) 13 | try: 14 | tinycoro_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 15 | process = subprocess.run( 16 | [f"{tinycoro_path}/scripts/perf_svg.sh", sys.argv[1], tinycoro_path], 17 | capture_output=True, 18 | text=True, 19 | ) 20 | if process.returncode != 0: 21 | print(f"{RED}{tinycoro_path}/scripts/perf_svg.sh run error!{RESET}") 22 | print(f"STDOUT:", process.stdout) 23 | print(f"STDERR:", process.stderr) 24 | sys.exit(0) 25 | print(f"perf run success! output svg path: {tinycoro_path}/temp/perf.svg") 26 | except Exception as e: 27 | print(f"{RED} exception occur! detail: {e}{RESET}") 28 | sys.exit(1) 29 | -------------------------------------------------------------------------------- /examples/tcp_echo_client.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define BUFFLEN 10240 6 | 7 | task<> client(const char* addr, int port) 8 | { 9 | log::info("client ready to start"); 10 | auto client = io::net::tcp::tcp_client(addr, port); 11 | int ret = 0; 12 | int sockfd = 0; 13 | sockfd = co_await client.connect(); 14 | assert(sockfd > 0 && "connect error"); 15 | log::info("connect success"); 16 | 17 | char buf[BUFFLEN] = {0}; 18 | auto conn = io::net::tcp::tcp_connector(sockfd); 19 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 20 | { 21 | log::info("client {} receive data: {}", sockfd, buf); 22 | ret = co_await conn.write(buf, ret); 23 | } 24 | 25 | ret = co_await conn.close(); 26 | log::info("client close"); 27 | assert(ret == 0); 28 | } 29 | 30 | int main(int argc, char const* argv[]) 31 | { 32 | /* code */ 33 | scheduler::init(); 34 | scheduler::submit(client("localhost", 8000)); 35 | scheduler::loop(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /include/coro/io/io_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace coro::io::detail 8 | { 9 | #define CASTPTR(data) reinterpret_cast(data) 10 | #define CASTDATA(data) static_cast(data) 11 | 12 | struct io_info; 13 | 14 | using std::coroutine_handle; 15 | using cb_type = std::function; 16 | 17 | enum io_type 18 | { 19 | nop, 20 | tcp_accept, 21 | tcp_connect, 22 | tcp_read, 23 | tcp_write, 24 | tcp_close, 25 | stdin, 26 | timer, 27 | none 28 | }; 29 | 30 | struct io_info 31 | { 32 | coroutine_handle<> handle; 33 | int32_t result; 34 | io_type type; 35 | uintptr_t data; 36 | cb_type cb; 37 | }; 38 | 39 | inline uintptr_t ioinfo_to_ptr(io_info* info) noexcept 40 | { 41 | return reinterpret_cast(info); 42 | } 43 | 44 | inline io_info* ptr_to_ioinfo(uintptr_t ptr) noexcept 45 | { 46 | return reinterpret_cast(ptr); 47 | } 48 | 49 | }; // namespace coro::io::detail -------------------------------------------------------------------------------- /include/coro/concepts/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coro::concepts 9 | { 10 | template 11 | concept in_types = (std::same_as || ...); 12 | 13 | template 14 | concept list_type = requires(type t) { 15 | { t.next() } -> std::same_as; 16 | }; 17 | 18 | template 19 | concept pod_type = std::is_standard_layout_v && std::is_trivial_v; 20 | 21 | template 22 | concept void_pod_type = pod_type || std::is_void_v; 23 | 24 | template 25 | concept all_same_type = (std::same_as || ...); 26 | 27 | template 28 | concept all_void_type = (std::is_void_v || ...); 29 | 30 | template 31 | concept all_noref_pod = (pod_type || ...); 32 | 33 | template 34 | concept conventional_type = !std::is_void_v && !std::is_reference_v && !std::is_const_v; 35 | 36 | }; // namespace coro::concepts -------------------------------------------------------------------------------- /benchmark/tinycoro_model/tinycoro_1k_bench.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define BUFFLEN 1024 + 16 6 | 7 | task<> session(int fd) 8 | { 9 | char buf[BUFFLEN] = {0}; 10 | auto conn = io::net::tcp::tcp_connector(fd); 11 | int ret = 0; 12 | 13 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 14 | { 15 | ret = co_await conn.write(buf, ret); 16 | if (ret <= 0) 17 | { 18 | break; 19 | } 20 | } 21 | 22 | ret = co_await conn.close(); 23 | assert(ret == 0); 24 | } 25 | 26 | task<> server(int port) 27 | { 28 | auto server = io::net::tcp::tcp_server(port); 29 | log::info("server start in {}", port); 30 | int client_fd; 31 | while ((client_fd = co_await server.accept()) > 0) 32 | { 33 | submit_to_scheduler(session(client_fd)); 34 | } 35 | } 36 | 37 | int main(int argc, char const* argv[]) 38 | { 39 | /* code */ 40 | scheduler::init(); 41 | 42 | submit_to_scheduler(server(8000)); 43 | scheduler::loop(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /benchmark/tinycoro_model/tinycoro_16k_bench.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define BUFFLEN (16 * 1024 + 16) 6 | 7 | task<> session(int fd) 8 | { 9 | char buf[BUFFLEN] = {0}; 10 | auto conn = io::net::tcp::tcp_connector(fd); 11 | int ret = 0; 12 | 13 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 14 | { 15 | ret = co_await conn.write(buf, ret); 16 | if (ret <= 0) 17 | { 18 | break; 19 | } 20 | } 21 | 22 | ret = co_await conn.close(); 23 | assert(ret == 0); 24 | } 25 | 26 | task<> server(int port) 27 | { 28 | auto server = io::net::tcp::tcp_server(port); 29 | log::info("server start in {}", port); 30 | int client_fd; 31 | while ((client_fd = co_await server.accept()) > 0) 32 | { 33 | submit_to_scheduler(session(client_fd)); 34 | } 35 | } 36 | 37 | int main(int argc, char const* argv[]) 38 | { 39 | /* code */ 40 | scheduler::init(); 41 | 42 | submit_to_scheduler(server(8000)); 43 | scheduler::loop(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 JiahuiWang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/tcp_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define BUFFLEN 10240 6 | 7 | task<> session(int fd) 8 | { 9 | char buf[BUFFLEN] = {0}; 10 | auto conn = io::net::tcp::tcp_connector(fd); 11 | int ret = 0; 12 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 13 | { 14 | log::info("client {} receive data: {}", fd, buf); 15 | ret = co_await conn.write(buf, ret); 16 | } 17 | 18 | ret = co_await conn.close(); 19 | log::info("client {} close connect", fd); 20 | assert(ret == 0); 21 | } 22 | 23 | task<> server(int port) 24 | { 25 | log::info("server start in {}", port); 26 | auto server = io::net::tcp::tcp_server(port); 27 | int client_fd; 28 | while ((client_fd = co_await server.accept()) > 0) 29 | { 30 | log::info("server receive new connect"); 31 | submit_to_scheduler(session(client_fd)); 32 | } 33 | } 34 | 35 | int main(int argc, char const* argv[]) 36 | { 37 | /* code */ 38 | scheduler::init(); 39 | 40 | submit_to_scheduler(server(8000)); 41 | scheduler::loop(); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /benchmark/tinycoro_model/tinycoro_128_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "coro/coro.hpp" 4 | 5 | using namespace coro; 6 | 7 | #define BUFFLEN 128 + 16 8 | 9 | task<> session(int fd) 10 | { 11 | char buf[BUFFLEN] = {0}; 12 | auto conn = io::net::tcp::tcp_connector(fd); 13 | int ret = 0; 14 | 15 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 16 | { 17 | ret = co_await conn.write(buf, ret); 18 | if (ret <= 0) 19 | { 20 | break; 21 | } 22 | } 23 | ret = co_await conn.close(); 24 | assert(ret == 0); 25 | } 26 | 27 | task<> server(int port) 28 | { 29 | auto server = io::net::tcp::tcp_server(port); 30 | log::info("server start in {}", port); 31 | int client_fd; 32 | while ((client_fd = co_await server.accept()) > 0) 33 | { 34 | submit_to_scheduler(session(client_fd)); 35 | } 36 | log::info("server stop in {}", port); 37 | } 38 | 39 | int main(int argc, char const* argv[]) 40 | { 41 | /* code */ 42 | scheduler::init(); 43 | 44 | submit_to_scheduler(server(8000)); 45 | scheduler::loop(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /examples/buffer_channel.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | channel ch; 6 | std::atomic number; 7 | 8 | task<> producer(int id) 9 | { 10 | for (int i = 0; i < 4; i++) 11 | { 12 | co_await ch.send(id * 10 + i); 13 | log::info("producer {} send once", id); 14 | } 15 | 16 | if (number.fetch_sub(1, std::memory_order_acq_rel) == 1) 17 | { 18 | ch.close(); 19 | } 20 | co_return; 21 | } 22 | 23 | task<> consumer(int id) 24 | { 25 | while (true) 26 | { 27 | auto data = co_await ch.recv(); 28 | if (data) 29 | { 30 | log::info("consumer {} receive data: {}", id, *data); 31 | } 32 | else 33 | { 34 | log::info("consumer {} receive close", id); 35 | break; 36 | } 37 | } 38 | } 39 | 40 | int main(int argc, char const* argv[]) 41 | { 42 | /* code */ 43 | scheduler::init(); 44 | 45 | number = 2; 46 | submit_to_scheduler(producer(0)); 47 | submit_to_scheduler(producer(1)); 48 | submit_to_scheduler(consumer(2)); 49 | submit_to_scheduler(consumer(3)); 50 | 51 | scheduler::loop(); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /scripts/README.MD: -------------------------------------------------------------------------------- 1 | # 性能分析profile教程 2 | 3 | tinycoro预置了使用perf一键生成火焰图的脚本`perf_svg.py`,本节教大家如何使用。 4 | 5 | ### 安装perf 6 | 7 | 对于linux系统,一般直接运行下列指令即可安装perf 8 | 9 | ```shell 10 | sudo apt install linux-tools-generic 11 | ``` 12 | 13 | 检查perf安装是否成功 14 | 15 | ```shell 16 | perf -v 17 | ``` 18 | 19 | 对于wsl,运行上述安装指令后运行perf系统会出现找不到命名的问题,此时用户用下列指令查找perf路径 20 | 21 | ```shell 22 | find /usr/lib -name perf 23 | ``` 24 | 25 | 将查找到的perf二进制文件拷贝到`/usr/bin`下面就可以正常使用perf了 26 | 27 | ### 火焰图生成脚本使用 28 | 29 | > 💡**什么是火焰图以及为啥能用来分析程序性能呢?** 30 | > 核心的性能分析功能当然是由perf来完成的,当使用perf分析某个程序时,perf会借助系统中断机制对程序的执行过程进行采样,每次采样会记录程序的调用栈,例如每秒采样1000次,如果当前执行程序在某次采样中正在执行函数A,那么可以近似理解为函数A的耗时增加1ms(1s/采样频率),这样对程序的完整执行过程(也支持以attach的形式进行部分采样)进行采样可以得到程序各个函数的具体耗时,此时不难发现perf的性能分析结果是一种近似结果,当采样频率越高结果越精准,但采样频率受到linux系统参数和硬件的限制,并不是随便增加的。 31 | > 32 | > perf并不会直接输出火焰图,而是一份原始数据文件,使用第三方库[FlameGraph](https://github.com/brendangregg/FlameGraph.git)可以将其转换成svg格式易于用户查看,因形状酷似火焰得名火焰图。 33 | 34 | 在tinycoro项目的`scripts`文件夹下的`perf_svg.py`即生成脚本,比如用户对`./build/bin/hello`这个二进制文件进行性能分析,可以执行下列指令 35 | 36 | ```shell 37 | python ./scripts/perf_svg.py ./build/bin/hello 38 | ``` 39 | 40 | 运行成功的话svg文件默认会存储在tinycoro项目的`temp`目录下 41 | 42 | 使用浏览器打开svg文件可查看火焰图,效果如下: 43 | 44 | ![样例火焰图](../resource/exp_perf_svg.png) 45 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB SOURCES "*.cpp") 2 | 3 | set(debug_name coro_debug) 4 | 5 | # auto build new examples 6 | foreach(SOURCE ${SOURCES}) 7 | get_filename_component(FILENAME "${SOURCE}" NAME) 8 | get_filename_component(TARNAME "${SOURCE}" NAME_WE) 9 | add_executable(${TARNAME} ${FILENAME}) 10 | target_link_libraries(${TARNAME} ${PROJECT_NAME}) 11 | target_compile_definitions(${TARNAME} PRIVATE "$<$:DEBUG>") 12 | target_compile_definitions(${TARNAME} PRIVATE "$<$:RELEASE>") 13 | 14 | # if(${TARNAME} STREQUAL ${debug_name}) 15 | # target_compile_options(${TARNAME} PRIVATE "-g") 16 | # else() 17 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 18 | target_compile_options(${TARNAME} PRIVATE "-g") 19 | endif() 20 | if(ENABLE_COMPILE_OPTIMIZE) 21 | target_compile_options(${TARNAME} PUBLIC -O3) 22 | endif() 23 | # endif() 24 | endforeach() 25 | 26 | if(TARGET ${debug_name}) 27 | add_custom_target(build-debug 28 | COMMAND echo "build ${debug_name}..." 29 | DEPENDS ${debug_name} 30 | ) 31 | add_custom_target(run-debug 32 | COMMAND $ 33 | DEPENDS ${debug_name} 34 | ) 35 | endif() -------------------------------------------------------------------------------- /include/coro/io/base_io_type.hpp: -------------------------------------------------------------------------------- 1 | #include "coro/engine.hpp" 2 | 3 | namespace coro::io::detail 4 | { 5 | 6 | // If you need to use the feature "IOSQE_FIXED_FILE", just include fixed_fds as member varaible, 7 | // this class will automatically fetch fixed fds from local engine's io_uring 8 | struct fixed_fds 9 | { 10 | fixed_fds() noexcept 11 | { 12 | // borrow fixed fd from uring 13 | item = ::coro::detail::local_engine().get_uring().get_fixed_fd(); 14 | } 15 | 16 | ~fixed_fds() noexcept { return_back(); } 17 | 18 | inline auto assign(int& fd, int& flag) noexcept -> void 19 | { 20 | if (!item.valid()) 21 | { 22 | return; 23 | } 24 | *(item.ptr) = fd; 25 | fd = item.idx; 26 | flag |= IOSQE_FIXED_FILE; 27 | ::coro::detail::local_engine().get_uring().update_register_fixed_fds(item.idx); 28 | } 29 | 30 | inline auto return_back() noexcept -> void 31 | { 32 | // return back fixed fd to uring 33 | if (item.valid()) 34 | { 35 | ::coro::detail::local_engine().get_uring().back_fixed_fd(item); 36 | item.set_invalid(); 37 | } 38 | } 39 | 40 | ::coro::uring::uring_fds_item item; 41 | }; 42 | }; // namespace coro::io::detail 43 | -------------------------------------------------------------------------------- /include/coro/comp/latch.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file latch.hpp 3 | * @author JiahuiWang 4 | * @brief lab4b 5 | * @version 1.1 6 | * @date 2025-03-24 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | 15 | #include "coro/comp/event.hpp" 16 | 17 | namespace coro 18 | { 19 | 20 | class latch 21 | { 22 | public: 23 | using event_t = event<>; 24 | latch(std::uint64_t count) noexcept : m_count(count), m_ev(count <= 0) {} 25 | latch(const latch&) = delete; 26 | latch(latch&&) = delete; 27 | auto operator=(const latch&) -> latch& = delete; 28 | auto operator=(latch&&) -> latch& = delete; 29 | 30 | auto count_down() noexcept -> void 31 | { 32 | if (m_count.fetch_sub(1, std::memory_order::acq_rel) <= 1) 33 | { 34 | m_ev.set(); 35 | } 36 | } 37 | 38 | auto wait() noexcept -> event_t::awaiter { return m_ev.wait(); } 39 | 40 | private: 41 | std::atomic m_count; 42 | event_t m_ev; 43 | }; 44 | 45 | class latch_guard 46 | { 47 | public: 48 | latch_guard(latch& l) noexcept : m_l(l) {} 49 | ~latch_guard() noexcept { m_l.count_down(); } 50 | 51 | private: 52 | latch& m_l; 53 | }; 54 | 55 | }; // namespace coro 56 | -------------------------------------------------------------------------------- /include/coro/attribute.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.h" 4 | 5 | // This is a GCC extension; define it only for GCC and compilers that emulate GCC. 6 | #if defined(__GNUC__) && !defined(__clang__) 7 | #define CORO_ATTR(attr) __attribute__((attr)) 8 | #define CORO_INLINE __attribute__((always_inline)) 9 | #else 10 | #define CORO_ATTR(attr) 11 | #define CORO_INLINE 12 | #endif 13 | 14 | #define CORO_AWAIT_HINT nodiscard("Did you forget to co_await?") 15 | #define CORO_DISCARD_HINT nodiscard("Discard is improper") 16 | #define CORO_ALIGN alignas(::coro::config::kCacheLineSize) 17 | #define CORO_MAYBE_UNUSED maybe_unused 18 | 19 | // if one function is marked by CORO_TEST_USED, this means test 20 | // will use this function, don't modify this function declaration 21 | #define CORO_TEST_USED(...) 22 | 23 | #define CORO_NO_COPY_MOVE(classname) \ 24 | classname(const classname&) = delete; \ 25 | classname(classname&&) = delete; \ 26 | classname& operator=(const classname&) = delete; \ 27 | classname& operator=(classname&&) = delete 28 | 29 | // #define CORO_TEST_USED 30 | -------------------------------------------------------------------------------- /tests/lab3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "coro/coro.hpp" 4 | 5 | using namespace coro; 6 | 7 | #define BUFFLEN 1024 + 16 8 | 9 | task<> session(int fd) 10 | { 11 | char buf[BUFFLEN] = {0}; 12 | auto conn = io::net::tcp::tcp_connector(fd); 13 | int ret = 0; 14 | 15 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 16 | { 17 | ret = co_await conn.write(buf, ret); 18 | if (ret <= 0) 19 | { 20 | break; 21 | } 22 | } 23 | ret = co_await conn.close(); 24 | assert(ret == 0); 25 | } 26 | 27 | task<> server(int port) 28 | { 29 | auto server = io::net::tcp::tcp_server(port); 30 | log::info("server start in {}", port); 31 | int client_fd; 32 | while ((client_fd = co_await server.accept()) > 0) 33 | { 34 | submit_to_scheduler(session(client_fd)); 35 | } 36 | log::info("server stop in {}", port); 37 | } 38 | 39 | int main(int argc, char const* argv[]) 40 | { 41 | /* code */ 42 | int port; 43 | if (argc != 3) 44 | { 45 | log::info("usage: . num should between [0,100]"); 46 | return 0; 47 | } 48 | else 49 | { 50 | int num = std::stoi(argv[1]); 51 | port = std::stoi(argv[2]); 52 | 53 | scheduler::init(num); 54 | } 55 | 56 | submit_to_scheduler(server(port)); 57 | scheduler::loop(); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /include/coro/comp/wait_group.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file wait_group.hpp 3 | * @author JiahuiWang 4 | * @brief lab4c 5 | * @version 1.1 6 | * @date 2025-03-24 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | #include "coro/detail/types.hpp" 17 | 18 | namespace coro 19 | { 20 | class context; 21 | using detail::awaiter_ptr; 22 | 23 | class wait_group 24 | { 25 | public: 26 | struct awaiter 27 | { 28 | awaiter(context& ctx, wait_group& wg) noexcept : m_ctx(ctx), m_wg(wg) {} 29 | 30 | constexpr auto await_ready() noexcept -> bool { return false; } 31 | 32 | auto await_suspend(std::coroutine_handle<> handle) noexcept -> bool; 33 | 34 | auto await_resume() noexcept -> void; 35 | 36 | auto resume() noexcept -> void; 37 | 38 | context& m_ctx; 39 | wait_group& m_wg; 40 | awaiter* m_next{nullptr}; 41 | std::coroutine_handle<> m_await_coro{nullptr}; 42 | }; 43 | 44 | explicit wait_group(int count = 0) noexcept : m_count(count) {} 45 | 46 | auto add(int count) noexcept -> void; 47 | 48 | auto done() noexcept -> void; 49 | 50 | auto wait() noexcept -> awaiter; 51 | 52 | private: 53 | friend awaiter; 54 | std::atomic m_count; 55 | std::atomic m_state; 56 | }; 57 | 58 | }; // namespace coro 59 | -------------------------------------------------------------------------------- /examples/cv_notify_one.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "coro/coro.hpp" 4 | 5 | using namespace coro; 6 | 7 | cond_var cv; 8 | mutex mtx; 9 | std::queue que; 10 | 11 | task<> consumer() 12 | { 13 | int cnt{0}; 14 | auto lock = co_await mtx.lock_guard(); 15 | log::info("consumer hold lock"); 16 | while (cnt < 100) 17 | { 18 | co_await cv.wait(mtx, [&]() { return !que.empty(); }); 19 | while (!que.empty()) 20 | { 21 | log::info("consumer fetch value: {}", que.front()); 22 | que.pop(); 23 | cnt++; 24 | } 25 | log::info("consumer cnt: {}", cnt); 26 | cv.notify_one(); 27 | } 28 | log::info("consumer finish"); 29 | } 30 | 31 | task<> producer() 32 | { 33 | for (int i = 0; i < 10; i++) 34 | { 35 | auto lock = co_await mtx.lock_guard(); 36 | log::info("producer hold lock"); 37 | co_await cv.wait(mtx, [&]() { return que.size() < 10; }); 38 | for (int j = 0; j < 10; j++) 39 | { 40 | que.push(i * 10 + j); 41 | } 42 | log::info("producer add value finish"); 43 | cv.notify_one(); 44 | } 45 | } 46 | 47 | int main(int argc, char const* argv[]) 48 | { 49 | /* code */ 50 | scheduler::init(); 51 | 52 | submit_to_scheduler(consumer()); 53 | submit_to_scheduler(producer()); 54 | 55 | scheduler::loop(); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /benchmark/README.MD: -------------------------------------------------------------------------------- 1 | # benchmark 2 | 3 | 接下来演示如何运行性能测试,注意shell代码里用`init_dir`指示该shell初始的path,`tinycoro_path`表示tinycoro项目根目录的路径 4 | 5 | ## 测试工具准备 6 | 7 | 安装cargo 8 | 9 | ```shell 10 | sudo apt-get install cargo 11 | ``` 12 | 13 | 编译测试工具 14 | 15 | ```shell 16 | # init_dir=tinycoro_path/build 17 | make build-benchtools 18 | ``` 19 | 20 | ## 运行测试 21 | 22 | 由于压测工具与待测试程序的构建分离,需要运行下面的指令构建待测试程序 23 | 24 | ```shell 25 | # init_dir=tinycoro_path/build 26 | make build-bench 27 | ``` 28 | 29 | tinycoro提供了5种服务端模型供压测 30 | 31 | - `bench_tinycoro_128` 32 | - `bench_tinycoro_1k` 33 | - `bench_tinycoro_16k` 34 | - `bench_epoll_server` // 暂时废弃 35 | - `bench_uring_server` // 暂时废弃 36 | 37 | 这里以负载1k,并发量为10为样例,用户开启两个终端分别为服务端和测试端 38 | 39 | **step1.** 运行服务端程序,注意这里不用直接运行二进制文件,只需要`make`后跟上述的服务端模型的名字就行,服务端程序默认使用配置文件中的端口号 40 | 41 | ```shell 42 | # init_dir=tinycoro_path/build 43 | make bench_tinycoro_1k 44 | ``` 45 | 46 | **step2.** 运行测试端工具 47 | 48 | ```shell 49 | # init_dir=tinycoro_path 50 | cd benchmark 51 | bash bench.sh 8000 10 1024 52 | ``` 53 | 54 | 注意`bench.sh`需要接收三个参数,端口号、并发量和负载大小,负载大小默认以byte为单位。 55 | 56 | ## 运行baseline模型 57 | 58 | 与tinycoro作对比的基线模型为rust_echo_server,读者若想运行该基线模型可按下列步骤操作。 59 | 60 | 克隆rust_echo_server至本地 61 | 62 | ```shell 63 | git clone https://github.com/haraldh/rust_echo_server.git 64 | ``` 65 | 66 | 编译并运行 67 | 68 | ```shell 69 | cd rust_echo_server 70 | cargo run --release 71 | ``` 72 | 73 | 注意rust_echo_server在本地开启监听的端口号为12345,对其进行压测的步骤与前述相同。 74 | -------------------------------------------------------------------------------- /include/coro/allocator/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "coro/attribute.hpp" 8 | #include "coro/detail/types.hpp" 9 | 10 | namespace coro::allocator::memory 11 | { 12 | /** 13 | * @brief The template for developer to implement memory allocator strategy 14 | * 15 | * @tparam alloc_strategy 16 | */ 17 | template 18 | class memory_allocator 19 | { 20 | public: 21 | struct config 22 | { 23 | }; 24 | 25 | public: 26 | ~memory_allocator() = default; 27 | 28 | auto init(config config) -> void {} 29 | 30 | auto allocate(size_t size) -> void* { return nullptr; } 31 | 32 | auto release(void* ptr) -> void {} 33 | }; 34 | 35 | /** 36 | * @brief std memory allocator 37 | * 38 | * @tparam 39 | */ 40 | template<> 41 | class memory_allocator 42 | { 43 | public: 44 | struct config 45 | { 46 | }; 47 | 48 | public: 49 | ~memory_allocator() = default; 50 | 51 | auto init(config config) -> void {} 52 | 53 | auto allocate(size_t size) -> void* CORO_INLINE { return malloc(size); } 54 | 55 | auto release(void* ptr) -> void CORO_INLINE { free(ptr); } 56 | }; 57 | 58 | // This config type is used to provide for outsides 59 | using mem_alloc_config = memory_allocator::config; 60 | 61 | }; // namespace coro::allocator::memory 62 | -------------------------------------------------------------------------------- /examples/stdin_client.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/coro.hpp" 2 | 3 | using namespace coro; 4 | 5 | #define BUFFLEN 10240 6 | 7 | task<> echo(int sockfd) 8 | { 9 | char buf[BUFFLEN] = {0}; 10 | int ret = 0; 11 | auto conn = io::net::tcp::tcp_connector(sockfd); 12 | 13 | while (true) 14 | { 15 | ret = co_await io::stdin_awaiter(buf, BUFFLEN, 0); 16 | log::info("receive data from stdin: {}", buf); 17 | ret = co_await conn.write(buf, ret); 18 | } 19 | } 20 | 21 | task<> client(const char* addr, int port) 22 | { 23 | auto client = io::net::tcp::tcp_client(addr, port); 24 | int ret = 0; 25 | int sockfd = 0; 26 | sockfd = co_await client.connect(); 27 | assert(sockfd > 0 && "connect error"); 28 | 29 | if (sockfd <= 0) 30 | { 31 | log::error("connect error"); 32 | co_return; 33 | } 34 | 35 | submit_to_scheduler(echo(sockfd)); 36 | 37 | char buf[BUFFLEN] = {0}; 38 | auto conn = io::net::tcp::tcp_connector(sockfd); 39 | while ((ret = co_await conn.read(buf, BUFFLEN)) > 0) 40 | { 41 | log::info("receive data from net: {}", buf); 42 | } 43 | 44 | ret = co_await conn.close(); 45 | assert(ret == 0); 46 | } 47 | 48 | int main(int argc, char const* argv[]) 49 | { 50 | /* code */ 51 | scheduler::init(); 52 | submit_to_scheduler(client("localhost", 8000)); 53 | 54 | scheduler::loop(); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /include/coro/meta_info.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef ENABLE_MEMORY_ALLOC 6 | #include "coro/allocator/memory.hpp" 7 | #endif 8 | #include "coro/attribute.hpp" 9 | 10 | namespace coro 11 | { 12 | class context; 13 | }; // namespace coro 14 | 15 | namespace coro::detail 16 | { 17 | using config::ctx_id; 18 | using std::atomic; 19 | class engine; 20 | 21 | /** 22 | * @brief store thread local variables 23 | * 24 | */ 25 | struct CORO_ALIGN local_info 26 | { 27 | context* ctx{nullptr}; 28 | engine* egn{nullptr}; 29 | // TODO: Add more local var 30 | }; 31 | 32 | /** 33 | * @brief store thread shared variables 34 | * 35 | */ 36 | struct global_info 37 | { 38 | atomic context_id{0}; 39 | atomic engine_id{0}; 40 | // TODO: Add more global var 41 | #ifdef ENABLE_MEMORY_ALLOC 42 | coro::allocator::memory::memory_allocator* mem_alloc; 43 | #endif 44 | }; 45 | 46 | inline thread_local local_info linfo; 47 | inline global_info ginfo; 48 | 49 | // init global info 50 | inline auto init_meta_info() noexcept -> void 51 | { 52 | ginfo.context_id = 0; 53 | ginfo.engine_id = 0; 54 | #ifdef ENABLE_MEMORY_ALLOC 55 | ginfo.mem_alloc = nullptr; 56 | #endif 57 | } 58 | 59 | // This function is used to distinguish whether you are currently in a worker thread 60 | inline auto is_in_working_state() noexcept -> bool 61 | { 62 | return linfo.ctx != nullptr; 63 | } 64 | }; // namespace coro::detail -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE TINYCORO_BENCH_SOURCES "${PROJECT_SOURCE_DIR}/benchmark/*/*.cpp") 2 | 3 | add_custom_target(build-bench COMMAND echo "Building benchmark case...") 4 | 5 | add_custom_target(build-benchtools 6 | COMMAND cargo run --release --manifest-path=${PROJECT_SOURCE_DIR}/third_party/rust_echo_bench/Cargo.toml -- --help 7 | ) 8 | 9 | foreach (tinycoro_bench_source ${TINYCORO_BENCH_SOURCES}) 10 | get_filename_component(tinycoro_bench_filename ${tinycoro_bench_source} NAME) 11 | string(REPLACE ".cpp" "" tinycoro_bench_name ${tinycoro_bench_filename}) 12 | add_executable(${tinycoro_bench_name} EXCLUDE_FROM_ALL ${tinycoro_bench_source}) 13 | add_dependencies(build-bench ${tinycoro_bench_name}) 14 | 15 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 16 | target_compile_options(${tinycoro_bench_name} PRIVATE "-g") 17 | endif() 18 | if(ENABLE_COMPILE_OPTIMIZE) 19 | target_compile_options(${tinycoro_bench_name} PUBLIC -O3) 20 | endif() 21 | target_link_libraries(${tinycoro_bench_name} ${PROJECT_NAME}) 22 | 23 | set_target_properties(${tinycoro_bench_name} 24 | PROPERTIES 25 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/benchmark" 26 | ) 27 | 28 | string(REPLACE "_bench" "" tinycoro_bench_command ${tinycoro_bench_name}) 29 | add_custom_target(bench_${tinycoro_bench_command} 30 | COMMAND $ 31 | DEPENDS ${tinycoro_bench_name} 32 | COMMENT "Running ${tinycoro_bench_command} bench..." 33 | ) 34 | endforeach() 35 | -------------------------------------------------------------------------------- /include/coro/spinlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(_MSC_VER) 6 | #include 7 | #endif 8 | 9 | namespace coro::detail 10 | { 11 | using std::atomic; 12 | using std::memory_order_acquire; 13 | using std::memory_order_relaxed; 14 | using std::memory_order_release; 15 | 16 | struct spinlock 17 | { 18 | atomic lock_ = {0}; 19 | 20 | void lock() noexcept 21 | { 22 | for (;;) 23 | { 24 | // Optimistically assume the lock is free on the first try 25 | if (!lock_.exchange(true, memory_order_acquire)) 26 | { 27 | return; 28 | } 29 | // Wait for lock to be released without generating cache misses 30 | while (lock_.load(memory_order_relaxed)) 31 | { 32 | // Issue X86 PAUSE or ARM YIELD instruction to reduce contention between 33 | // hyper-threads 34 | 35 | #if defined(__GNUC__) || defined(__clang__) 36 | __builtin_ia32_pause(); 37 | #elif defined(_MSC_VER) 38 | _mm_pause(); 39 | #else 40 | __builtin_ia32_pause(); 41 | #endif 42 | } 43 | } 44 | } 45 | 46 | bool try_lock() noexcept 47 | { 48 | // First do a relaxed load to check if lock is free in order to prevent 49 | // unnecessary cache misses if someone does while(!try_lock()) 50 | return !lock_.load(memory_order_relaxed) && !lock_.exchange(true, memory_order_acquire); 51 | } 52 | 53 | void unlock() noexcept { lock_.store(false, memory_order_release); } 54 | }; 55 | 56 | }; // namespace coro -------------------------------------------------------------------------------- /scripts/CITests.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import sys 3 | import subprocess 4 | 5 | file_path = "CITests.yml" 6 | 7 | 8 | def exec_command(command: str) -> bool: 9 | print(f"ready to run command: {command}") 10 | try: 11 | result = subprocess.run( 12 | command, shell=True, check=True, text=True, capture_output=True 13 | ) 14 | if result.returncode == 0: 15 | print(f"commmand [{command}] exec success!") 16 | return True 17 | else: 18 | print(f"commmand [{command}] exec failed!") 19 | print(f"err info: {e.stderr}") 20 | return False 21 | 22 | except subprocess.CalledProcessError as e: 23 | print(f"commmand [{command}] exec occur exception!") 24 | print(f"err info: {e.stderr}") 25 | return False 26 | 27 | 28 | def ci_tests(file_path: str): 29 | with open(file_path, "r", encoding="utf-8") as file: 30 | data = yaml.safe_load(file) 31 | 32 | tests_items = data["tests"] 33 | 34 | # pre-command 35 | for key in tests_items.keys(): 36 | if key == "pre_command": 37 | pre_commands = tests_items[key] 38 | for item in pre_commands: 39 | if exec_command(item) is False: 40 | sys.exit(1) 41 | else: 42 | test_body = tests_items[key] 43 | for sub_key in test_body.keys(): 44 | value = test_body[sub_key] 45 | if value["enable"] is True: 46 | if exec_command(value["command"]) is False: 47 | sys.exit(1) 48 | 49 | 50 | if __name__ == "__main__": 51 | ci_tests(file_path) 52 | -------------------------------------------------------------------------------- /src/comp/wait_group.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/comp/wait_group.hpp" 2 | #include "coro/scheduler.hpp" 3 | 4 | namespace coro 5 | { 6 | 7 | auto wait_group::awaiter::await_suspend(std::coroutine_handle<> handle) noexcept -> bool 8 | { 9 | m_await_coro = handle; 10 | m_ctx.register_wait(); 11 | while (true) 12 | { 13 | if (m_wg.m_count.load(std::memory_order_acquire) == 0) 14 | { 15 | return false; 16 | } 17 | auto head = m_wg.m_state.load(std::memory_order_acquire); 18 | m_next = static_cast(head); 19 | if (m_wg.m_state.compare_exchange_weak( 20 | head, static_cast(this), std::memory_order_acq_rel, std::memory_order_relaxed)) 21 | { 22 | // m_ctx.register_wait(m_register_state); 23 | // m_register_state = false; 24 | return true; 25 | } 26 | } 27 | } 28 | 29 | auto wait_group::awaiter::await_resume() noexcept -> void 30 | { 31 | m_ctx.unregister_wait(); 32 | } 33 | 34 | auto wait_group::awaiter::resume() noexcept -> void 35 | { 36 | m_ctx.submit_task(m_await_coro); 37 | } 38 | 39 | auto wait_group::add(int count) noexcept -> void 40 | { 41 | m_count.fetch_add(count, std::memory_order_acq_rel); 42 | } 43 | 44 | auto wait_group::done() noexcept -> void 45 | { 46 | if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1) 47 | { 48 | auto head = static_cast(m_state.exchange(nullptr, std::memory_order_acq_rel)); 49 | while (head != nullptr) 50 | { 51 | head->resume(); 52 | head = head->m_next; 53 | } 54 | } 55 | } 56 | 57 | auto wait_group::wait() noexcept -> awaiter 58 | { 59 | return awaiter{local_context(), *this}; 60 | } 61 | 62 | }; // namespace coro 63 | -------------------------------------------------------------------------------- /src/comp/event.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/comp/event.hpp" 2 | #include "coro/scheduler.hpp" 3 | 4 | namespace coro::detail 5 | { 6 | auto event_base::awaiter_base::await_ready() noexcept -> bool 7 | { 8 | m_ctx.register_wait(); 9 | return m_ev.is_set(); 10 | } 11 | 12 | auto event_base::awaiter_base::await_suspend(std::coroutine_handle<> handle) noexcept -> bool 13 | { 14 | m_await_coro = handle; 15 | return m_ev.register_awaiter(this); 16 | } 17 | 18 | auto event_base::awaiter_base::await_resume() noexcept -> void 19 | { 20 | m_ctx.unregister_wait(); 21 | } 22 | 23 | auto event_base::set_state() noexcept -> void 24 | { 25 | auto flag = m_state.exchange(this, std::memory_order_acq_rel); 26 | if (flag != this) 27 | { 28 | auto waiter = static_cast(flag); 29 | resume_all_awaiter(waiter); 30 | } 31 | } 32 | 33 | auto event_base::resume_all_awaiter(awaiter_ptr waiter) noexcept -> void 34 | { 35 | while (waiter != nullptr) 36 | { 37 | auto cur = static_cast(waiter); 38 | cur->m_ctx.submit_task(cur->m_await_coro); 39 | // cur->m_ctx.unregister_wait(); 40 | waiter = cur->m_next; 41 | } 42 | } 43 | 44 | auto event_base::register_awaiter(awaiter_base* waiter) noexcept -> bool 45 | { 46 | const auto set_state = this; 47 | awaiter_ptr old_value = nullptr; 48 | 49 | do 50 | { 51 | old_value = m_state.load(std::memory_order_acquire); 52 | if (old_value == this) 53 | { 54 | waiter->m_next = nullptr; 55 | return false; 56 | } 57 | waiter->m_next = static_cast(old_value); 58 | } while (!m_state.compare_exchange_weak(old_value, waiter, std::memory_order_acquire)); 59 | 60 | // waiter->m_ctx.register_wait(); 61 | return true; 62 | } 63 | 64 | }; // namespace coro::detail 65 | -------------------------------------------------------------------------------- /include/coro/marked_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace coro::detail 9 | { 10 | 11 | /** 12 | * @brief marked_buffer is a fixed buffer which can lease buffer item to outside 13 | * 14 | * @tparam type: buffer item type 15 | * @tparam length: length of fixed buffer 16 | */ 17 | template 18 | struct marked_buffer 19 | { 20 | struct item 21 | { 22 | inline auto valid() -> bool { return idx >= 0; } 23 | inline auto set_invalid() -> void 24 | { 25 | idx = -1; 26 | ptr = nullptr; 27 | } 28 | int idx; // idx < 0 means item invalid 29 | Type* ptr; 30 | }; 31 | 32 | marked_buffer() noexcept { init(); } 33 | 34 | void init() noexcept 35 | { 36 | std::queue temp; 37 | que.swap(temp); 38 | } 39 | 40 | void set_data(const std::vector& values) 41 | { 42 | assert(data.size() == length && ""); 43 | 44 | for (int i = 0; i < length; i++) 45 | { 46 | que.push(i); 47 | } 48 | 49 | for (int i = 0; i < length; i++) 50 | { 51 | data[i] = values[i]; 52 | } 53 | } 54 | 55 | // Allocate a buffer item to outsize 56 | item borrow() noexcept 57 | { 58 | if (que.empty()) 59 | { 60 | return item{.idx = -1, .ptr = nullptr}; 61 | } 62 | auto idx = que.front(); 63 | que.pop(); 64 | return item{.idx = idx, .ptr = &(data[idx])}; 65 | } 66 | 67 | // Recycle a buffer item from outsize 68 | void return_back(item it) noexcept 69 | { 70 | // ignore invalid item 71 | if (!it.valid()) 72 | { 73 | return; 74 | } 75 | it.ptr = nullptr; 76 | que.push(it.idx); 77 | } 78 | 79 | Type data[length]; 80 | // Every index in que means this index is free now 81 | std::queue que; 82 | }; 83 | 84 | }; // namespace coro::detail -------------------------------------------------------------------------------- /include/coro/dispatcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "coro/context.hpp" 8 | #include "coro/detail/types.hpp" 9 | 10 | namespace coro::detail 11 | { 12 | using ctx_container = std::vector>; 13 | 14 | // template 15 | // concept dispatcher_type = requires(dispatcher t) { 16 | // { t.dispatch_impl() } -> std::same_as; 17 | // }; 18 | 19 | // template 20 | // class dispatcher_base 21 | // { 22 | // public: 23 | // dispatcher_base(const size_t ctx_cnt, ctx_container& ctxs) noexcept : m_ctx_cnt(ctx_cnt), m_ctxs(ctxs) {} 24 | 25 | // size_t dispatch() noexcept { return static_cast(this)->dispatch_impl(); } 26 | 27 | // protected: 28 | // const size_t m_ctx_cnt; 29 | // ctx_container& m_ctxs; 30 | // }; 31 | 32 | template 33 | class dispatcher 34 | { 35 | public: 36 | // Each strategy must impl init and dispatch. 37 | 38 | /** 39 | * @brief 40 | * 41 | * @param ctx_cnt 42 | * @param ctxs 43 | */ 44 | void init([[CORO_MAYBE_UNUSED]] const size_t ctx_cnt, [[CORO_MAYBE_UNUSED]] ctx_container* ctxs) noexcept {} 45 | 46 | /** 47 | * @brief Choose one context to schedule by specific strategy. 48 | * The impl needs to ensure thread safety. 49 | * 50 | * @return size_t 51 | */ 52 | auto dispatch() noexcept -> size_t { return 0; } 53 | }; 54 | 55 | /** 56 | * @brief round robin is an easy load balance algorithm 57 | * 58 | * @tparam 59 | */ 60 | template<> 61 | class dispatcher 62 | { 63 | public: 64 | void init(size_t ctx_cnt, [[CORO_MAYBE_UNUSED]] ctx_container* ctxs) noexcept 65 | { 66 | m_ctx_cnt = ctx_cnt; 67 | m_cur = 0; 68 | } 69 | 70 | auto dispatch() noexcept -> size_t { return m_cur.fetch_add(1, std::memory_order_acq_rel) % m_ctx_cnt; } 71 | 72 | private: 73 | size_t m_ctx_cnt; 74 | std::atomic m_cur{0}; 75 | }; 76 | 77 | }; // namespace coro::detail 78 | -------------------------------------------------------------------------------- /include/coro/io/net/tcp/tcp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "config.h" 13 | #include "coro/io/base_io_type.hpp" 14 | #include "coro/io/io_awaiter.hpp" 15 | 16 | namespace coro::io::net::tcp 17 | { 18 | 19 | class tcp_connector 20 | { 21 | public: 22 | explicit tcp_connector(int sockfd) noexcept : m_sockfd(sockfd), m_original_fd(sockfd), m_sqe_flag(0) 23 | { 24 | m_fixed_fd.assign(m_sockfd, m_sqe_flag); 25 | } 26 | 27 | tcp_read_awaiter read(char* buf, size_t len, int io_flags = 0) noexcept 28 | { 29 | return tcp_read_awaiter(m_sockfd, buf, len, io_flags, m_sqe_flag); 30 | } 31 | 32 | tcp_write_awaiter write(char* buf, size_t len, int io_flags = 0) noexcept 33 | { 34 | return tcp_write_awaiter(m_sockfd, buf, len, io_flags, m_sqe_flag); 35 | } 36 | 37 | // close() must use original sock fd 38 | tcp_close_awaiter close() noexcept 39 | { 40 | m_fixed_fd.return_back(); 41 | return tcp_close_awaiter(m_original_fd); 42 | } 43 | 44 | private: 45 | int m_sockfd; // Can be converted to fix fd 46 | const int m_original_fd; // original sock fd 47 | 48 | detail::fixed_fds m_fixed_fd; 49 | int m_sqe_flag; 50 | }; 51 | 52 | class tcp_server 53 | { 54 | public: 55 | explicit tcp_server(int port = ::coro::config::kDefaultPort) noexcept : tcp_server(nullptr, port) {} 56 | 57 | tcp_server(const char* addr, int port) noexcept; 58 | 59 | tcp_accept_awaiter accept(int io_flags = 0) noexcept; 60 | 61 | private: 62 | int m_listenfd; 63 | int m_port; 64 | sockaddr_in m_servaddr; 65 | 66 | detail::fixed_fds m_fixed_fd; 67 | int m_sqe_flag{0}; 68 | }; 69 | 70 | class tcp_client 71 | { 72 | public: 73 | tcp_client(const char* addr, int port) noexcept; 74 | 75 | tcp_connect_awaiter connect() noexcept; 76 | 77 | private: 78 | int m_clientfd; 79 | int m_port; 80 | sockaddr_in m_servaddr; 81 | }; 82 | 83 | }; // namespace coro::io::net::tcp 84 | -------------------------------------------------------------------------------- /benchtests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE TINYCORO_BENCHTEST_SOURCES "${PROJECT_SOURCE_DIR}/benchtests/*bench.cpp") 2 | 3 | set(benchbuild_list "") 4 | set(benchtest_list "") 5 | 6 | add_custom_target(_build-benchtests) 7 | 8 | foreach (tinycoro_benchtest_source ${TINYCORO_BENCHTEST_SOURCES}) 9 | get_filename_component(tinycoro_benchtest_filename ${tinycoro_benchtest_source} NAME) 10 | string(REPLACE ".cpp" "" tinycoro_benchtest_name ${tinycoro_benchtest_filename}) 11 | add_executable(${tinycoro_benchtest_name} EXCLUDE_FROM_ALL ${tinycoro_benchtest_source}) 12 | add_dependencies(_build-benchtests ${tinycoro_benchtest_name}) 13 | 14 | target_link_libraries(${tinycoro_benchtest_name} ${PROJECT_NAME} benchmark) 15 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 16 | target_compile_options(${tinycoro_benchtest_name} PRIVATE "-g") 17 | endif() 18 | if(ENABLE_COMPILE_OPTIMIZE) 19 | target_compile_options(${tinycoro_benchtest_name} PUBLIC -O3) 20 | endif() 21 | 22 | set_target_properties(${tinycoro_benchtest_name} 23 | PROPERTIES 24 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/benchtests" 25 | COMMAND ${tinycoro_benchtest_name} 26 | ) 27 | 28 | string(REPLACE "_bench" "" tinycoro_benchtest_command ${tinycoro_benchtest_name}) 29 | add_custom_target(benchbuild-${tinycoro_benchtest_command} 30 | COMMAND echo "build ${tinycoro_benchtest_command} benchtest..." 31 | DEPENDS ${tinycoro_benchtest_name} 32 | COMMENT "build ${tinycoro_benchtest_command} benchtests..." 33 | ) 34 | 35 | add_custom_target(benchtest-${tinycoro_benchtest_command} 36 | COMMAND $ 37 | DEPENDS ${tinycoro_benchtest_name} 38 | COMMENT "Running ${tinycoro_benchtest_command} benchtests..." 39 | ) 40 | 41 | list(APPEND benchbuild_list benchbuild-${tinycoro_benchtest_command}) 42 | list(APPEND benchtest_list benchtest-${tinycoro_benchtest_command}) 43 | endforeach() 44 | 45 | add_custom_target(build-benchtests 46 | COMMAND echo "[benchtests build command]" 47 | COMMAND ${PROJECT_SOURCE_DIR}/scripts/print_list.sh ${benchbuild_list} 48 | COMMAND echo "[benchtests run command]" 49 | COMMAND ${PROJECT_SOURCE_DIR}/scripts/print_list.sh ${benchtest_list} 50 | DEPENDS _build-benchtests 51 | ) -------------------------------------------------------------------------------- /src/scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/scheduler.hpp" 2 | #include "coro/meta_info.hpp" 3 | 4 | namespace coro 5 | { 6 | auto scheduler::init_impl(size_t ctx_cnt) noexcept -> void 7 | { 8 | detail::init_meta_info(); 9 | 10 | m_ctx_cnt = ctx_cnt; 11 | m_ctxs = detail::ctx_container{}; 12 | m_ctxs.reserve(m_ctx_cnt); 13 | for (int i = 0; i < m_ctx_cnt; i++) 14 | { 15 | m_ctxs.emplace_back(std::make_unique()); 16 | } 17 | m_dispatcher.init(m_ctx_cnt, &m_ctxs); 18 | m_ctx_stop_flag = stop_flag_type(m_ctx_cnt, detail::atomic_ref_wrapper{.val = 1}); 19 | m_stop_token = m_ctx_cnt; 20 | 21 | #ifdef ENABLE_MEMORY_ALLOC 22 | coro::allocator::memory::mem_alloc_config config; 23 | m_mem_alloc.init(config); 24 | ginfo.mem_alloc = &m_mem_alloc; 25 | #endif 26 | } 27 | 28 | auto scheduler::start_impl() noexcept -> void 29 | { 30 | for (int i = 0; i < m_ctx_cnt; i++) 31 | { 32 | m_ctxs[i]->set_stop_cb( 33 | [&, i]() 34 | { 35 | auto cnt = std::atomic_ref(this->m_ctx_stop_flag[i].val).fetch_and(0, memory_order_acq_rel); 36 | if (this->m_stop_token.fetch_sub(cnt) == cnt) 37 | { 38 | // Stop token decrease to zero, it's time to close all context 39 | this->stop_impl(); 40 | } 41 | }); 42 | m_ctxs[i]->start(); 43 | } 44 | } 45 | 46 | auto scheduler::loop_impl() noexcept -> void 47 | { 48 | start_impl(); 49 | for (int i = 0; i < m_ctx_cnt; i++) 50 | { 51 | m_ctxs[i]->join(); 52 | } 53 | } 54 | 55 | auto scheduler::stop_impl() noexcept -> void 56 | { 57 | for (int i = 0; i < m_ctx_cnt; i++) 58 | { 59 | m_ctxs[i]->notify_stop(); 60 | } 61 | } 62 | 63 | auto scheduler::submit_task_impl(std::coroutine_handle<> handle) noexcept -> void 64 | { 65 | assert(this->m_stop_token.load(std::memory_order_acquire) != 0 && "error! submit task after scheduler loop finish"); 66 | size_t ctx_id = m_dispatcher.dispatch(); 67 | m_stop_token.fetch_add( 68 | 1 - std::atomic_ref(m_ctx_stop_flag[ctx_id].val).fetch_or(1, memory_order_acq_rel), memory_order_acq_rel); 69 | m_ctxs[ctx_id]->submit_task(handle); 70 | } 71 | }; // namespace coro 72 | -------------------------------------------------------------------------------- /include/coro/comp/condition_variable.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file condition_variable.hpp 3 | * @author JiahuiWang 4 | * @brief lab5b 5 | * @version 1.1 6 | * @date 2025-03-24 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | 15 | #include "coro/attribute.hpp" 16 | #include "coro/comp/mutex.hpp" 17 | #include "coro/spinlock.hpp" 18 | 19 | namespace coro 20 | { 21 | 22 | using cond_type = std::function; 23 | 24 | class condition_variable; 25 | using cond_var = condition_variable; 26 | 27 | class condition_variable final 28 | { 29 | public: 30 | struct cv_awaiter : public mutex::mutex_awaiter 31 | { 32 | friend condition_variable; 33 | 34 | cv_awaiter(context& ctx, mutex& mtx, cond_var& cv) noexcept 35 | : mutex_awaiter(ctx, mtx), 36 | m_cv(cv), 37 | m_suspend_state(false) 38 | { 39 | } 40 | cv_awaiter(context& ctx, mutex& mtx, cond_var& cv, cond_type& cond) noexcept 41 | : mutex_awaiter(ctx, mtx), 42 | m_cv(cv), 43 | m_cond(cond), 44 | m_suspend_state(false) 45 | { 46 | } 47 | 48 | auto await_suspend(std::coroutine_handle<> handle) noexcept -> bool; 49 | 50 | auto await_resume() noexcept -> void; 51 | 52 | protected: 53 | auto register_lock() noexcept -> bool; 54 | 55 | auto register_cv() noexcept -> void; 56 | 57 | auto wake_up() noexcept -> void; 58 | 59 | auto resume() noexcept -> void override; 60 | 61 | cond_type m_cond; 62 | cond_var& m_cv; 63 | bool m_suspend_state; 64 | }; 65 | 66 | public: 67 | condition_variable() noexcept = default; 68 | ~condition_variable() noexcept; 69 | 70 | CORO_NO_COPY_MOVE(condition_variable); 71 | 72 | auto wait(mutex& mtx) noexcept -> cv_awaiter; 73 | 74 | auto wait(mutex& mtx, cond_type&& cond) noexcept -> cv_awaiter; 75 | 76 | auto wait(mutex& mtx, cond_type& cond) noexcept -> cv_awaiter; 77 | 78 | auto notify_one() noexcept -> void; 79 | 80 | auto notify_all() noexcept -> void; 81 | 82 | private: 83 | detail::spinlock m_lock; 84 | alignas(config::kCacheLineSize) cv_awaiter* m_head{nullptr}; 85 | alignas(config::kCacheLineSize) cv_awaiter* m_tail{nullptr}; 86 | }; 87 | 88 | }; // namespace coro 89 | -------------------------------------------------------------------------------- /include/coro/comp/mutex.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mutex.hpp 3 | * @author JiahuiWang 4 | * @brief lab4d 5 | * @version 1.1 6 | * @date 2025-03-24 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "coro/comp/mutex_guard.hpp" 19 | #include "coro/detail/types.hpp" 20 | 21 | namespace coro 22 | { 23 | 24 | class context; 25 | 26 | using detail::awaiter_ptr; 27 | 28 | class mutex 29 | { 30 | public: 31 | struct mutex_awaiter 32 | { 33 | mutex_awaiter(context& ctx, mutex& mtx) noexcept : m_ctx(ctx), m_mtx(mtx) {} 34 | 35 | constexpr auto await_ready() noexcept -> bool { return false; } 36 | 37 | auto await_suspend(std::coroutine_handle<> handle) noexcept -> bool; 38 | 39 | auto await_resume() noexcept -> void; 40 | 41 | auto register_lock() noexcept -> bool; 42 | 43 | virtual auto resume() noexcept -> void; 44 | 45 | context& m_ctx; 46 | mutex& m_mtx; 47 | mutex_awaiter* m_next{nullptr}; 48 | std::coroutine_handle<> m_await_coro{nullptr}; 49 | }; 50 | 51 | struct mutex_guard_awaiter : public mutex_awaiter 52 | { 53 | using guard_type = detail::lock_guard; 54 | using mutex_awaiter::mutex_awaiter; 55 | 56 | auto await_resume() noexcept -> guard_type 57 | { 58 | mutex_awaiter::await_resume(); 59 | return guard_type(m_mtx); 60 | } 61 | }; 62 | 63 | public: 64 | mutex() noexcept : m_state(nolocked), m_resume_list_head(nullptr) {} 65 | ~mutex() noexcept { assert(m_state.load(std::memory_order_acquire) == mutex::nolocked); } 66 | 67 | auto try_lock() noexcept -> bool; 68 | 69 | auto lock() noexcept -> mutex_awaiter; 70 | 71 | auto unlock() noexcept -> void; 72 | 73 | auto lock_guard() noexcept -> mutex_guard_awaiter; 74 | 75 | private: 76 | friend mutex_awaiter; 77 | 78 | // make locked_no_waiting = 0 make state change easier 79 | inline static awaiter_ptr nolocked = reinterpret_cast(1); 80 | inline static awaiter_ptr locked_no_waiting = 0; // nullptr 81 | std::atomic m_state; 82 | awaiter_ptr m_resume_list_head; 83 | }; 84 | 85 | }; // namespace coro 86 | -------------------------------------------------------------------------------- /include/coro/io/io_awaiter.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file io_awaiter.hpp 3 | * @author Jiahui Wang 4 | * @brief This file includes all awaiter of IO operations 5 | * @version 1.2 6 | * @date 2025-05-27 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | 15 | #include "coro/io/base_awaiter.hpp" 16 | 17 | namespace coro::io 18 | { 19 | using ::coro::io::detail::io_info; 20 | 21 | class noop_awaiter : public detail::base_io_awaiter 22 | { 23 | public: 24 | noop_awaiter() noexcept; 25 | static auto callback(io_info* data, int res) noexcept -> void; 26 | }; 27 | 28 | class stdin_awaiter : public detail::base_io_awaiter 29 | { 30 | public: 31 | stdin_awaiter(char* buf, size_t len, int io_flag = 0, int sqe_flag = 0) noexcept; 32 | 33 | static auto callback(io_info* data, int res) noexcept -> void; 34 | }; 35 | 36 | namespace net 37 | { 38 | /** 39 | * @brief tcp awaiter 40 | * 41 | */ 42 | namespace tcp 43 | { 44 | class tcp_accept_awaiter : public detail::base_io_awaiter 45 | { 46 | public: 47 | tcp_accept_awaiter(int listenfd, int io_flag = 0, int sqe_flag = 0) noexcept; 48 | 49 | static auto callback(io_info* data, int res) noexcept -> void; 50 | 51 | private: 52 | inline static socklen_t len = sizeof(sockaddr_in); 53 | }; 54 | 55 | class tcp_read_awaiter : public detail::base_io_awaiter 56 | { 57 | public: 58 | tcp_read_awaiter(int sockfd, char* buf, size_t len, int io_flag = 0, int sqe_flag = 0) noexcept; 59 | 60 | static auto callback(io_info* data, int res) noexcept -> void; 61 | }; 62 | 63 | class tcp_write_awaiter : public detail::base_io_awaiter 64 | { 65 | public: 66 | tcp_write_awaiter(int sockfd, char* buf, size_t len, int io_flag = 0, int sqe_flag = 0) noexcept; 67 | 68 | static auto callback(io_info* data, int res) noexcept -> void; 69 | }; 70 | 71 | class tcp_close_awaiter : public detail::base_io_awaiter 72 | { 73 | public: 74 | tcp_close_awaiter(int sockfd) noexcept; 75 | 76 | static auto callback(io_info* data, int res) noexcept -> void; 77 | }; 78 | 79 | class tcp_connect_awaiter : public detail::base_io_awaiter 80 | { 81 | public: 82 | tcp_connect_awaiter(int sockfd, const sockaddr* addr, socklen_t addrlen) noexcept; 83 | 84 | static auto callback(io_info* data, int res) noexcept -> void; 85 | }; 86 | }; // namespace tcp 87 | }; // namespace net 88 | 89 | }; // namespace coro::io 90 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveMacros: 'true' 5 | AlignConsecutiveAssignments: 'true' 6 | AlignConsecutiveDeclarations: 'true' 7 | AlignEscapedNewlines: Right 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'true' 10 | AllowAllArgumentsOnNextLine: 'true' 11 | AllowAllConstructorInitializersOnNextLine: 'false' 12 | AllowAllParametersOfDeclarationOnNextLine: 'true' 13 | AllowShortBlocksOnASingleLine: 'true' 14 | AllowShortCaseLabelsOnASingleLine: 'false' 15 | AllowShortFunctionsOnASingleLine: InlineOnly 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: 'false' 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: 'true' 21 | AlwaysBreakTemplateDeclarations: 'Yes' 22 | BinPackArguments: 'false' 23 | BinPackParameters: 'false' 24 | BreakAfterJavaFieldAnnotations: 'true' 25 | BreakBeforeBinaryOperators: None 26 | BreakBeforeBraces: Allman 27 | BreakBeforeTernaryOperators: 'true' 28 | BreakConstructorInitializers: BeforeColon 29 | BreakInheritanceList: BeforeColon 30 | BreakStringLiterals: 'false' 31 | ColumnLimit: '120' 32 | CompactNamespaces: 'false' 33 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 34 | ConstructorInitializerIndentWidth: '4' 35 | ContinuationIndentWidth: '4' 36 | Cpp11BracedListStyle: 'true' 37 | FixNamespaceComments: 'true' 38 | IncludeBlocks: Preserve 39 | IndentCaseLabels: 'true' 40 | IndentPPDirectives: BeforeHash 41 | IndentWidth: '4' 42 | IndentWrappedFunctionNames: 'true' 43 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 44 | Language: Cpp 45 | MaxEmptyLinesToKeep: '1' 46 | NamespaceIndentation: None 47 | PointerAlignment: Left 48 | ReflowComments: 'true' 49 | SortIncludes: 'true' 50 | SortUsingDeclarations: 'true' 51 | SpaceAfterCStyleCast: 'false' 52 | SpaceAfterLogicalNot: 'false' 53 | SpaceAfterTemplateKeyword: 'false' 54 | SpaceBeforeAssignmentOperators: 'true' 55 | SpaceBeforeCpp11BracedList: 'false' 56 | SpaceBeforeCtorInitializerColon: 'true' 57 | SpaceBeforeInheritanceColon: 'true' 58 | SpaceBeforeParens: ControlStatements 59 | SpaceBeforeRangeBasedForLoopColon: 'true' 60 | SpaceInEmptyParentheses: 'false' 61 | SpacesInAngles: 'false' 62 | SpacesInCStyleCastParentheses: 'false' 63 | SpacesInContainerLiterals: 'false' 64 | SpacesInParentheses: 'false' 65 | SpacesInSquareBrackets: 'false' 66 | Standard: Cpp11 67 | UseTab: Never 68 | ... 69 | -------------------------------------------------------------------------------- /src/io/net/tcp/tcp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "coro/io/net/tcp/tcp.hpp" 4 | #include "coro/log.hpp" 5 | #include "coro/utils.hpp" 6 | 7 | namespace coro::io::net::tcp 8 | { 9 | tcp_server::tcp_server(const char* addr, int port) noexcept 10 | { 11 | m_listenfd = socket(AF_INET, SOCK_STREAM, 0); 12 | assert(m_listenfd != -1); 13 | 14 | coro::utils::set_fd_noblock(m_listenfd); 15 | 16 | memset(&m_servaddr, 0, sizeof(m_servaddr)); 17 | m_servaddr.sin_family = AF_INET; 18 | m_servaddr.sin_port = htons(port); 19 | if (addr != nullptr) 20 | { 21 | if (inet_pton(AF_INET, addr, &m_servaddr.sin_addr.s_addr) < 0) 22 | { 23 | log::error("addr invalid"); 24 | std::exit(1); 25 | } 26 | } 27 | else 28 | { 29 | m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 30 | } 31 | 32 | if (bind(m_listenfd, (sockaddr*)&m_servaddr, sizeof(m_servaddr)) != 0) 33 | { 34 | log::error("server bind error"); 35 | std::exit(1); 36 | } 37 | 38 | if (listen(m_listenfd, ::coro::config::kBacklog) != 0) 39 | { 40 | log::error("server listen error"); 41 | std::exit(1); 42 | } 43 | 44 | m_sqe_flag = 0; 45 | m_fixed_fd.assign(m_listenfd, m_sqe_flag); 46 | } 47 | 48 | tcp_accept_awaiter tcp_server::accept(int io_flags) noexcept 49 | { 50 | return tcp_accept_awaiter(m_listenfd, io_flags, m_sqe_flag); 51 | } 52 | 53 | tcp_client::tcp_client(const char* addr, int port) noexcept 54 | { 55 | m_clientfd = socket(AF_INET, SOCK_STREAM, 0); 56 | if (m_clientfd < 0) 57 | { 58 | log::error("clientfd init error"); 59 | std::exit(1); 60 | } 61 | 62 | utils::set_fd_noblock(m_clientfd); 63 | 64 | memset(&m_servaddr, 0, sizeof(m_servaddr)); 65 | m_servaddr.sin_family = AF_INET; 66 | m_servaddr.sin_port = htons(port); 67 | if (addr != nullptr) 68 | { 69 | if (inet_pton(AF_INET, addr, &m_servaddr.sin_addr.s_addr) < 0) 70 | { 71 | log::error("address error"); 72 | std::exit(1); 73 | } 74 | } 75 | else 76 | { 77 | m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 78 | } 79 | } 80 | 81 | tcp_connect_awaiter tcp_client::connect() noexcept 82 | { 83 | return tcp_connect_awaiter(m_clientfd, (sockaddr*)&m_servaddr, sizeof(m_servaddr)); 84 | } 85 | 86 | }; // namespace coro::io::net::tcp 87 | -------------------------------------------------------------------------------- /scripts/CITests.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | # 将各项设置为true来开启对应的测试程序 3 | pre_command: ["make build-tests","make build-benchtests"] 4 | 5 | lab1: 6 | base_tests: 7 | command: ["make test-lab1"] 8 | enable: false 9 | mem_tests: 10 | command: ["make memtest-lab1"] 11 | enable: false 12 | 13 | lab2a: 14 | base_tests: 15 | command: ["make test-lab2a"] 16 | enable: false 17 | mem_tests: 18 | command: ["make memtest-lab2a"] 19 | enable: false 20 | 21 | lab2b: 22 | base_tests: 23 | command: ["make test-lab2b"] 24 | enable: false 25 | mem_tests: 26 | command: ["make memtest-lab2b"] 27 | enable: false 28 | 29 | # lab3 is special, the ci tests of lab3 will be supported later 30 | # lab3: 31 | # base_tests: 32 | # enable: false 33 | 34 | lab4a: 35 | base_tests: 36 | command: ["make test-lab4a"] 37 | enable: false 38 | mem_tests: 39 | command: ["make memtest-lab4a"] 40 | enable: false 41 | bench_tests: 42 | command: ["make benchtest-lab4a"] 43 | enable: false 44 | 45 | lab4b: 46 | base_tests: 47 | command: ["make test-lab4b"] 48 | enable: false 49 | mem_tests: 50 | command: ["make memtest-lab4b"] 51 | enable: false 52 | bench_tests: 53 | command: ["make benchtest-lab4b"] 54 | enable: false 55 | 56 | lab4c: 57 | base_tests: 58 | command: ["make test-lab4c"] 59 | enable: false 60 | mem_tests: 61 | command: ["make memtest-lab4c"] 62 | enable: false 63 | bench_tests: 64 | command: ["make benchtest-lab4c"] 65 | enable: false 66 | 67 | lab4d: 68 | base_tests: 69 | command: ["make test-lab4d"] 70 | enable: false 71 | mem_tests: 72 | command: ["make memtest-lab4d"] 73 | enable: false 74 | bench_tests: 75 | command: ["make benchtest-lab4d"] 76 | enable: false 77 | 78 | lab5a: 79 | base_tests: 80 | command: ["make test-lab5a"] 81 | enable: false 82 | mem_tests: 83 | command: ["make memtest-lab5a"] 84 | enable: false 85 | 86 | lab5b: 87 | base_tests: 88 | command: ["make test-lab5b"] 89 | enable: false 90 | mem_tests: 91 | command: ["make memtest-lab5b"] 92 | enable: false 93 | bench_tests: 94 | command: ["make benchtest-lab5b"] 95 | enable: false 96 | 97 | lab5c: 98 | base_tests: 99 | command: ["make test-lab5c"] 100 | enable: false 101 | mem_tests: 102 | command: ["make memtest-lab5c"] 103 | enable: false 104 | bench_tests: 105 | command: ["make benchtest-lab5c"] 106 | enable: false 107 | -------------------------------------------------------------------------------- /src/context.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/context.hpp" 2 | #include "coro/scheduler.hpp" 3 | 4 | namespace coro 5 | { 6 | context::context() noexcept 7 | { 8 | m_id = ginfo.context_id.fetch_add(1, std::memory_order_relaxed); 9 | } 10 | 11 | auto context::start() noexcept -> void 12 | { 13 | m_job = make_unique( 14 | [this](stop_token token) 15 | { 16 | this->init(); 17 | 18 | // Stop cb is empty means this context is out control of scheduler, 19 | // so it should learn to close itself 20 | if (!(this->m_stop_cb)) 21 | { 22 | m_stop_cb = [&]() { m_job->request_stop(); }; 23 | } 24 | this->run(token); 25 | this->deinit(); 26 | }); 27 | } 28 | 29 | auto context::notify_stop() noexcept -> void 30 | { 31 | m_job->request_stop(); 32 | m_engine.wake_up(); 33 | } 34 | 35 | auto context::set_stop_cb(stop_cb cb) noexcept -> void 36 | { 37 | m_stop_cb = cb; 38 | } 39 | 40 | auto context::init() noexcept -> void 41 | { 42 | linfo.ctx = this; 43 | m_engine.init(); 44 | } 45 | 46 | auto context::deinit() noexcept -> void 47 | { 48 | linfo.ctx = nullptr; 49 | m_engine.deinit(); 50 | } 51 | 52 | auto context::run(stop_token token) noexcept -> void 53 | { 54 | while (!token.stop_requested()) 55 | { 56 | process_work(); 57 | // if (token.stop_requested() && empty_wait_task()) 58 | // { 59 | // if (!m_engine.ready()) 60 | // { 61 | // break; 62 | // } 63 | // else 64 | // { 65 | // continue; 66 | // } 67 | // } 68 | if (empty_wait_task()) 69 | { 70 | if (!m_engine.ready()) 71 | { 72 | m_stop_cb(); 73 | } 74 | else 75 | { 76 | continue; 77 | } 78 | } 79 | 80 | poll_work(); 81 | // if (token.stop_requested() && empty_wait_task() && !m_engine.ready()) 82 | // { 83 | // break; 84 | // } 85 | } 86 | } 87 | 88 | auto context::process_work() noexcept -> void 89 | { 90 | // Why don't use below codes? 91 | // while (m_engine.ready()) 92 | // { 93 | // m_engine.exec_one_task(); 94 | // } 95 | // I want to keep task processed in a fifo order, 96 | // even task queue was added more tasks during process_work, 97 | // just process io task next 98 | 99 | auto num = m_engine.num_task_schedule(); 100 | for (int i = 0; i < num; i++) 101 | { 102 | m_engine.exec_one_task(); 103 | } 104 | } 105 | 106 | }; // namespace coro -------------------------------------------------------------------------------- /include/coro/parallel/parallel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file parallel.hpp 3 | * @author Jiahui Wang 4 | * @brief Convenient and friendly parallel computing functions provided for users. 5 | * @version 1.2 6 | * @date 2025-06-03 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include 14 | 15 | #include "coro/comp/when_all.hpp" 16 | #include "coro/concepts/awaitable.hpp" 17 | #include "coro/concepts/function_traits.hpp" 18 | 19 | namespace coro::parallel 20 | { 21 | namespace detail 22 | { 23 | template 24 | concept parallel_tasks_void_type = 25 | std::ranges::range && ::coro::concepts::awaitable_void>; 26 | 27 | template 28 | concept parallel_tasks_novoid_type = 29 | std::ranges::range && ::coro::concepts::awaitable>; 30 | 31 | template 32 | concept reduce_operator_input = std::ranges::range; 33 | }; // namespace detail 34 | 35 | // notes: reduce func is used to collect result, like mapreduce 36 | 37 | /** 38 | * @brief Parallel calc but no reduce func 39 | * 40 | * @param tasks subtask collections 41 | * @return task 42 | */ 43 | template 44 | auto parallel_func(tasks_type&& tasks) -> task 45 | { 46 | co_await when_all(std::forward(tasks)); 47 | } 48 | 49 | /** 50 | * @brief Convert any funciton object to std::function 51 | * 52 | * @tparam func_type 53 | */ 54 | template 55 | requires(coro::concepts::function_traits::arity == 1) auto make_parallel_reduce_func(func_type func) 56 | { 57 | using std_func_type = std::function::return_type( 58 | typename coro::concepts::function_traits::args<0>::type)>; 59 | return std_func_type(func); 60 | } 61 | 62 | /** 63 | * @brief Parallel calc with reduce func to collect result 64 | * 65 | * @tparam tasks_type 66 | * @tparam input_type: the input parameter of reduce func 67 | * @tparam return_type 68 | * @tparam ::coro::concepts::awaitable_traits>::awaiter_return_type 69 | */ 70 | template< 71 | detail::parallel_tasks_novoid_type tasks_type, 72 | detail::reduce_operator_input input_type, 73 | typename return_type, 74 | typename task_return_type = 75 | typename ::coro::concepts::awaitable_traits>::awaiter_return_type> 76 | requires(std::is_same_v>) auto parallel_func( 77 | tasks_type&& tasks, std::function reduce) -> task 78 | { 79 | auto result = co_await when_all(std::forward(tasks)); 80 | co_return reduce(result); 81 | } 82 | 83 | }; // namespace coro::parallel 84 | -------------------------------------------------------------------------------- /include/coro/concepts/function_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace coro::concepts 6 | { 7 | template 8 | struct function_traits; 9 | 10 | // normal function 11 | template 12 | struct function_traits 13 | { 14 | enum 15 | { 16 | arity = sizeof...(Args) 17 | }; 18 | using return_type = ReturnType; 19 | using function_type = ReturnType(Args...); 20 | using stl_function_type = std::function; 21 | using pointer = ReturnType (*)(Args...); 22 | 23 | template 24 | struct args 25 | { 26 | static_assert(I < arity, "index is out of range, index must less than sizeof Args"); 27 | using type = typename std::tuple_element>::type; 28 | }; 29 | 30 | using tuple_type = std::tuple>...>; 31 | using bare_tuple_type = std::tuple>...>; 32 | }; 33 | 34 | // function pointer 35 | template 36 | struct function_traits : function_traits 37 | { 38 | }; 39 | 40 | // std::function 41 | template 42 | struct function_traits> : function_traits 43 | { 44 | }; 45 | 46 | // member function 47 | #define FUNCTION_TRAITS(...) \ 48 | template \ 49 | struct function_traits : function_traits \ 50 | { \ 51 | }; 52 | 53 | FUNCTION_TRAITS() 54 | FUNCTION_TRAITS(const) 55 | FUNCTION_TRAITS(volatile) 56 | FUNCTION_TRAITS(const volatile) 57 | 58 | // function class 59 | template 60 | struct function_traits : function_traits 61 | { 62 | }; 63 | 64 | template 65 | typename function_traits::stl_function_type to_function(const Function& lambda) 66 | { 67 | return static_cast::stl_function_type>(lambda); 68 | } 69 | 70 | template 71 | typename function_traits::stl_function_type to_function(Function&& lambda) 72 | { 73 | return static_cast::stl_function_type>(std::forward(lambda)); 74 | } 75 | 76 | template 77 | typename function_traits::pointer to_function_pointer(const Function& lambda) 78 | { 79 | return static_cast::pointer>(lambda); 80 | } 81 | 82 | }; // namespace coro::concepts -------------------------------------------------------------------------------- /include/coro/concepts/awaitable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "coro/concepts/common.hpp" 4 | 5 | namespace coro::concepts 6 | { 7 | /** 8 | * This concept declares a type that is required to meet the c++20 coroutine operator 9 | * co_await() retun type. It requires the following three member functions: await_ready() 10 | * -> bool await_suspend(std::coroutine_handle<>) -> void|bool|std::coroutine_handle<> 11 | * await_resume() -> decltype(auto) 12 | * Where the return type on await_resume is the requested return of the 13 | * awaitable. 14 | */ 15 | // clang-format off 16 | template 17 | concept awaiter = requires(type t, std::coroutine_handle<> c) 18 | { 19 | { t.await_ready() } -> std::same_as; 20 | { t.await_suspend(c) } -> in_types>; 21 | { t.await_resume() }; 22 | }; 23 | 24 | template 25 | concept member_co_await_awaitable = requires(type t) 26 | { 27 | { t.operator co_await() } -> awaiter; 28 | }; 29 | 30 | template 31 | concept global_co_await_awaitable = requires(type t) 32 | { 33 | { operator co_await(t) } -> awaiter; 34 | }; 35 | 36 | /** 37 | * This concept declares a type that can be operator co_await()'ed and returns an awaiter_type. 38 | */ 39 | template 40 | concept awaitable = member_co_await_awaitable || global_co_await_awaitable || awaiter; 41 | 42 | template 43 | concept awaiter_void = awaiter && requires(type t) 44 | { 45 | {t.await_resume()} -> std::same_as; 46 | }; 47 | 48 | template 49 | concept member_co_await_awaitable_void = requires(type t) 50 | { 51 | { t.operator co_await() } -> awaiter_void; 52 | }; 53 | 54 | template 55 | concept global_co_await_awaitable_void = requires(type t) 56 | { 57 | { operator co_await(t) } -> awaiter_void; 58 | }; 59 | 60 | template 61 | concept awaitable_void = member_co_await_awaitable_void || global_co_await_awaitable_void || awaiter_void; 62 | 63 | template 64 | concept list_awaiter = awaiter && list_type; 65 | 66 | template 67 | struct awaitable_traits 68 | { 69 | }; 70 | 71 | template 72 | static auto get_awaiter(awaitable&& value) 73 | { 74 | if constexpr (member_co_await_awaitable) 75 | return std::forward(value).operator co_await(); 76 | else if constexpr (global_co_await_awaitable) 77 | return operator co_await(std::forward(value)); 78 | else if constexpr (awaiter) { 79 | return std::forward(value); 80 | } 81 | } 82 | 83 | template 84 | struct awaitable_traits 85 | { 86 | using awaiter_type = decltype(get_awaiter(std::declval())); 87 | using awaiter_return_type = decltype(std::declval().await_resume()); 88 | }; 89 | // clang-format on 90 | 91 | } // namespace coro::concepts 92 | -------------------------------------------------------------------------------- /src/comp/mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/comp/mutex.hpp" 2 | #include "coro/scheduler.hpp" 3 | 4 | namespace coro 5 | { 6 | auto mutex::mutex_awaiter::await_resume() noexcept -> void 7 | { 8 | m_ctx.unregister_wait(); 9 | } 10 | 11 | auto mutex::mutex_awaiter::await_suspend(std::coroutine_handle<> handle) noexcept -> bool 12 | { 13 | m_await_coro = handle; 14 | m_ctx.register_wait(); 15 | return register_lock(); 16 | } 17 | 18 | auto mutex::mutex_awaiter::register_lock() noexcept -> bool 19 | { 20 | while (true) 21 | { 22 | auto state = m_mtx.m_state.load(std::memory_order_acquire); 23 | m_next = nullptr; 24 | if (state == mutex::nolocked) 25 | { 26 | if (m_mtx.m_state.compare_exchange_weak( 27 | state, mutex::locked_no_waiting, std::memory_order_acq_rel, std::memory_order_relaxed)) 28 | { 29 | return false; 30 | } 31 | } 32 | else 33 | { 34 | m_next = reinterpret_cast(state); 35 | if (m_mtx.m_state.compare_exchange_weak( 36 | state, reinterpret_cast(this), std::memory_order_acq_rel, std::memory_order_relaxed)) 37 | { 38 | // m_ctx.register_wait(m_register_state); 39 | // m_register_state = false; 40 | return true; 41 | } 42 | } 43 | } 44 | } 45 | 46 | auto mutex::mutex_awaiter::resume() noexcept -> void 47 | { 48 | m_ctx.submit_task(m_await_coro); 49 | // m_ctx.unregister_wait(); 50 | // m_register_state = true; 51 | } 52 | 53 | auto mutex::try_lock() noexcept -> bool 54 | { 55 | auto target = nolocked; 56 | return m_state.compare_exchange_strong(target, locked_no_waiting, std::memory_order_acq_rel, memory_order_relaxed); 57 | } 58 | 59 | auto mutex::lock() noexcept -> mutex_awaiter 60 | { 61 | return mutex_awaiter(local_context(), *this); 62 | } 63 | 64 | auto mutex::unlock() noexcept -> void 65 | { 66 | assert(m_state.load(std::memory_order_acquire) != nolocked && "unlock the mutex with unlock state"); 67 | 68 | auto to_resume = reinterpret_cast(m_resume_list_head); 69 | if (to_resume == nullptr) 70 | { 71 | auto target = locked_no_waiting; 72 | if (m_state.compare_exchange_strong(target, nolocked, std::memory_order_acq_rel, std::memory_order_relaxed)) 73 | { 74 | return; 75 | } 76 | 77 | auto head = m_state.exchange(locked_no_waiting, std::memory_order_acq_rel); 78 | assert(head != nolocked && head != locked_no_waiting); 79 | 80 | auto awaiter = reinterpret_cast(head); 81 | do 82 | { 83 | auto temp = awaiter->m_next; 84 | awaiter->m_next = to_resume; 85 | to_resume = awaiter; 86 | awaiter = temp; 87 | } while (awaiter != nullptr); 88 | } 89 | 90 | assert(to_resume != nullptr && "unexpected to_resume value: nullptr"); 91 | m_resume_list_head = to_resume->m_next; 92 | to_resume->resume(); 93 | } 94 | 95 | auto mutex::lock_guard() noexcept -> mutex_guard_awaiter 96 | { 97 | return mutex_guard_awaiter(local_context(), *this); 98 | } 99 | }; // namespace coro -------------------------------------------------------------------------------- /tests/lab4c_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "coro/coro.hpp" 8 | #include "gtest/gtest.h" 9 | 10 | using namespace coro; 11 | 12 | /************************************************************* 13 | * pre-definition * 14 | *************************************************************/ 15 | 16 | int main(int argc, char** argv) 17 | { 18 | ::testing::InitGoogleTest(&argc, argv); 19 | return RUN_ALL_TESTS(); 20 | } 21 | 22 | class WaitgroupTest : public ::testing::TestWithParam> 23 | { 24 | protected: 25 | void SetUp() override { m_id = 0; } 26 | 27 | void TearDown() override {} 28 | 29 | wait_group m_wg; 30 | std::atomic m_id; 31 | std::vector m_done_vec; 32 | std::vector m_wait_vec; 33 | }; 34 | 35 | task<> done_func(wait_group& wg, std::atomic& id, int* data) 36 | { 37 | *data = id.fetch_add(1, std::memory_order_acq_rel); 38 | wg.done(); 39 | co_return; 40 | } 41 | 42 | task<> wait_func(wait_group& wg, std::atomic& id, int* data) 43 | { 44 | co_await wg.wait(); 45 | *data = id.fetch_add(1, std::memory_order_acq_rel); 46 | } 47 | 48 | /************************************************************* 49 | * tests * 50 | *************************************************************/ 51 | 52 | TEST_P(WaitgroupTest, DoneAndWait) 53 | { 54 | int thread_num, done_num, wait_num; 55 | std::tie(thread_num, done_num, wait_num) = GetParam(); 56 | 57 | scheduler::init(thread_num); 58 | 59 | m_done_vec = std::vector(done_num, 0); 60 | m_wait_vec = std::vector(wait_num, 0); 61 | 62 | for (int i = 0; i < wait_num; i++) 63 | { 64 | submit_to_scheduler(wait_func(m_wg, m_id, &(m_wait_vec[i]))); 65 | } 66 | 67 | for (int i = 0; i < done_num; i++) 68 | { 69 | m_wg.add(1); 70 | submit_to_scheduler(done_func(m_wg, m_id, &(m_done_vec[i]))); 71 | } 72 | 73 | scheduler::loop(); 74 | 75 | std::sort(m_done_vec.begin(), m_done_vec.end()); 76 | std::sort(m_wait_vec.begin(), m_wait_vec.end()); 77 | 78 | ASSERT_LT(*m_done_vec.rbegin(), *m_wait_vec.begin()); 79 | for (int i = 0; i < done_num; i++) 80 | { 81 | ASSERT_EQ(m_done_vec[i], i); 82 | } 83 | for (int i = 0; i < wait_num; i++) 84 | { 85 | ASSERT_EQ(m_wait_vec[i], i + done_num); 86 | } 87 | } 88 | 89 | INSTANTIATE_TEST_SUITE_P( 90 | WaitgroupTests, 91 | WaitgroupTest, 92 | ::testing::Values( 93 | std::make_tuple(1, 1, 1), 94 | std::make_tuple(1, 1, 100), 95 | std::make_tuple(1, 1, 10000), 96 | std::make_tuple(1, 100, 1), 97 | std::make_tuple(1, 100, 100), 98 | std::make_tuple(1, 100, 10000), 99 | std::make_tuple(0, 1, 1), 100 | std::make_tuple(0, 1, 100), 101 | std::make_tuple(0, 1, 10000), 102 | std::make_tuple(0, 100, 1), 103 | std::make_tuple(0, 100, 100), 104 | std::make_tuple(0, 100, 10000), 105 | std::make_tuple(0, 100, config::kMaxTestTaskNum))); 106 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE_EXTS=".c .h .cpp .hpp .cc .hh .cxx .tcc" 4 | 5 | # Determins if a file has the right extension to be clang-format'ed. 6 | should_clang_format() { 7 | local filename=$(basename "$1") 8 | local extension=".${filename##*.}" 9 | local ext 10 | 11 | local result=0 12 | 13 | # Ignore the test/catch*.hpp file 14 | if [[ "$1" != *"catch"* ]]; then 15 | for ext in $FILE_EXTS; do 16 | # Otherwise, if the extension is in the array of extensions to reformat, echo 1. 17 | [[ "$ext" == "$extension" ]] && result=1 && break 18 | done 19 | fi 20 | 21 | echo $result 22 | } 23 | 24 | # Run the clang-format across the project's changed files. 25 | # for file in $(git diff-index --cached --name-only HEAD); do 26 | # if [ -f "${file}" ] && [ "$(should_clang_format "${file}")" != "0" ] ; then 27 | # echo "clang-format ${file}" 28 | # clang-format -i --style=file "${file}" 29 | # git add "${file}" 30 | # fi 31 | # done 32 | 33 | # Update the README.MD example code with the given macros. 34 | template_contents=$(cat '.githooks/readme-template.MD') 35 | cp .githooks/readme-template.MD README.MD 36 | 37 | template_contents=$(cat 'README.MD') 38 | example_contents=$(cat 'examples/parallel_calc.cpp') 39 | echo "${template_contents/\$\{EXAMPLE_CORO_PARALLEL\}/$example_contents}" > README.MD 40 | 41 | template_contents=$(cat 'README.MD') 42 | example_contents=$(cat 'examples/timer.cpp') 43 | echo "${template_contents/\$\{EXAMPLE_CORO_TIMER\}/$example_contents}" > README.MD 44 | 45 | template_contents=$(cat 'README.MD') 46 | example_contents=$(cat 'examples/event.cpp') 47 | echo "${template_contents/\$\{EXAMPLE_CORO_EVENT\}/$example_contents}" > README.MD 48 | 49 | template_contents=$(cat 'README.MD') 50 | example_contents=$(cat 'examples/latch.cpp') 51 | echo "${template_contents/\$\{EXAMPLE_CORO_LATCH\}/$example_contents}" > README.MD 52 | 53 | template_contents=$(cat 'README.MD') 54 | example_contents=$(cat 'examples/wait_group.cpp') 55 | echo "${template_contents/\$\{EXAMPLE_CORO_WAIT_GROUP\}/$example_contents}" > README.MD 56 | 57 | template_contents=$(cat 'README.MD') 58 | example_contents=$(cat 'examples/mutex.cpp') 59 | echo "${template_contents/\$\{EXAMPLE_CORO_MUTEX\}/$example_contents}" > README.MD 60 | 61 | template_contents=$(cat 'README.MD') 62 | example_contents=$(cat 'examples/when_all.cpp') 63 | echo "${template_contents/\$\{EXAMPLE_CORO_WHEN_ALL\}/$example_contents}" > README.MD 64 | 65 | template_contents=$(cat 'README.MD') 66 | example_contents=$(cat 'examples/cv_notify_one.cpp') 67 | echo "${template_contents/\$\{EXAMPLE_CORO_CONDITION_VARIABLE\}/$example_contents}" > README.MD 68 | 69 | template_contents=$(cat 'README.MD') 70 | example_contents=$(cat 'examples/channel.cpp') 71 | echo "${template_contents/\$\{EXAMPLE_CORO_CHANNEL\}/$example_contents}" > README.MD 72 | 73 | template_contents=$(cat 'README.MD') 74 | example_contents=$(cat 'examples/stdin_client.cpp') 75 | echo "${template_contents/\$\{EXAMPLE_CORO_TCP_STDIN_CLIENT\}/$example_contents}" > README.MD 76 | 77 | template_contents=$(cat 'README.MD') 78 | example_contents=$(cat 'examples/tcp_echo_server.cpp') 79 | echo "${template_contents/\$\{EXAMPLE_CORO_TCP_ECHO_SERVER\}/$example_contents}" > README.MD 80 | 81 | git add README.MD 82 | -------------------------------------------------------------------------------- /tests/lab4b_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "coro/coro.hpp" 8 | #include "gtest/gtest.h" 9 | 10 | using namespace coro; 11 | 12 | /************************************************************* 13 | * pre-definition * 14 | *************************************************************/ 15 | 16 | int main(int argc, char** argv) 17 | { 18 | ::testing::InitGoogleTest(&argc, argv); 19 | return RUN_ALL_TESTS(); 20 | } 21 | 22 | class LatchTest : public ::testing::TestWithParam> 23 | { 24 | protected: 25 | void SetUp() override { m_id = 0; } 26 | 27 | void TearDown() override {} 28 | 29 | std::atomic m_id; 30 | std::vector m_countdown_vec; 31 | std::vector m_wait_vec; 32 | }; 33 | 34 | task<> countdown_func(latch& lt, std::atomic& id, int* data) 35 | { 36 | auto guard = latch_guard{lt}; 37 | *data = id.fetch_add(1, std::memory_order_acq_rel); 38 | co_return; 39 | } 40 | 41 | task<> wait_func(latch& lt, std::atomic& id, int* data) 42 | { 43 | co_await lt.wait(); 44 | *data = id.fetch_add(1, std::memory_order_acq_rel); 45 | } 46 | 47 | /************************************************************* 48 | * tests * 49 | *************************************************************/ 50 | 51 | TEST_P(LatchTest, CountdownAndWait) 52 | { 53 | int thread_num, countdown_num, wait_num; 54 | std::tie(thread_num, countdown_num, wait_num) = GetParam(); 55 | 56 | scheduler::init(thread_num); 57 | 58 | latch lt(countdown_num); 59 | 60 | m_countdown_vec = std::vector(countdown_num, 0); 61 | m_wait_vec = std::vector(wait_num, 0); 62 | 63 | for (int i = 0; i < wait_num; i++) 64 | { 65 | submit_to_scheduler(wait_func(lt, m_id, &(m_wait_vec[i]))); 66 | } 67 | 68 | for (int i = 0; i < countdown_num; i++) 69 | { 70 | submit_to_scheduler(countdown_func(lt, m_id, &(m_countdown_vec[i]))); 71 | } 72 | 73 | scheduler::loop(); 74 | 75 | std::sort(m_countdown_vec.begin(), m_countdown_vec.end()); 76 | std::sort(m_wait_vec.begin(), m_wait_vec.end()); 77 | 78 | ASSERT_LT(*m_countdown_vec.rbegin(), *m_wait_vec.begin()); 79 | for (int i = 0; i < countdown_num; i++) 80 | { 81 | ASSERT_EQ(m_countdown_vec[i], i); 82 | } 83 | for (int i = 0; i < wait_num; i++) 84 | { 85 | ASSERT_EQ(m_wait_vec[i], i + countdown_num); 86 | } 87 | } 88 | 89 | INSTANTIATE_TEST_SUITE_P( 90 | LatchTests, 91 | LatchTest, 92 | ::testing::Values( 93 | std::make_tuple(1, 1, 1), 94 | std::make_tuple(1, 1, 100), 95 | std::make_tuple(1, 1, 10000), 96 | std::make_tuple(1, 100, 1), 97 | std::make_tuple(1, 100, 100), 98 | std::make_tuple(1, 100, 10000), 99 | std::make_tuple(0, 1, 1), 100 | std::make_tuple(0, 1, 100), 101 | std::make_tuple(0, 1, 10000), 102 | std::make_tuple(0, 100, 1), 103 | std::make_tuple(0, 100, 100), 104 | std::make_tuple(0, 100, 10000), 105 | std::make_tuple(0, 100, config::kMaxTestTaskNum))); 106 | -------------------------------------------------------------------------------- /src/comp/condition_variable.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/comp/condition_variable.hpp" 2 | #include "coro/scheduler.hpp" 3 | 4 | namespace coro 5 | { 6 | auto condition_variable::cv_awaiter::await_suspend(std::coroutine_handle<> handle) noexcept -> bool 7 | { 8 | m_await_coro = handle; 9 | return register_lock(); 10 | } 11 | 12 | auto condition_variable::cv_awaiter::await_resume() noexcept -> void 13 | { 14 | m_ctx.unregister_wait(m_suspend_state); 15 | } 16 | 17 | auto condition_variable::cv_awaiter::register_lock() noexcept -> bool 18 | { 19 | if (m_cond && m_cond()) 20 | { 21 | return false; 22 | } 23 | 24 | m_ctx.register_wait(!m_suspend_state); 25 | m_suspend_state = true; 26 | 27 | register_cv(); 28 | m_mtx.unlock(); 29 | return true; 30 | } 31 | 32 | auto condition_variable::cv_awaiter::register_cv() noexcept -> void 33 | { 34 | m_next = nullptr; 35 | 36 | m_cv.m_lock.lock(); 37 | if (m_cv.m_tail == nullptr) 38 | { 39 | m_cv.m_head = m_cv.m_tail = this; 40 | } 41 | else 42 | { 43 | m_cv.m_tail->m_next = this; 44 | m_cv.m_tail = this; 45 | } 46 | m_cv.m_lock.unlock(); 47 | } 48 | 49 | auto condition_variable::cv_awaiter::wake_up() noexcept -> void 50 | { 51 | if (!mutex_awaiter::register_lock()) 52 | { 53 | resume(); 54 | } 55 | } 56 | 57 | auto condition_variable::cv_awaiter::resume() noexcept -> void 58 | { 59 | if (m_cond && !m_cond()) 60 | { 61 | m_ctx.register_wait(!m_suspend_state); 62 | m_suspend_state = true; 63 | 64 | register_cv(); 65 | m_mtx.unlock(); 66 | return; 67 | } 68 | mutex_awaiter::resume(); 69 | } 70 | 71 | condition_variable::~condition_variable() noexcept 72 | { 73 | assert(m_head == nullptr && m_tail == nullptr && "exist sleep awaiter when cv destruct"); 74 | } 75 | 76 | auto condition_variable::wait(mutex& mtx) noexcept -> cv_awaiter 77 | { 78 | return cv_awaiter(local_context(), mtx, *this); 79 | } 80 | 81 | auto condition_variable::wait(mutex& mtx, cond_type&& cond) noexcept -> cv_awaiter 82 | { 83 | return cv_awaiter(local_context(), mtx, *this, cond); 84 | } 85 | 86 | auto condition_variable::wait(mutex& mtx, cond_type& cond) noexcept -> cv_awaiter 87 | { 88 | return cv_awaiter(local_context(), mtx, *this, cond); 89 | } 90 | 91 | // https://stackoverflow.com/questions/17101922/do-i-have-to-acquire-lock-before-calling-condition-variable-notify-one 92 | auto condition_variable::notify_one() noexcept -> void 93 | { 94 | m_lock.lock(); 95 | auto cur = m_head; 96 | if (cur != nullptr) 97 | { 98 | m_head = reinterpret_cast(m_head->m_next); 99 | if (m_head == nullptr) 100 | { 101 | m_tail = nullptr; 102 | } 103 | m_lock.unlock(); 104 | cur->wake_up(); 105 | } 106 | else 107 | { 108 | m_lock.unlock(); 109 | } 110 | } 111 | 112 | auto condition_variable::notify_all() noexcept -> void 113 | { 114 | cv_awaiter* nxt{nullptr}; 115 | 116 | m_lock.lock(); 117 | auto cur_head = m_head; 118 | m_head = m_tail = nullptr; 119 | m_lock.unlock(); 120 | 121 | while (cur_head != nullptr) 122 | { 123 | nxt = reinterpret_cast(cur_head->m_next); 124 | cur_head->wake_up(); 125 | cur_head = nxt; 126 | } 127 | } 128 | } // namespace coro 129 | -------------------------------------------------------------------------------- /include/coro/scheduler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "config.h" 9 | #ifdef ENABLE_MEMORY_ALLOC 10 | #include "coro/allocator/memory.hpp" 11 | #endif 12 | #include "coro/detail/atomic_helper.hpp" 13 | #include "coro/dispatcher.hpp" 14 | 15 | namespace coro 16 | { 17 | 18 | /** 19 | * @brief scheduler just control context to run and stop, 20 | * it also use dispatcher to decide which context can accept the task 21 | * 22 | */ 23 | class scheduler 24 | { 25 | friend context; 26 | using stop_token_type = std::atomic; 27 | using stop_flag_type = std::vector>; 28 | 29 | public: 30 | inline static auto init(size_t ctx_cnt = std::thread::hardware_concurrency()) noexcept -> void 31 | { 32 | if (ctx_cnt == 0) 33 | { 34 | ctx_cnt = std::thread::hardware_concurrency(); 35 | } 36 | get_instance()->init_impl(ctx_cnt); 37 | } 38 | 39 | /** 40 | * @brief loop work, auto wait all context finish job 41 | * 42 | */ 43 | inline static auto loop() noexcept -> void { get_instance()->loop_impl(); } 44 | 45 | static inline auto submit(task&& task) noexcept -> void 46 | { 47 | auto handle = task.handle(); 48 | task.detach(); 49 | submit(handle); 50 | } 51 | 52 | static inline auto submit(task& task) noexcept -> void { submit(task.handle()); } 53 | 54 | inline static auto submit(std::coroutine_handle<> handle) noexcept -> void 55 | { 56 | get_instance()->submit_task_impl(handle); 57 | } 58 | 59 | private: 60 | static auto get_instance() noexcept -> scheduler* 61 | { 62 | static scheduler sc; 63 | return ≻ 64 | } 65 | 66 | auto init_impl(size_t ctx_cnt) noexcept -> void; 67 | 68 | auto start_impl() noexcept -> void; 69 | 70 | auto loop_impl() noexcept -> void; 71 | 72 | auto stop_impl() noexcept -> void; 73 | 74 | auto submit_task_impl(std::coroutine_handle<> handle) noexcept -> void; 75 | 76 | private: 77 | // The number of context 78 | size_t m_ctx_cnt{0}; 79 | detail::ctx_container m_ctxs; 80 | 81 | // Dispatcher control the task dispatch strategys 82 | detail::dispatcher m_dispatcher; 83 | 84 | // This variable includes some atomic flag to indicate the releated context's running state, 85 | // atomic flag equals to 0 means the related context finish all job, 86 | // but atomic flag can be increased again due to the related context receive new task 87 | stop_flag_type m_ctx_stop_flag; 88 | 89 | // When stop_token equals to 0, all context finish all work, so scheduler can stop all context 90 | stop_token_type m_stop_token; 91 | 92 | #ifdef ENABLE_MEMORY_ALLOC 93 | // Memory Allocator 94 | coro::allocator::memory::memory_allocator m_mem_alloc; 95 | #endif 96 | }; 97 | 98 | inline void submit_to_scheduler(task&& task) noexcept 99 | { 100 | scheduler::submit(std::move(task)); 101 | } 102 | 103 | inline void submit_to_scheduler(task& task) noexcept 104 | { 105 | scheduler::submit(task.handle()); 106 | } 107 | 108 | inline void submit_to_scheduler(std::coroutine_handle<> handle) noexcept 109 | { 110 | scheduler::submit(handle); 111 | } 112 | 113 | }; // namespace coro 114 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(GoogleTest) 2 | 3 | file(GLOB_RECURSE TINYCORO_TEST_SOURCES "${PROJECT_SOURCE_DIR}/tests/*test.cpp") 4 | 5 | add_custom_target(build-tests COMMAND ${CMAKE_CTEST_COMMAND} --show-only) 6 | add_custom_target(check-tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose) 7 | 8 | foreach (tinycoro_test_source ${TINYCORO_TEST_SOURCES}) 9 | get_filename_component(tinycoro_test_filename ${tinycoro_test_source} NAME) 10 | string(REPLACE ".cpp" "" tinycoro_test_name ${tinycoro_test_filename}) 11 | add_executable(${tinycoro_test_name} EXCLUDE_FROM_ALL ${tinycoro_test_source}) 12 | add_dependencies(build-tests ${tinycoro_test_name}) 13 | add_dependencies(check-tests ${tinycoro_test_name}) 14 | 15 | gtest_discover_tests(${tinycoro_test_name} 16 | EXTRA_ARGS 17 | --gtest_color=auto 18 | --gtest_output=xml:${CMAKE_BINARY_DIR}/tests/${tinycoro_test_name}.xml 19 | --gtest_catch_exceptions=0 20 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tests 21 | DISCOVERY_TIMEOUT 120 22 | PROPERTIES 23 | TIMEOUT 120 24 | ) 25 | 26 | target_link_libraries(${tinycoro_test_name} ${PROJECT_NAME} gtest) 27 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 28 | target_compile_options(${tinycoro_test_name} PRIVATE "-g") 29 | endif() 30 | if(ENABLE_COMPILE_OPTIMIZE) 31 | target_compile_options(${tinycoro_test_name} PUBLIC -O3) 32 | endif() 33 | 34 | # Set test target properties and dependencies. 35 | set_target_properties(${tinycoro_test_name} 36 | PROPERTIES 37 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" 38 | COMMAND ${tinycoro_test_name} 39 | ) 40 | 41 | string(REPLACE "_test" "" tinycoro_test_command ${tinycoro_test_name}) 42 | add_custom_target(build-${tinycoro_test_command} 43 | COMMAND echo "build ${tinycoro_test_command} test..." 44 | DEPENDS ${tinycoro_test_name} 45 | COMMENT "build ${tinycoro_test_command} tests..." 46 | ) 47 | 48 | add_custom_target(test-${tinycoro_test_command} 49 | COMMAND $ 50 | DEPENDS ${tinycoro_test_name} 51 | COMMENT "Running ${tinycoro_test_command} tests..." 52 | ) 53 | 54 | add_custom_target(memtest-${tinycoro_test_command} 55 | COMMAND bash ${PROJECT_SOURCE_DIR}/scripts/memcheck.sh ${PROJECT_SOURCE_DIR}/temp/valgrind_output.xml $ ${PROJECT_SOURCE_DIR}/temp 56 | COMMAND ${python_command} ${PROJECT_SOURCE_DIR}/scripts/analysis_valgrind.py ${PROJECT_SOURCE_DIR}/temp/valgrind_output.xml 57 | $ 58 | DEPENDS ${tinycoro_test_name} 59 | COMMENT "Running ${tinycoro_test_command} memtests..." 60 | ) 61 | 62 | endforeach () 63 | 64 | # special test for lab3 65 | 66 | add_executable(lab3 EXCLUDE_FROM_ALL lab3.cpp) 67 | add_dependencies(build-tests lab3) 68 | add_dependencies(check-tests lab3) 69 | target_link_libraries(lab3 ${PROJECT_NAME}) 70 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 71 | target_compile_options(lab3 PRIVATE "-g") 72 | endif() 73 | if(ENABLE_COMPILE_OPTIMIZE) 74 | target_compile_options(lab3 PUBLIC -O3) 75 | endif() 76 | set_target_properties(${tinycoro_test_name} 77 | PROPERTIES 78 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" 79 | ) 80 | 81 | add_custom_target(build-lab3 82 | COMMAND echo "build lab3 test..." 83 | DEPENDS lab3 84 | COMMENT "build lab3 tests..." 85 | ) 86 | 87 | add_custom_target(test-lab3 88 | COMMAND ${python_command} ${PROJECT_SOURCE_DIR}/tests/lab3_test.py $ ${PROJECT_SOURCE_DIR}/third_party/rust_echo_bench/target/release/echo_bench 89 | DEPENDS lab3 90 | ) 91 | 92 | add_dependencies(test-lab3 build-benchtools) 93 | 94 | -------------------------------------------------------------------------------- /benchtests/lab4d_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bench_helper.hpp" 6 | #include "benchmark/benchmark.h" 7 | #include "coro/coro.hpp" 8 | 9 | using namespace coro; 10 | 11 | static const int thread_num = std::thread::hardware_concurrency(); 12 | 13 | template 14 | void mutex_bench(const int loop_num); 15 | 16 | /************************************************************* 17 | * threadpool_stl_mutex * 18 | *************************************************************/ 19 | 20 | static void add_tp(std::mutex& mtx, const int loop_num) 21 | { 22 | mtx.lock(); 23 | loop_add; 24 | mtx.unlock(); 25 | } 26 | 27 | static void threadpool_stl_mutex(benchmark::State& state) 28 | { 29 | for (auto _ : state) 30 | { 31 | const int loop_num = state.range(0); 32 | 33 | thread_pool pool; 34 | std::mutex mtx; 35 | size_t cnt = 0; 36 | for (int i = 0; i < thread_num; i++) 37 | { 38 | pool.submit_task([&]() { add_tp(mtx, loop_num); }); 39 | } 40 | 41 | pool.start(); 42 | pool.join(); 43 | } 44 | } 45 | 46 | CORO_BENCHMARK3(threadpool_stl_mutex, 100, 100000, 100000000); 47 | 48 | /************************************************************* 49 | * coro_stl_mutex * 50 | *************************************************************/ 51 | 52 | static task<> add(std::mutex& mtx, const int loop_num) 53 | { 54 | mtx.lock(); 55 | loop_add; 56 | mtx.unlock(); 57 | co_return; 58 | } 59 | 60 | static void coro_stl_mutex(benchmark::State& state) 61 | { 62 | for (auto _ : state) 63 | { 64 | const int loop_num = state.range(0); 65 | mutex_bench(loop_num); 66 | } 67 | } 68 | 69 | CORO_BENCHMARK3(coro_stl_mutex, 100, 100000, 100000000); 70 | 71 | /************************************************************* 72 | * coro_spinlock * 73 | *************************************************************/ 74 | 75 | static task<> add(detail::spinlock& mtx, const int loop_num) 76 | { 77 | mtx.lock(); 78 | loop_add; 79 | mtx.unlock(); 80 | co_return; 81 | } 82 | 83 | static void coro_spinlock(benchmark::State& state) 84 | { 85 | for (auto _ : state) 86 | { 87 | const int loop_num = state.range(0); 88 | mutex_bench(loop_num); 89 | } 90 | } 91 | 92 | CORO_BENCHMARK3(coro_spinlock, 100, 100000, 100000000); 93 | 94 | /************************************************************* 95 | * coro_mutex * 96 | *************************************************************/ 97 | 98 | static task<> add(mutex& mtx, const int loop_num) 99 | { 100 | co_await mtx.lock(); 101 | loop_add; 102 | mtx.unlock(); 103 | } 104 | 105 | static void coro_mutex(benchmark::State& state) 106 | { 107 | for (auto _ : state) 108 | { 109 | const int loop_num = state.range(0); 110 | mutex_bench(loop_num); 111 | } 112 | } 113 | 114 | CORO_BENCHMARK3(coro_mutex, 100, 100000, 100000000); 115 | 116 | BENCHMARK_MAIN(); 117 | 118 | template 119 | void mutex_bench(const int loop_num) 120 | { 121 | scheduler::init(); 122 | 123 | mutex_type mtx; 124 | 125 | for (int i = 0; i < thread_num; i++) 126 | { 127 | submit_to_scheduler(add(mtx, loop_num)); 128 | } 129 | 130 | scheduler::loop(); 131 | } 132 | -------------------------------------------------------------------------------- /include/coro/comp/event.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file event.hpp 3 | * @author JiahuiWang 4 | * @brief lab4a 5 | * @version 1.1 6 | * @date 2025-03-24 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #pragma once 12 | #include 13 | #include 14 | 15 | #include "coro/attribute.hpp" 16 | #include "coro/concepts/awaitable.hpp" 17 | #include "coro/context.hpp" 18 | #include "coro/detail/container.hpp" 19 | #include "coro/detail/types.hpp" 20 | 21 | namespace coro 22 | { 23 | class context; 24 | 25 | namespace detail 26 | { 27 | class event_base 28 | { 29 | public: 30 | struct awaiter_base 31 | { 32 | awaiter_base(context& ctx, event_base& e) noexcept : m_ctx(ctx), m_ev(e) {} 33 | 34 | inline auto next() noexcept -> awaiter_base* { return m_next; } 35 | 36 | auto await_ready() noexcept -> bool; 37 | 38 | auto await_suspend(std::coroutine_handle<> handle) noexcept -> bool; 39 | 40 | auto await_resume() noexcept -> void; 41 | 42 | context& m_ctx; 43 | event_base& m_ev; 44 | awaiter_base* m_next{nullptr}; 45 | std::coroutine_handle<> m_await_coro{nullptr}; 46 | }; 47 | 48 | event_base(bool initial_set = false) noexcept : m_state((initial_set) ? this : nullptr) {} 49 | ~event_base() noexcept = default; 50 | 51 | event_base(const event_base&) = delete; 52 | event_base(event_base&&) = delete; 53 | event_base& operator=(const event_base&) = delete; 54 | event_base& operator=(event_base&&) = delete; 55 | 56 | inline auto is_set() const noexcept -> bool { return m_state.load(std::memory_order_acquire) == this; } 57 | 58 | auto set_state() noexcept -> void; 59 | 60 | auto resume_all_awaiter(awaiter_ptr waiter) noexcept -> void; 61 | 62 | auto register_awaiter(awaiter_base* waiter) noexcept -> bool; 63 | 64 | private: 65 | std::atomic m_state{nullptr}; 66 | }; 67 | }; // namespace detail 68 | 69 | template 70 | class event : public detail::event_base, public detail::container 71 | { 72 | public: 73 | using event_base::event_base; 74 | struct [[CORO_AWAIT_HINT]] awaiter : public detail::event_base::awaiter_base 75 | { 76 | using awaiter_base::awaiter_base; 77 | auto await_resume() noexcept -> decltype(auto) 78 | { 79 | detail::event_base::awaiter_base::await_resume(); 80 | return static_cast(m_ev).result(); 81 | } 82 | }; 83 | 84 | [[CORO_AWAIT_HINT]] awaiter wait() noexcept { return awaiter(local_context(), *this); } 85 | 86 | template 87 | auto set(value_type&& value) noexcept -> void 88 | { 89 | this->return_value(std::forward(value)); 90 | set_state(); 91 | } 92 | }; 93 | 94 | template<> 95 | class event : public detail::event_base 96 | { 97 | public: 98 | using event_base::event_base; 99 | struct [[CORO_AWAIT_HINT]] awaiter : public detail::event_base::awaiter_base 100 | { 101 | using awaiter_base::awaiter_base; 102 | }; 103 | 104 | [[CORO_AWAIT_HINT]] awaiter wait() noexcept { return awaiter(local_context(), *this); } 105 | 106 | auto set() noexcept -> void { set_state(); } 107 | }; 108 | 109 | class event_guard 110 | { 111 | using guard_type = event<>; 112 | 113 | public: 114 | event_guard(guard_type& ev) noexcept : m_ev(ev) {} 115 | ~event_guard() noexcept { m_ev.set(); } 116 | 117 | private: 118 | guard_type& m_ev; 119 | }; 120 | 121 | }; // namespace coro 122 | -------------------------------------------------------------------------------- /benchtests/lab4b_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bench_helper.hpp" 6 | #include "benchmark/benchmark.h" 7 | #include "coro/coro.hpp" 8 | 9 | using namespace coro; 10 | 11 | static const int thread_num = std::thread::hardware_concurrency(); 12 | 13 | template 14 | void latch_bench(const int loop_num); 15 | 16 | /************************************************************* 17 | * threadpool_stl_future * 18 | *************************************************************/ 19 | 20 | void countdown_tp(std::latch& lt, const int loop_num) 21 | { 22 | loop_add; 23 | lt.count_down(); 24 | } 25 | 26 | void wait_tp(std::latch& lt, const int loop_num) 27 | { 28 | lt.wait(); 29 | loop_add; 30 | } 31 | 32 | static void threadpool_stl_latch(benchmark::State& state) 33 | { 34 | for (auto _ : state) 35 | { 36 | const int loop_num = state.range(0); 37 | 38 | const int countdown_num = thread_num / 2; 39 | const int wait_num = thread_num - countdown_num; 40 | 41 | thread_pool pool; 42 | std::latch lt(countdown_num); 43 | 44 | for (int i = 0; i < countdown_num; i++) 45 | { 46 | pool.submit_task([&]() { countdown_tp(lt, loop_num); }); 47 | } 48 | for (int i = 0; i < wait_num; i++) 49 | { 50 | pool.submit_task([&]() { wait_tp(lt, loop_num); }); 51 | } 52 | pool.start(); 53 | pool.join(); 54 | } 55 | } 56 | 57 | CORO_BENCHMARK3(threadpool_stl_latch, 100, 100000, 100000000); 58 | 59 | /************************************************************* 60 | * coro_stl_latch * 61 | *************************************************************/ 62 | 63 | task<> countdown(std::latch& lt, const int loop_num) 64 | { 65 | loop_add; 66 | lt.count_down(); 67 | co_return; 68 | } 69 | 70 | task<> wait(std::latch& lt, const int loop_num) 71 | { 72 | lt.wait(); 73 | loop_add; 74 | co_return; 75 | } 76 | 77 | static void coro_stl_latch(benchmark::State& state) 78 | { 79 | for (auto _ : state) 80 | { 81 | const int loop_num = state.range(0); 82 | latch_bench(loop_num); 83 | } 84 | } 85 | 86 | CORO_BENCHMARK3(coro_stl_latch, 100, 100000, 100000000); 87 | 88 | /************************************************************* 89 | * coro_latch * 90 | *************************************************************/ 91 | 92 | task<> countdown(latch& lt, const int loop_num) 93 | { 94 | loop_add; 95 | lt.count_down(); 96 | co_return; 97 | } 98 | 99 | task<> wait(latch& lt, const int loop_num) 100 | { 101 | co_await lt.wait(); 102 | loop_add; 103 | co_return; 104 | } 105 | 106 | static void coro_latch(benchmark::State& state) 107 | { 108 | for (auto _ : state) 109 | { 110 | const int loop_num = state.range(0); 111 | latch_bench(loop_num); 112 | } 113 | } 114 | 115 | CORO_BENCHMARK3(coro_latch, 100, 100000, 100000000); 116 | 117 | BENCHMARK_MAIN(); 118 | 119 | template 120 | void latch_bench(const int loop_num) 121 | { 122 | const int countdown_num = thread_num / 2; 123 | const int wait_num = thread_num - countdown_num; 124 | 125 | scheduler::init(); 126 | 127 | latch_type lt(countdown_num); 128 | 129 | for (int i = 0; i < countdown_num; i++) 130 | { 131 | submit_to_scheduler(countdown(lt, loop_num)); 132 | } 133 | 134 | for (int i = 0; i < wait_num; i++) 135 | { 136 | submit_to_scheduler(wait(lt, loop_num)); 137 | } 138 | 139 | scheduler::loop(); 140 | } -------------------------------------------------------------------------------- /benchtests/lab4c_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bench_helper.hpp" 6 | #include "benchmark/benchmark.h" 7 | #include "coro/coro.hpp" 8 | 9 | using namespace coro; 10 | 11 | static const int thread_num = std::thread::hardware_concurrency(); 12 | 13 | template 14 | void waitgroup_bench(const int loop_num); 15 | 16 | /************************************************************* 17 | * threadpool_stl_latch * 18 | *************************************************************/ 19 | 20 | void countdown_tp(std::latch& lt, const int loop_num) 21 | { 22 | loop_add; 23 | lt.count_down(); 24 | } 25 | 26 | void wait_tp(std::latch& lt, const int loop_num) 27 | { 28 | lt.wait(); 29 | loop_add; 30 | } 31 | 32 | static void threadpool_stl_latch(benchmark::State& state) 33 | { 34 | for (auto _ : state) 35 | { 36 | const int loop_num = state.range(0); 37 | 38 | const int countdown_num = thread_num / 2; 39 | const int wait_num = thread_num - countdown_num; 40 | 41 | thread_pool pool; 42 | std::latch lt(countdown_num); 43 | 44 | for (int i = 0; i < countdown_num; i++) 45 | { 46 | pool.submit_task([&]() { countdown_tp(lt, loop_num); }); 47 | } 48 | for (int i = 0; i < wait_num; i++) 49 | { 50 | pool.submit_task([&]() { wait_tp(lt, loop_num); }); 51 | } 52 | pool.start(); 53 | pool.join(); 54 | } 55 | } 56 | 57 | CORO_BENCHMARK3(threadpool_stl_latch, 100, 100000, 100000000); 58 | 59 | /************************************************************* 60 | * coro_stl_latch * 61 | *************************************************************/ 62 | 63 | task<> done(std::latch& lt, const int loop_num) 64 | { 65 | loop_add; 66 | lt.count_down(); 67 | co_return; 68 | } 69 | 70 | task<> wait(std::latch& lt, const int loop_num) 71 | { 72 | lt.wait(); 73 | loop_add; 74 | co_return; 75 | } 76 | 77 | static void coro_stl_latch(benchmark::State& state) 78 | { 79 | for (auto _ : state) 80 | { 81 | const int loop_num = state.range(0); 82 | waitgroup_bench(loop_num); 83 | } 84 | } 85 | 86 | CORO_BENCHMARK3(coro_stl_latch, 100, 100000, 100000000); 87 | 88 | /************************************************************* 89 | * coro_waitgroup * 90 | *************************************************************/ 91 | 92 | task<> done(wait_group& wg, const int loop_num) 93 | { 94 | loop_add; 95 | wg.done(); 96 | co_return; 97 | } 98 | 99 | task<> wait(wait_group& wg, const int loop_num) 100 | { 101 | co_await wg.wait(); 102 | loop_add; 103 | co_return; 104 | } 105 | 106 | static void coro_waitgroup(benchmark::State& state) 107 | { 108 | for (auto _ : state) 109 | { 110 | const int loop_num = state.range(0); 111 | waitgroup_bench(loop_num); 112 | } 113 | } 114 | 115 | CORO_BENCHMARK3(coro_waitgroup, 100, 100000, 100000000); 116 | 117 | BENCHMARK_MAIN(); 118 | 119 | template 120 | void waitgroup_bench(const int loop_num) 121 | { 122 | const int done_num = thread_num / 2; 123 | const int wait_num = thread_num - done_num; 124 | 125 | scheduler::init(); 126 | 127 | waitgroup_type wg(done_num); 128 | 129 | for (int i = 0; i < done_num; i++) 130 | { 131 | submit_to_scheduler(done(wg, loop_num)); 132 | } 133 | 134 | for (int i = 0; i < wait_num; i++) 135 | { 136 | submit_to_scheduler(wait(wg, loop_num)); 137 | } 138 | 139 | scheduler::loop(); 140 | } -------------------------------------------------------------------------------- /benchtests/lab4a_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bench_helper.hpp" 6 | #include "benchmark/benchmark.h" 7 | #include "coro/coro.hpp" 8 | 9 | using namespace coro; 10 | 11 | static const int thread_num = std::thread::hardware_concurrency(); 12 | 13 | template 14 | void event_bench(const int loop_num); 15 | 16 | /************************************************************* 17 | * threadpool_stl_future * 18 | *************************************************************/ 19 | 20 | void set_tp(std::promise& promise, const int loop_num) 21 | { 22 | loop_add; 23 | promise.set_value(); 24 | } 25 | 26 | void wait_tp(std::future& future, const int loop_num) 27 | { 28 | future.wait(); 29 | loop_add; 30 | } 31 | 32 | static void threadpool_stl_future(benchmark::State& state) 33 | { 34 | for (auto _ : state) 35 | { 36 | const int loop_num = state.range(0); 37 | thread_pool pool; 38 | 39 | std::promise pro; 40 | auto fut = pro.get_future(); 41 | 42 | for (int i = 0; i < thread_num - 1; i++) 43 | { 44 | pool.submit_task([&]() { wait_tp(fut, loop_num); }); 45 | } 46 | pool.submit_task([&]() { set_tp(pro, loop_num); }); 47 | pool.start(); 48 | pool.join(); 49 | } 50 | } 51 | 52 | CORO_BENCHMARK3(threadpool_stl_future, 100, 100000, 100000000); 53 | 54 | /************************************************************* 55 | * coro_stl_future * 56 | *************************************************************/ 57 | 58 | task<> set(std::promise& promise, const int loop_num) 59 | { 60 | loop_add; 61 | promise.set_value(); 62 | co_return; 63 | } 64 | 65 | task<> wait(std::future& future, const int loop_num) 66 | { 67 | future.wait(); 68 | loop_add; 69 | co_return; 70 | } 71 | 72 | static void coro_stl_future(benchmark::State& state) 73 | { 74 | for (auto _ : state) 75 | { 76 | const int loop_num = state.range(0); 77 | event_bench>(loop_num); 78 | } 79 | } 80 | 81 | CORO_BENCHMARK3(coro_stl_future, 100, 100000, 100000000); 82 | 83 | /************************************************************* 84 | * coro_event * 85 | *************************************************************/ 86 | 87 | task<> set(event<>& ev, const int loop_num) 88 | { 89 | loop_add; 90 | ev.set(); 91 | co_return; 92 | } 93 | 94 | task<> wait(event<>& ev, const int loop_num) 95 | { 96 | co_await ev.wait(); 97 | loop_add; 98 | } 99 | 100 | static void coro_event(benchmark::State& state) 101 | { 102 | for (auto _ : state) 103 | { 104 | const int loop_num = state.range(0); 105 | event_bench>(loop_num); 106 | } 107 | } 108 | 109 | CORO_BENCHMARK3(coro_event, 100, 100000, 100000000); 110 | 111 | BENCHMARK_MAIN(); 112 | 113 | template 114 | void event_bench(const int loop_num) 115 | { 116 | scheduler::init(); 117 | 118 | if constexpr (std::is_same_v>) 119 | { 120 | std::promise pro; 121 | auto fut = pro.get_future(); 122 | 123 | for (int i = 0; i < thread_num - 1; i++) 124 | { 125 | submit_to_scheduler(wait(fut, loop_num)); 126 | } 127 | submit_to_scheduler(set(pro, loop_num)); 128 | 129 | scheduler::loop(); 130 | } 131 | else 132 | { 133 | event_type ev; 134 | 135 | for (int i = 0; i < thread_num - 1; i++) 136 | { 137 | submit_to_scheduler(wait(ev, loop_num)); 138 | } 139 | submit_to_scheduler(set(ev, loop_num)); 140 | 141 | scheduler::loop(); 142 | } 143 | } -------------------------------------------------------------------------------- /benchmark/bench_results.txt: -------------------------------------------------------------------------------- 1 | liburing version:2.10 2 | 3 | wsl2: 4 | 操作系统版本:Ubuntu 22.04.5 LTS 5 | 内核版本:5.15.167.4-microsoft-standard-WSL2 6 | 系统详细信息:Linux DESKTOP-59OORP1 5.15.167.4-microsoft-standard-WSL2 #1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux 7 | cpu信息: 8 | Architecture: x86_64 9 | CPU op-mode(s): 32-bit, 64-bit 10 | Address sizes: 48 bits physical, 48 bits virtual 11 | Byte Order: Little Endian 12 | CPU(s): 16 13 | On-line CPU(s) list: 0-15 14 | Model name: AMD Ryzen 9 7940HS w/ Radeon 780M Graphics 15 | CPU family: 25 16 | Model: 116 17 | Thread(s) per core: 2 18 | Core(s) per socket: 8 19 | Socket(s): 1 20 | Stepping: 1 21 | BogoMIPS: 7985.00 22 | Caches (sum of all): 23 | L1d: 256 KiB (8 instances) 24 | L1i: 256 KiB (8 instances) 25 | L2: 8 MiB (8 instances) 26 | L3: 16 MiB (1 instance) 27 | 内存大小:7.4Gi 28 | 29 | ubuntu: 30 | 操作系统版本:Ubuntu 24.04.2 LTS 31 | 内核版本:6.8.0-55-generic 32 | 系统详细信息:Linux ubuntu 6.8.0-55-generic #57-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 12 23:42:21 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux 33 | cpu信息: 34 | Architecture: x86_64 35 | CPU op-mode(s): 32-bit, 64-bit 36 | Address sizes: 39 bits physical, 48 bits virtual 37 | Byte Order: Little Endian 38 | CPU(s): 12 39 | On-line CPU(s) list: 0-11 40 | Vendor ID: GenuineIntel 41 | Model name: 12th Gen Intel(R) Core(TM) i5-12400 42 | CPU family: 6 43 | Model: 151 44 | Thread(s) per core: 2 45 | Core(s) per socket: 6 46 | Socket(s): 1 47 | Stepping: 5 48 | Caches (sum of all): 49 | L1d: 288 KiB (6 instances) 50 | L1i: 192 KiB (6 instances) 51 | L2: 7.5 MiB (6 instances) 52 | L3: 18 MiB (1 instance) 53 | 内存大小:31Gi 54 | 55 | 56 | 128b-avg(req/s) 57 | tinycoro(wsl2) tinycoro(ubuntu) rust_echo_server(wsl2) rust_echo_server(ubuntu) 58 | 1 15906 81228 18286 88452 59 | 10 129679 595051 144092 701498 60 | 50 1037350 621318 1051087 683328 61 | 100 1102234 661939 1011170 662700 62 | 63 | 1k-avg(req/s) 64 | tinycoro(wsl2) tinycoro(ubuntu) rust_echo_server(wsl2) rust_echo_server(ubuntu) 65 | 1 17082 77259 17885 83227 66 | 10 124883 586725 139094 683537 67 | 50 986684 616243 1000662 669019 68 | 100 1034611 637657 988743 647984 69 | 70 | 16k-avg(req/s) 71 | tinycoro(wsl2) tinycoro(ubuntu) rust_echo_server(wsl2) rust_echo_server(ubuntu) 72 | 1 15991 47988 read error read error 73 | 10 111741 518705 read error read error 74 | 50 820905 464276 read error read error 75 | 100 817026 437072 read error read error -------------------------------------------------------------------------------- /include/coro/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "spdlog/sinks/basic_file_sink.h" 7 | #include "spdlog/spdlog.h" 8 | 9 | #include "config.h" 10 | 11 | namespace coro::log 12 | { 13 | #define CONFIG_LOG_LEVEL(log_level) spdlog::level::log_level 14 | 15 | using std::make_shared; 16 | using std::shared_ptr; 17 | using std::string; 18 | using spdlogger = shared_ptr; 19 | 20 | class logger 21 | { 22 | public: 23 | #ifdef LOGTOFILE 24 | static auto get_logger() noexcept -> spdlogger& 25 | { 26 | static logger log; 27 | return log.m_logger; 28 | }; 29 | #endif 30 | 31 | logger(const logger&) = delete; 32 | logger(logger&&) = delete; 33 | auto operator=(const logger&) -> logger& = delete; 34 | auto operator=(logger&&) -> logger& = delete; 35 | 36 | private: 37 | logger() noexcept 38 | { 39 | #ifdef LOGTOFILE 40 | string log_path = string(SOURCE_DIR) + string(coro::config::kLogFileName); 41 | m_logger = spdlog::create("corolog", log_path.c_str(), true); 42 | m_logger->set_pattern("[%n][%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"); 43 | m_logger->set_level(CONFIG_LOG_LEVEL(LOG_LEVEL)); 44 | spdlog::flush_every(std::chrono::seconds(config::kFlushDura)); 45 | #endif 46 | } 47 | 48 | ~logger() noexcept 49 | { 50 | #ifdef LOGTOFILE 51 | m_logger->flush_on(CONFIG_LOG_LEVEL(LOG_LEVEL)); 52 | #endif 53 | } 54 | 55 | private: 56 | #ifdef LOGTOFILE 57 | spdlogger m_logger; 58 | #endif 59 | }; 60 | 61 | template 62 | inline void trace(const char* __restrict__ fmt, const T&... args) 63 | { 64 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::trace) 65 | { 66 | #ifdef LOGTOFILE 67 | logger::get_logger()->trace(spdlog::fmt_runtime_string{fmt}, args...); 68 | #endif 69 | spdlog::trace(spdlog::fmt_runtime_string{fmt}, args...); 70 | } 71 | } 72 | 73 | template 74 | inline void debug(const char* __restrict__ fmt, const T&... args) 75 | { 76 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::debug) 77 | { 78 | #ifdef LOGTOFILE 79 | logger::get_logger()->debug(spdlog::fmt_runtime_string{fmt}, args...); 80 | #endif 81 | spdlog::debug(spdlog::fmt_runtime_string{fmt}, args...); 82 | } 83 | } 84 | 85 | template 86 | inline void info(const char* __restrict__ fmt, const T&... args) 87 | { 88 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::info) 89 | { 90 | #ifdef LOGTOFILE 91 | logger::get_logger()->info(spdlog::fmt_runtime_string{fmt}, args...); 92 | #endif 93 | spdlog::info(spdlog::fmt_runtime_string{fmt}, args...); 94 | } 95 | } 96 | 97 | template 98 | inline void warn(const char* __restrict__ fmt, const T&... args) 99 | { 100 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::warn) 101 | { 102 | #ifdef LOGTOFILE 103 | logger::get_logger()->warn(spdlog::fmt_runtime_string{fmt}, args...); 104 | #endif 105 | spdlog::warn(spdlog::fmt_runtime_string{fmt}, args...); 106 | } 107 | } 108 | 109 | template 110 | inline void error(const char* __restrict__ fmt, const T&... args) 111 | { 112 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::err) 113 | { 114 | #ifdef LOGTOFILE 115 | logger::get_logger()->error(spdlog::fmt_runtime_string{fmt}, args...); 116 | #endif 117 | spdlog::error(spdlog::fmt_runtime_string{fmt}, args...); 118 | } 119 | } 120 | 121 | template 122 | inline void critical(const char* __restrict__ fmt, const T&... args) 123 | { 124 | if constexpr ((CONFIG_LOG_LEVEL(LOG_LEVEL)) <= spdlog::level::critical) 125 | { 126 | #ifdef LOGTOFILE 127 | logger::get_logger()->critical(spdlog::fmt_runtime_string{fmt}, args...); 128 | #endif 129 | spdlog::critical(spdlog::fmt_runtime_string{fmt}, args...); 130 | } 131 | } 132 | 133 | }; // namespace coro::log -------------------------------------------------------------------------------- /tests/lab3_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | import time 4 | 5 | RED = "\033[31m" 6 | GREEN = "\033[32m" 7 | RESET = "\033[0m" 8 | 9 | # test cases: [(, ) ...] 10 | paras = [(1, 8000), (0, 8001)] 11 | 12 | 13 | def run_test( 14 | thread_num: int, 15 | server_port: int, 16 | benchprogram_path: str, 17 | benchtool_path: str, 18 | ) -> bool: 19 | try: 20 | program_process = subprocess.Popen( 21 | [benchprogram_path, str(thread_num), str(server_port)], 22 | stdout=subprocess.PIPE, 23 | stderr=subprocess.PIPE, 24 | text=True, 25 | ) 26 | time.sleep(2) 27 | if program_process.poll() is not None: # terminate 28 | stdout, stderr = program_process.communicate() 29 | print(f"{RED}[error] {benchprogram_path} start failed{RESET}") 30 | print(f"{RED}[return code] {program_process.returncode}{RESET}") 31 | print(f"{RED}[stdout] {stdout}{RESET}") 32 | print(f"{RED}[stderr] {stderr}{RESET}") 33 | return False 34 | 35 | bench_process = subprocess.Popen( 36 | [ 37 | benchtool_path, 38 | "--address", 39 | f"127.0.0.1:{server_port}", 40 | "--number", 41 | "100", 42 | "--duration", 43 | "30", 44 | "--length", 45 | "1024", 46 | ], 47 | stdout=subprocess.PIPE, 48 | stderr=subprocess.PIPE, 49 | text=True, 50 | ) 51 | 52 | time.sleep(2) 53 | if bench_process.stderr.readline() != "": 54 | print(f"{RED}[error] bench tools running error{RESET}") 55 | bench_process.kill() 56 | _, stderr = bench_process.communicate() 57 | print(f"{RED}[stderr] {stderr}{RESET}") 58 | if program_process.poll() is None: 59 | program_process.kill() 60 | return False 61 | 62 | time.sleep(40) 63 | 64 | # check status 65 | if bench_process.poll() is None: # running 66 | stdout, stderr = program_process.communicate() 67 | print( 68 | f"{RED}[error] {benchtool_path} hasn't finish running, please check your server code{RESET}" 69 | ) 70 | bench_process.kill() 71 | if program_process.poll() is None: 72 | program_process.kill() 73 | return False 74 | 75 | if program_process.poll() is not None: # terminate 76 | stdout, stderr = program_process.communicate() 77 | print(f"{RED}[error] {benchprogram_path} crashed when test{RESET}") 78 | print(f"{RED}[return code] {program_process.returncode}{RESET}") 79 | print(f"{RED}[stdout] {stdout}{RESET}") 80 | print(f"{RED}[stderr] {stderr}{RESET}") 81 | return False 82 | 83 | # collect result 84 | stdout, _ = bench_process.communicate() 85 | print(f"{GREEN}[test result] {stdout}{RESET}") 86 | 87 | program_process.kill() 88 | 89 | except Exception as e: 90 | print(f"{RED}exception occur! detail: {e}{RESET}") 91 | return False 92 | 93 | return True 94 | 95 | 96 | if __name__ == "__main__": 97 | for i, para in enumerate(paras): 98 | print( 99 | f"{GREEN}================== lab3 test case {i} begin =================={RESET}" 100 | ) 101 | thread_num = para[0] 102 | if thread_num == 0: 103 | thread_num = "default core number" 104 | print(f"{GREEN}thread number: {thread_num}, server port: {para[1]}{RESET}") 105 | if not run_test(para[0], para[1], sys.argv[1], sys.argv[2]): 106 | print( 107 | f"{RED}xxxxxxxxxxxxxxxxxx lab3 test case {i} error xxxxxxxxxxxxxxxxxx{RESET}" 108 | ) 109 | sys.exit(1) 110 | else: 111 | print( 112 | f"{GREEN}================== lab3 test case {i} finish ================={RESET}" 113 | ) 114 | time.sleep(2) 115 | print(f"{GREEN}you pass lab3 test, congratulations!{RESET}") 116 | -------------------------------------------------------------------------------- /.github/workflows/ci-ubuntu.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: CMake on ubuntu 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | pull_request: 9 | branches: [ "master" ] 10 | 11 | env: 12 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 13 | BUILD_TYPE: Release 14 | 15 | jobs: 16 | build: 17 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 18 | # You can convert this to a matrix build if you need cross-platform coverage. 19 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Setup Python 26 | uses: actions/setup-python@v5.6.0 27 | with: 28 | # Version range or exact version of Python or PyPy to use, using SemVer's version range syntax. Reads from .python-version if unset. 29 | python-version: 3.12.10 30 | # File containing the Python version to use. Example: .python-version 31 | # python-version-file: # optional 32 | # Used to specify a package manager for caching in the default directory. Supported values: pip, pipenv, poetry. 33 | # cache: # optional 34 | # The target architecture (x86, x64, arm64) of the Python or PyPy interpreter. 35 | architecture: x64 36 | # Set this option if you want the action to check for the latest available version that satisfies the version spec. 37 | # check-latest: # optional 38 | # The token used to authenticate when fetching Python distributions from https://github.com/actions/python-versions. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting. 39 | # token: # optional, default is ${{ github.server_url == 'https://github.com' && github.token || '' }} 40 | # Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies. 41 | # cache-dependency-path: # optional 42 | # Set this option if you want the action to update environment variables. 43 | # update-environment: # optional, default is true 44 | # When 'true', a version range passed to 'python-version' input will match prerelease versions if no GA versions are found. Only 'x.y' version range is supported for CPython. 45 | # allow-prereleases: # optional 46 | # When 'true', use the freethreaded version of Python. 47 | # freethreaded: # optional 48 | 49 | - name: Python deps install 50 | run: pip install pyyaml 51 | 52 | - name: Submodule init 53 | uses: actions/checkout@v4 54 | with: 55 | submodules: true 56 | 57 | - name: Install Liburing 58 | run: | 59 | mkdir temp 60 | cd temp 61 | git clone https://github.com/axboe/liburing.git 62 | cd liburing 63 | ./configure --cc=gcc --cxx=g++ 64 | make -j$(nproc) 65 | make liburing.pc 66 | sudo make install 67 | 68 | - name: Configure CMake 69 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 70 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 71 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 72 | 73 | - name: Build 74 | # Build your program with the given configuration 75 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 76 | 77 | - name: Test 78 | working-directory: ${{github.workspace}}/build 79 | # Execute tests defined by the CMake configuration. 80 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 81 | run: | 82 | cp ${{github.workspace}}/scripts/CITests.py ${{github.workspace}}/build/CITests.py 83 | cp ${{github.workspace}}/scripts/CITests.yml ${{github.workspace}}/build/CITests.yml 84 | python CITests.py 85 | 86 | -------------------------------------------------------------------------------- /tests/lab4a_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "coro/coro.hpp" 8 | #include "gtest/gtest.h" 9 | 10 | using namespace coro; 11 | 12 | /************************************************************* 13 | * pre-definition * 14 | *************************************************************/ 15 | 16 | int main(int argc, char** argv) 17 | { 18 | ::testing::InitGoogleTest(&argc, argv); 19 | return RUN_ALL_TESTS(); 20 | } 21 | 22 | class EventTest : public ::testing::TestWithParam> 23 | { 24 | protected: 25 | void SetUp() override 26 | { 27 | m_id = 0; 28 | m_set = 0; 29 | } 30 | 31 | void TearDown() override {} 32 | 33 | int m_set; 34 | std::vector m_wait_vec; 35 | event<> m_ev; 36 | std::atomic m_id; 37 | }; 38 | 39 | class EventValueTest : public ::testing::TestWithParam> 40 | { 41 | protected: 42 | void SetUp() override {} 43 | 44 | void TearDown() override {} 45 | 46 | std::vector m_wait_vec; 47 | event m_ev; 48 | }; 49 | 50 | task<> set_func(event<>& ev, std::atomic& id, int* p) 51 | { 52 | auto guard = event_guard(ev); 53 | utils::msleep(100); 54 | *p = id.fetch_add(1, std::memory_order_acq_rel) + 1; 55 | co_return; 56 | } 57 | 58 | task<> wait_func(event<>& ev, std::atomic& id, int* p) 59 | { 60 | co_await ev.wait(); 61 | *p = id.fetch_add(1, std::memory_order_acq_rel) + 1; 62 | } 63 | 64 | task<> set_value_func(event& ev, int value) 65 | { 66 | utils::msleep(100); 67 | ev.set(value); 68 | co_return; 69 | } 70 | 71 | task<> wait_value_func(event& ev, int* p) 72 | { 73 | auto val = co_await ev.wait(); 74 | *p = val; 75 | } 76 | 77 | /************************************************************* 78 | * tests * 79 | *************************************************************/ 80 | 81 | TEST_P(EventTest, SetAndWait) 82 | { 83 | int thread_num, wait_num; 84 | std::tie(thread_num, wait_num) = GetParam(); 85 | 86 | scheduler::init(thread_num); 87 | 88 | m_wait_vec = std::vector(wait_num, 0); 89 | 90 | for (int i = 0; i < wait_num; i++) 91 | { 92 | submit_to_scheduler(wait_func(m_ev, m_id, &(m_wait_vec[i]))); 93 | } 94 | 95 | submit_to_scheduler(set_func(m_ev, m_id, &m_set)); 96 | 97 | scheduler::loop(); 98 | 99 | std::sort(m_wait_vec.begin(), m_wait_vec.end()); 100 | ASSERT_EQ(m_set, 1); 101 | for (int i = 0; i < wait_num; i++) 102 | { 103 | ASSERT_EQ(m_wait_vec[i], i + 2); 104 | } 105 | } 106 | 107 | INSTANTIATE_TEST_SUITE_P( 108 | EventTests, 109 | EventTest, 110 | ::testing::Values( 111 | std::make_tuple(1, 1), 112 | std::make_tuple(1, 100), 113 | std::make_tuple(1, 10000), 114 | std::make_tuple(0, 1), 115 | std::make_tuple(0, 100), 116 | std::make_tuple(0, 10000), 117 | std::make_tuple(0, config::kMaxTestTaskNum))); 118 | 119 | TEST_P(EventValueTest, SetValueAndWait) 120 | { 121 | srand(static_cast(std::time(0))); 122 | int randnum = rand(); 123 | 124 | int thread_num, wait_num; 125 | std::tie(thread_num, wait_num) = GetParam(); 126 | 127 | scheduler::init(thread_num); 128 | 129 | m_wait_vec = std::vector(wait_num, 0); 130 | 131 | for (int i = 0; i < wait_num; i++) 132 | { 133 | submit_to_scheduler(wait_value_func(m_ev, &(m_wait_vec[i]))); 134 | } 135 | 136 | submit_to_scheduler(set_value_func(m_ev, randnum)); 137 | 138 | scheduler::loop(); 139 | 140 | std::sort(m_wait_vec.begin(), m_wait_vec.end()); 141 | 142 | for (int i = 0; i < wait_num; i++) 143 | { 144 | ASSERT_EQ(m_wait_vec[i], randnum); 145 | } 146 | } 147 | 148 | INSTANTIATE_TEST_SUITE_P( 149 | EventValueTests, 150 | EventValueTest, 151 | ::testing::Values( 152 | std::make_tuple(1, 1), 153 | std::make_tuple(1, 100), 154 | std::make_tuple(1, 10000), 155 | std::make_tuple(0, 1), 156 | std::make_tuple(0, 100), 157 | std::make_tuple(0, 10000), 158 | std::make_tuple(0, config::kMaxTestTaskNum))); 159 | -------------------------------------------------------------------------------- /src/io/io_awaiter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "coro/io/io_awaiter.hpp" 6 | #include "coro/scheduler.hpp" 7 | 8 | namespace coro::io 9 | { 10 | using ::coro::detail::local_engine; 11 | using detail::io_type; 12 | 13 | noop_awaiter::noop_awaiter() noexcept 14 | { 15 | m_info.type = io_type::nop; 16 | m_info.cb = &noop_awaiter::callback; 17 | 18 | io_uring_prep_nop(m_urs); 19 | io_uring_sqe_set_data(m_urs, &m_info); 20 | local_engine().add_io_submit(); 21 | } 22 | 23 | auto noop_awaiter::callback(io_info* data, int res) noexcept -> void 24 | { 25 | data->result = res; 26 | submit_to_context(data->handle); 27 | } 28 | 29 | stdin_awaiter::stdin_awaiter(char* buf, size_t len, int io_flag, int sqe_flag) noexcept 30 | { 31 | m_info.type = io_type::stdin; 32 | m_info.cb = &stdin_awaiter::callback; 33 | 34 | io_uring_sqe_set_flags(m_urs, sqe_flag); 35 | io_uring_prep_read(m_urs, STDIN_FILENO, buf, len, io_flag); 36 | io_uring_sqe_set_data(m_urs, &m_info); 37 | local_engine().add_io_submit(); 38 | } 39 | 40 | auto stdin_awaiter::callback(io_info* data, int res) noexcept -> void 41 | { 42 | data->result = res; 43 | submit_to_context(data->handle); 44 | } 45 | 46 | namespace net 47 | { 48 | /** 49 | * @brief tcp awaiter 50 | * 51 | */ 52 | namespace tcp 53 | { 54 | tcp_accept_awaiter::tcp_accept_awaiter(int listenfd, int io_flag, int sqe_flag) noexcept 55 | { 56 | m_info.type = io_type::tcp_accept; 57 | m_info.cb = &tcp_accept_awaiter::callback; 58 | 59 | // FIXME: this isn't atomic, maybe cause bug? 60 | io_uring_sqe_set_flags(m_urs, sqe_flag); 61 | io_uring_prep_accept(m_urs, listenfd, nullptr, &len, io_flag); 62 | io_uring_sqe_set_data(m_urs, &m_info); // old uring version need set data after prep 63 | local_engine().add_io_submit(); 64 | } 65 | 66 | auto tcp_accept_awaiter::callback(io_info* data, int res) noexcept -> void 67 | { 68 | data->result = res; 69 | submit_to_context(data->handle); 70 | } 71 | 72 | tcp_read_awaiter::tcp_read_awaiter(int sockfd, char* buf, size_t len, int io_flag, int sqe_flag) noexcept 73 | { 74 | m_info.type = io_type::tcp_read; 75 | m_info.cb = &tcp_read_awaiter::callback; 76 | 77 | io_uring_sqe_set_flags(m_urs, sqe_flag); 78 | io_uring_prep_recv(m_urs, sockfd, buf, len, io_flag); 79 | io_uring_sqe_set_data(m_urs, &m_info); 80 | local_engine().add_io_submit(); 81 | } 82 | 83 | auto tcp_read_awaiter::callback(io_info* data, int res) noexcept -> void 84 | { 85 | data->result = res; 86 | submit_to_context(data->handle); 87 | } 88 | 89 | tcp_write_awaiter::tcp_write_awaiter(int sockfd, char* buf, size_t len, int io_flag, int sqe_flag) noexcept 90 | { 91 | m_info.type = io_type::tcp_write; 92 | m_info.cb = &tcp_write_awaiter::callback; 93 | 94 | io_uring_sqe_set_flags(m_urs, sqe_flag); 95 | io_uring_prep_send(m_urs, sockfd, buf, len, io_flag); 96 | io_uring_sqe_set_data(m_urs, &m_info); 97 | local_engine().add_io_submit(); 98 | } 99 | 100 | auto tcp_write_awaiter::callback(io_info* data, int res) noexcept -> void 101 | { 102 | data->result = res; 103 | submit_to_context(data->handle); 104 | } 105 | 106 | tcp_close_awaiter::tcp_close_awaiter(int sockfd) noexcept 107 | { 108 | m_info.type = io_type::tcp_close; 109 | m_info.cb = &tcp_close_awaiter::callback; 110 | 111 | io_uring_prep_close(m_urs, sockfd); 112 | io_uring_sqe_set_data(m_urs, &m_info); 113 | local_engine().add_io_submit(); 114 | } 115 | 116 | auto tcp_close_awaiter::callback(io_info* data, int res) noexcept -> void 117 | { 118 | data->result = res; 119 | submit_to_context(data->handle); 120 | } 121 | 122 | tcp_connect_awaiter::tcp_connect_awaiter(int sockfd, const sockaddr* addr, socklen_t addrlen) noexcept 123 | { 124 | m_info.type = io_type::tcp_connect; 125 | m_info.cb = &tcp_connect_awaiter::callback; 126 | m_info.data = CASTDATA(sockfd); 127 | 128 | io_uring_prep_connect(m_urs, sockfd, addr, addrlen); 129 | io_uring_sqe_set_data(m_urs, &m_info); 130 | local_engine().add_io_submit(); 131 | } 132 | 133 | auto tcp_connect_awaiter::callback(io_info* data, int res) noexcept -> void 134 | { 135 | if (res != 0) 136 | { 137 | data->result = res; 138 | } 139 | else 140 | { 141 | data->result = static_cast(data->data); 142 | } 143 | submit_to_context(data->handle); 144 | } 145 | }; // namespace tcp 146 | }; // namespace net 147 | 148 | }; // namespace coro::io -------------------------------------------------------------------------------- /src/engine.cpp: -------------------------------------------------------------------------------- 1 | #include "coro/engine.hpp" 2 | #include "coro/io/io_info.hpp" 3 | #include "coro/log.hpp" 4 | #include "coro/task.hpp" 5 | 6 | namespace coro::detail 7 | { 8 | using std::memory_order_relaxed; 9 | 10 | auto engine::init() noexcept -> void 11 | { 12 | linfo.egn = this; 13 | m_num_io_wait_submit = 0; 14 | m_num_io_running = 0; 15 | m_max_recursive_depth = 0; 16 | m_upxy.init(config::kEntryLength); 17 | } 18 | 19 | auto engine::deinit() noexcept -> void 20 | { 21 | m_upxy.deinit(); 22 | m_num_io_wait_submit = 0; 23 | m_num_io_running = 0; 24 | m_max_recursive_depth = 0; 25 | if (!m_task_queue.was_empty()) 26 | { 27 | log::warn("task queue isn't empty when engine deinit"); 28 | } 29 | mpmc_queue> task_queue; 30 | m_task_queue.swap(task_queue); 31 | } 32 | 33 | auto engine::schedule() noexcept -> coroutine_handle<> 34 | { 35 | auto coro = m_task_queue.pop(); 36 | assert(bool(coro)); 37 | return coro; 38 | } 39 | 40 | // TODO: This function is very important to tinyCoro normally run, 41 | // after every update for tinyCoro, this function should be carefully checked! 42 | auto engine::submit_task(coroutine_handle<> handle) noexcept -> void 43 | { 44 | // FIXME: This way will cause stack overflow 45 | assert(handle != nullptr && "engine get nullptr task handle"); 46 | if (m_task_queue.try_push(handle)) 47 | { 48 | wake_up(); 49 | } 50 | else if (is_in_working_state()) 51 | { 52 | // A forced push will cause thread blocked forever, 53 | // so exec this task directly 54 | if (m_max_recursive_depth >= config::kMaxRecursiveDepth) 55 | { 56 | log::error("the recursive depth exceed limit, so the task will be discard"); 57 | return; 58 | } 59 | ++m_max_recursive_depth; 60 | exec_task(handle); 61 | --m_max_recursive_depth; 62 | 63 | // if (is_local_engine()) 64 | // { 65 | // // Engine push task to itself, it will be blocked forever, 66 | // // so exec this task first, which make engine won't block itself 67 | // exec_task(handle); 68 | // } 69 | // else 70 | // { 71 | // // Other thread push task to engine, because engine won't block itself, 72 | // // so just block until engine's task queue has free position 73 | // // This case can also call exec_task(handle) directly 74 | // m_task_queue.push(handle); 75 | // } 76 | } 77 | else 78 | { 79 | log::error("push task out of capacity before work thread run!"); 80 | } 81 | } 82 | 83 | auto engine::exec_one_task() noexcept -> void 84 | { 85 | auto coro = schedule(); 86 | exec_task(coro); 87 | } 88 | 89 | auto engine::exec_task(coroutine_handle<> handle) -> void 90 | { 91 | handle.resume(); 92 | if (handle.done()) 93 | { 94 | clean(handle); 95 | } 96 | } 97 | 98 | auto engine::handle_cqe_entry(urcptr cqe) noexcept -> void 99 | { 100 | auto data = reinterpret_cast(io_uring_cqe_get_data(cqe)); 101 | data->cb(data, cqe->res); 102 | } 103 | 104 | auto engine::do_io_submit() noexcept -> void 105 | { 106 | // int num_task_wait = m_num_io_wait_submit.load(std::memory_order_acquire); 107 | if (m_num_io_wait_submit > 0) 108 | { 109 | // A submit call will submit all io waited. 110 | // If polling mode is enabled, call submit may do nothing, 111 | // but still need to call submit to wake up kernel thread 112 | [[CORO_MAYBE_UNUSED]] auto _ = m_upxy.submit(); 113 | // assert(num_task_wait == 0); 114 | m_num_io_running += m_num_io_wait_submit; 115 | m_num_io_wait_submit = 0; 116 | } 117 | } 118 | 119 | auto engine::poll_submit() noexcept -> void 120 | { 121 | do_io_submit(); 122 | 123 | // TODO: reduce call wait 124 | auto cnt = m_upxy.wait_eventfd(); 125 | if (!wake_by_cqe(cnt)) 126 | { 127 | return; 128 | } 129 | 130 | auto num = m_upxy.peek_batch_cqe(m_urc.data(), m_num_io_running); 131 | 132 | if (num != 0) 133 | { 134 | for (int i = 0; i < num; i++) 135 | { 136 | handle_cqe_entry(m_urc[i]); 137 | } 138 | m_upxy.cq_advance(num); 139 | m_num_io_running -= num; 140 | } 141 | } 142 | 143 | auto engine::wake_up(uint64_t val) noexcept -> void 144 | { 145 | m_upxy.write_eventfd(val); 146 | } 147 | }; // namespace coro::detail 148 | -------------------------------------------------------------------------------- /benchmark/base_model/epoll_server_bench.cpp: -------------------------------------------------------------------------------- 1 | #include //设置非阻塞 2 | #include //控制台输出 3 | #include //socket addr 4 | #include //epoll 5 | #include //创建socket 6 | #include //close函数 7 | 8 | using namespace std; 9 | 10 | int main() 11 | { 12 | const int EVENTS_SIZE = 10240; 13 | // 读socket的数组 14 | char buff[16384 + 16] = {0}; 15 | 16 | // 创建一个tcp socket 17 | int socketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 18 | 19 | // 设置socket监听的地址和端口 20 | sockaddr_in sockAddr{}; 21 | sockAddr.sin_port = htons(8000); 22 | sockAddr.sin_family = AF_INET; 23 | sockAddr.sin_addr.s_addr = htons(INADDR_ANY); 24 | 25 | // 将socket和地址绑定 26 | if (bind(socketFd, (sockaddr*)&sockAddr, sizeof(sockAddr)) == -1) 27 | { 28 | cout << "bind error" << endl; 29 | return -1; 30 | } 31 | 32 | // 开始监听socket,当调用listen之后, 33 | // 进程就可以调用accept来接受一个外来的请求 34 | // 第二个参数,请求队列的长度 35 | if (listen(socketFd, 10) == -1) 36 | { 37 | cout << "listen error" << endl; 38 | return -1; 39 | } 40 | 41 | // 创建一个epoll,size已经不起作用了,一般填1就好了 42 | int eFd = epoll_create(1); 43 | 44 | // 把socket包装成一个epoll_event对象 45 | // 并添加到epoll中 46 | epoll_event epev{}; 47 | epev.events = EPOLLIN; // 可以响应的事件,这里只响应可读就可以了 48 | epev.data.fd = socketFd; // socket的文件描述符 49 | epoll_ctl(eFd, EPOLL_CTL_ADD, socketFd, &epev); // 添加到epoll中 50 | 51 | // 回调事件的数组,当epoll中有响应事件时,通过这个数组返回 52 | epoll_event events[EVENTS_SIZE]; 53 | 54 | // 整个epoll_wait 处理都要在一个死循环中处理 55 | while (true) 56 | { 57 | // 这个函数会阻塞,直到超时或者有响应事件发生 58 | int eNum = epoll_wait(eFd, events, EVENTS_SIZE, -1); 59 | 60 | if (eNum == -1) 61 | { 62 | cout << "epoll_wait" << endl; 63 | return -1; 64 | } 65 | // 遍历所有的事件 66 | for (int i = 0; i < eNum; i++) 67 | { 68 | // 判断这次是不是socket可读(是不是有新的连接) 69 | if (events[i].data.fd == socketFd) 70 | { 71 | if (events[i].events & EPOLLIN) 72 | { 73 | sockaddr_in cli_addr{}; 74 | socklen_t length = sizeof(cli_addr); 75 | // 接受来自socket连接 76 | int fd = accept(socketFd, (sockaddr*)&cli_addr, &length); 77 | if (fd > 0) 78 | { 79 | // 设置响应事件,设置可读和边缘(ET)模式 80 | // 很多人会把可写事件(EPOLLOUT)也注册了,后面会解释 81 | epev.events = EPOLLIN | EPOLLET; 82 | epev.data.fd = fd; 83 | // 设置连接为非阻塞模式 84 | int flags = fcntl(fd, F_GETFL, 0); 85 | if (flags < 0) 86 | { 87 | continue; 88 | } 89 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) 90 | { 91 | continue; 92 | } 93 | // 将新的连接添加到epoll中 94 | epoll_ctl(eFd, EPOLL_CTL_ADD, fd, &epev); 95 | } 96 | } 97 | } 98 | else 99 | { // 不是socket的响应事件 100 | 101 | // 判断是不是断开和连接出错 102 | // 因为连接断开和出错时,也会响应`EPOLLIN`事件 103 | if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) 104 | { 105 | // 出错时,从epoll中删除对应的连接 106 | // 第一个是要操作的epoll的描述符 107 | // 因为是删除,所有event参数天null就可以了 108 | epoll_ctl(eFd, EPOLL_CTL_DEL, events[i].data.fd, nullptr); 109 | close(events[i].data.fd); 110 | } 111 | else if (events[i].events & EPOLLIN) 112 | { // 如果是可读事件 113 | 114 | // 如果在windows中,读socket中的数据要用recv()函数 115 | int len = read(events[i].data.fd, buff, sizeof(buff)); 116 | // 如果读取数据出错,关闭并从epoll中删除连接 117 | if (len == -1) 118 | { 119 | epoll_ctl(eFd, EPOLL_CTL_DEL, events[i].data.fd, nullptr); 120 | close(events[i].data.fd); 121 | } 122 | else 123 | { 124 | // 向客户端发数据 125 | // 如果在windows中,向socket中写数据要用send()函数 126 | [[maybe_unused]] auto p = write(events[i].data.fd, buff, len); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /include/coro/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace coro::utils 10 | { 11 | /** 12 | * @brief set the fd noblock 13 | * 14 | * @param fd 15 | */ 16 | auto set_fd_noblock(int fd) noexcept -> void; 17 | 18 | /** 19 | * @brief Get the nonsense fd, don't forget to close 20 | * 21 | * @return int 22 | */ 23 | auto get_null_fd() noexcept -> int; 24 | 25 | inline auto sleep(int64_t t) noexcept -> void 26 | { 27 | std::this_thread::sleep_for(std::chrono::seconds(t)); 28 | } 29 | 30 | inline auto msleep(int64_t t) noexcept -> void 31 | { 32 | std::this_thread::sleep_for(std::chrono::milliseconds(t)); 33 | } 34 | 35 | inline auto usleep(int64_t t) noexcept -> void 36 | { 37 | std::this_thread::sleep_for(std::chrono::microseconds(t)); 38 | } 39 | 40 | /** 41 | * @brief remove to_trim from s 42 | * 43 | * @param s 44 | * @param to_trim 45 | * @return std::string& 46 | */ 47 | auto trim(std::string& s, const char* to_trim) noexcept -> std::string&; 48 | 49 | /** 50 | * @brief Convert the letters to lowercase 51 | * 52 | * @param c 53 | * @return unsigned char 54 | */ 55 | inline auto to_lower(int c) -> unsigned char 56 | { 57 | const static unsigned char table[256] = { 58 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 59 | 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 60 | 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 61 | 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 62 | 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 63 | 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 64 | 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 65 | 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 66 | 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, 227, 228, 229, 67 | 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 68 | 252, 253, 254, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 69 | 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 70 | }; 71 | return table[(unsigned char)(char)c]; 72 | } 73 | 74 | /** 75 | * @brief Calculate the hash value of string 76 | * 77 | * @warning case insensitive 78 | */ 79 | struct hash 80 | { 81 | auto operator()(const std::string& key) const -> size_t { return hash_core(key.data(), key.size(), 0); } 82 | 83 | auto hash_core(const char* s, size_t l, size_t h) const -> size_t 84 | { 85 | return (l == 0) ? h 86 | : hash_core( 87 | s + 1, 88 | l - 1, 89 | // Unsets the 6 high bits of h, therefore no 90 | // overflow happens 91 | (((std::numeric_limits::max)() >> 6) & h * 33) ^ 92 | static_cast(to_lower(*s))); 93 | } 94 | }; 95 | 96 | /** 97 | * @brief return if a == b 98 | * 99 | * @warning case insensitive 100 | */ 101 | inline auto equal(const std::string& a, const std::string& b) -> bool 102 | { 103 | return a.size() == b.size() && 104 | std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { return to_lower(ca) == to_lower(cb); }); 105 | } 106 | 107 | struct equal_to 108 | { 109 | auto operator()(const std::string& a, const std::string& b) const -> bool { return equal(a, b); } 110 | }; 111 | 112 | /** 113 | * @brief Determine whether a string is a number 114 | * 115 | * @param str 116 | * @return true 117 | * @return false 118 | */ 119 | inline auto is_numeric(const std::string& str) -> bool 120 | { 121 | return !str.empty() && std::all_of(str.cbegin(), str.cend(), [](unsigned char c) { return std::isdigit(c); }); 122 | } 123 | 124 | /** 125 | * @brief Find file extension 126 | * 127 | * @param path 128 | * @return std::string 129 | */ 130 | inline auto file_extension(const std::string& path) -> std::string 131 | { 132 | std::smatch m; 133 | thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$"); 134 | if (std::regex_search(path, m, re)) 135 | { 136 | return m[1].str(); 137 | } 138 | return std::string(); 139 | } 140 | }; // namespace coro::utils -------------------------------------------------------------------------------- /benchmark/base_model/uring_server_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define EVENT_ACCEPT 0 8 | #define EVENT_READ 1 9 | #define EVENT_WRITE 2 10 | 11 | struct conn_info 12 | { 13 | int fd; 14 | int event; 15 | }; 16 | 17 | int init_server(unsigned short port) 18 | { 19 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 20 | struct sockaddr_in serveraddr; 21 | memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 22 | serveraddr.sin_family = AF_INET; 23 | serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 24 | serveraddr.sin_port = htons(port); 25 | 26 | if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) 27 | { 28 | perror("bind"); 29 | return -1; 30 | } 31 | 32 | listen(sockfd, 10); 33 | 34 | return sockfd; 35 | } 36 | 37 | #define ENTRIES_LENGTH 1024 38 | #define BUFFER_LENGTH 1024 39 | 40 | void set_event_recv(struct io_uring* ring, int sockfd, void* buf, size_t len, int flags) 41 | { 42 | struct io_uring_sqe* sqe = io_uring_get_sqe(ring); 43 | 44 | struct conn_info accept_info = { 45 | .fd = sockfd, 46 | .event = EVENT_READ, 47 | }; 48 | 49 | io_uring_prep_recv(sqe, sockfd, buf, len, flags); 50 | memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info)); 51 | } 52 | 53 | void set_event_send(struct io_uring* ring, int sockfd, void* buf, size_t len, int flags) 54 | { 55 | struct io_uring_sqe* sqe = io_uring_get_sqe(ring); 56 | 57 | struct conn_info accept_info = { 58 | .fd = sockfd, 59 | .event = EVENT_WRITE, 60 | }; 61 | 62 | io_uring_prep_send(sqe, sockfd, buf, len, flags); 63 | memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info)); 64 | } 65 | 66 | void set_event_accept(struct io_uring* ring, int sockfd, struct sockaddr* addr, socklen_t* addrlen, int flags) 67 | { 68 | struct io_uring_sqe* sqe = io_uring_get_sqe(ring); 69 | 70 | struct conn_info accept_info = { 71 | .fd = sockfd, 72 | .event = EVENT_ACCEPT, 73 | }; 74 | 75 | io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags); 76 | memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info)); 77 | } 78 | 79 | int main(int argc, char* argv[]) 80 | { 81 | unsigned short port = 9999; 82 | int sockfd = init_server(port); 83 | 84 | struct io_uring_params params; 85 | memset(¶ms, 0, sizeof(params)); 86 | 87 | struct io_uring ring; 88 | io_uring_queue_init_params(ENTRIES_LENGTH, &ring, ¶ms); 89 | 90 | #if 0 91 | struct sockaddr_in clientaddr; 92 | socklen_t len = sizeof(clientaddr); 93 | accept(sockfd, (struct sockaddr 94 | 95 | *)&clientaddr, &len); 96 | #else 97 | 98 | struct sockaddr_in clientaddr; 99 | socklen_t len = sizeof(clientaddr); 100 | set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0); 101 | 102 | #endif 103 | 104 | char buffer[BUFFER_LENGTH] = {0}; 105 | 106 | while (1) 107 | { 108 | io_uring_submit(&ring); 109 | 110 | struct io_uring_cqe* cqe; 111 | io_uring_wait_cqe(&ring, &cqe); 112 | 113 | struct io_uring_cqe* cqes[128]; 114 | int nready = io_uring_peek_batch_cqe(&ring, cqes, 128); // epoll_wait 115 | 116 | int i = 0; 117 | for (i = 0; i < nready; i++) 118 | { 119 | struct io_uring_cqe* entries = cqes[i]; 120 | struct conn_info result; 121 | memcpy(&result, &entries->user_data, sizeof(struct conn_info)); 122 | 123 | if (result.event == EVENT_ACCEPT) 124 | { 125 | set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0); 126 | // printf("set_event_accept\n"); // 127 | 128 | int connfd = entries->res; 129 | 130 | set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0); 131 | } 132 | else if (result.event == EVENT_READ) 133 | { // 134 | 135 | int ret = entries->res; 136 | // printf("set_event_recv ret: %d, %s\n", ret, buffer); // 137 | 138 | if (ret == 0) 139 | { 140 | close(result.fd); 141 | } 142 | else if (ret > 0) 143 | { 144 | set_event_send(&ring, result.fd, buffer, ret, 0); 145 | } 146 | } 147 | else if (result.event == EVENT_WRITE) 148 | { 149 | int ret = entries->res; 150 | // printf("set_event_send ret: %d, %s\n", ret, buffer); 151 | 152 | set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0); 153 | } 154 | } 155 | 156 | io_uring_cq_advance(&ring, nready); 157 | } 158 | 159 | return 0; 160 | } -------------------------------------------------------------------------------- /include/coro/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "config.h" 9 | #include "coro/engine.hpp" 10 | #include "coro/meta_info.hpp" 11 | #include "coro/task.hpp" 12 | 13 | namespace coro 14 | { 15 | using config::ctx_id; 16 | using std::atomic; 17 | using std::jthread; 18 | using std::make_unique; 19 | using std::memory_order_acq_rel; 20 | using std::memory_order_acquire; 21 | using std::memory_order_relaxed; 22 | using std::stop_token; 23 | using std::unique_ptr; 24 | 25 | using detail::ginfo; 26 | using detail::linfo; 27 | 28 | using engine = detail::engine; 29 | 30 | class scheduler; 31 | 32 | /** 33 | * @brief each context own one engine, it's the core part of tinycoro, 34 | * which can process computation task and io task 35 | * 36 | */ 37 | class context 38 | { 39 | using stop_cb = std::function; 40 | 41 | public: 42 | context() noexcept; 43 | ~context() noexcept = default; 44 | context(const context&) = delete; 45 | context(context&&) = delete; 46 | context& operator=(const context&) = delete; 47 | context& operator=(context&&) = delete; 48 | 49 | /** 50 | * @brief work thread start running 51 | * 52 | */ 53 | [[CORO_TEST_USED(lab2b)]] auto start() noexcept -> void; 54 | 55 | /** 56 | * @brief send stop signal to work thread 57 | * 58 | */ 59 | [[CORO_TEST_USED(lab2b)]] auto notify_stop() noexcept -> void; 60 | 61 | /** 62 | * @brief wait work thread stop 63 | * 64 | */ 65 | inline auto join() noexcept -> void { m_job->join(); } 66 | 67 | /** 68 | * @brief Set the stop cb object 69 | * 70 | * @param cb 71 | */ 72 | auto set_stop_cb(stop_cb cb) noexcept -> void; 73 | 74 | inline auto submit_task(task&& task) noexcept -> void 75 | { 76 | auto handle = task.handle(); 77 | task.detach(); 78 | this->submit_task(handle); 79 | } 80 | 81 | inline auto submit_task(task& task) noexcept -> void { submit_task(task.handle()); } 82 | 83 | /** 84 | * @brief submit one task handle to context 85 | * 86 | * @param handle 87 | */ 88 | [[CORO_TEST_USED(lab2b)]] inline auto submit_task(std::coroutine_handle<> handle) noexcept -> void 89 | { 90 | m_engine.submit_task(handle); 91 | } 92 | 93 | /** 94 | * @brief get context unique id 95 | * 96 | * @return ctx_id 97 | */ 98 | inline auto get_ctx_id() noexcept -> ctx_id { return m_id; } 99 | 100 | /** 101 | * @brief add reference count of context 102 | * 103 | * @param register_cnt 104 | */ 105 | [[CORO_TEST_USED(lab2b)]] inline auto register_wait(int register_cnt = 1) noexcept -> void CORO_INLINE 106 | { 107 | m_num_wait_task.fetch_add(size_t(register_cnt), memory_order_acq_rel); 108 | } 109 | 110 | /** 111 | * @brief reduce reference count of context 112 | * 113 | * @param register_cnt 114 | */ 115 | [[CORO_TEST_USED(lab2b)]] inline auto unregister_wait(int register_cnt = 1) noexcept -> void CORO_INLINE 116 | { 117 | m_num_wait_task.fetch_sub(register_cnt, memory_order_acq_rel); 118 | } 119 | 120 | inline auto get_engine() noexcept -> engine& { return m_engine; } 121 | 122 | [[CORO_TEST_USED(lab2b)]] auto init() noexcept -> void; 123 | 124 | [[CORO_TEST_USED(lab2b)]] auto deinit() noexcept -> void; 125 | 126 | /** 127 | * @brief main logic of work thread 128 | * 129 | * @param token 130 | */ 131 | [[CORO_TEST_USED(lab2b)]] auto run(stop_token token) noexcept -> void; 132 | 133 | auto process_work() noexcept -> void; 134 | 135 | inline auto poll_work() noexcept -> void { m_engine.poll_submit(); } 136 | 137 | /** 138 | * @brief return if reference count is zero 139 | * 140 | * @return true 141 | * @return false 142 | */ 143 | [[CORO_TEST_USED(lab2b)]] inline auto empty_wait_task() noexcept -> bool CORO_INLINE 144 | { 145 | return m_num_wait_task.load(memory_order_acquire) == 0 && m_engine.empty_io(); 146 | } 147 | 148 | private: 149 | CORO_ALIGN engine m_engine; 150 | unique_ptr m_job; 151 | ctx_id m_id; 152 | atomic m_num_wait_task{0}; 153 | stop_cb m_stop_cb; 154 | }; 155 | 156 | inline context& local_context() noexcept 157 | { 158 | return *linfo.ctx; 159 | } 160 | 161 | inline void submit_to_context(task&& task) noexcept 162 | { 163 | local_context().submit_task(std::move(task)); 164 | } 165 | 166 | inline void submit_to_context(task& task) noexcept 167 | { 168 | local_context().submit_task(task.handle()); 169 | } 170 | 171 | inline void submit_to_context(std::coroutine_handle<> handle) noexcept 172 | { 173 | local_context().submit_task(handle); 174 | } 175 | 176 | }; // namespace coro 177 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(tinycoro 3 | VERSION 1.2 4 | LANGUAGES CXX 5 | DESCRIPTION "a high performance C++20 coroutine library combined with io_uring" 6 | ) 7 | 8 | # check build dir 9 | get_filename_component(BUILD_DIR_NAME ${CMAKE_BINARY_DIR} NAME) 10 | string(TOLOWER "${BUILD_DIR_NAME}" BUILD_DIR_NAME_LOWER) 11 | if(NOT BUILD_DIR_NAME_LOWER MATCHES "build") 12 | message(FATAL_ERROR 13 | "The build directory must be named include 'build'.\n" 14 | "Current build directory: ${CMAKE_BINARY_DIR}\n" 15 | "Please create a build directory with the correct name and run CMake again." 16 | ) 17 | endif() 18 | 19 | set(CMAKE_CXX_STANDARD 20) 20 | set(CMAKE_CXX_EXTENSIONS OFF) 21 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 22 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 23 | 24 | # TODO: Complete api visiblity 25 | # set(CMAKE_CXX_VISIBILITY_PRESET hidden) 26 | # set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) 27 | 28 | include(GNUInstallDirs) 29 | include(GenerateExportHeader) 30 | include(CMakeDependentOption) 31 | include(cmake/env.cmake) 32 | 33 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY 34 | ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 35 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY 36 | ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) 37 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY 38 | ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) 39 | 40 | set(SRC_INCLUDE_DIR 41 | "${PROJECT_SOURCE_DIR}/include" 42 | "${PROJECT_BINARY_DIR}/include" 43 | "${PROJECT_SOURCE_DIR}/config") 44 | set(THIRD_PARTY_INCLUDE_DIR 45 | "${PROJECT_SOURCE_DIR}/third_party/spdlog/include" 46 | "${PROJECT_SOURCE_DIR}/third_party/googletest/googletest/include" 47 | "${PROJECT_SOURCE_DIR}/third_party/atomic_queue/include" 48 | "${PROJECT_SOURCE_DIR}/third_party/benchmark/include") 49 | 50 | option(ENABLE_UNIT_TESTS "Enable unit tests" ON) 51 | option(ENABLE_DEBUG_MODE "Enable debug mode" OFF) 52 | option(ENABLE_BUILD_SHARED_LIBS "Enable build shared libs" OFF) 53 | cmake_dependent_option(ENABLE_COMPILE_OPTIMIZE "Enable compile options -O3" ON "NOT ENABLE_DEBUG_MODE" OFF) 54 | 55 | set(BUILD_SHARED_LIBS ${ENABLE_BUILD_SHARED_LIBS} CACHE INTERNAL "") 56 | 57 | if(NOT CMAKE_BUILD_TYPE) 58 | if(ENABLE_DEBUG_MODE) 59 | set(CMAKE_BUILD_TYPE Debug) 60 | else() 61 | set(CMAKE_BUILD_TYPE Release) 62 | endif() 63 | elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") 64 | set(ENABLE_DEBUG_MODE ON) 65 | set(ENABLE_COMPILE_OPTIMIZE OFF) 66 | endif() 67 | 68 | 69 | # TODO: add more options 70 | 71 | message(STATUS "${PROJECT_NAME} version: ${PROJECT_VERSION}") 72 | message(STATUS "Complier path: ${CMAKE_CXX_COMPILER}") 73 | message(STATUS "Enable testing: ${ENABLE_UNIT_TESTS}") 74 | message(STATUS "Enable debug mode: ${ENABLE_DEBUG_MODE}") 75 | message(STATUS "Enable build shared libs: ${ENABLE_BUILD_SHARED_LIBS}") 76 | message(STATUS "Enable compile options -O3: ${ENABLE_COMPILE_OPTIMIZE}") 77 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 78 | message(WARNING "The debug mode use -O0(-Og), which cause gcc won't optimize coroutine tail recursion, " 79 | "loop co_await too many times(such as 10w) will cause stack overflow!") 80 | endif() 81 | 82 | # check python version 83 | check_python_command() 84 | if(PYTHON_COMMAND_AVAILABLE) 85 | message(STATUS "python command: ${python_command}") 86 | else() 87 | message(FATAL_ERROR "Can't exec python or python3 or version is lower than 3.7. Please install Python 3.7 or higher.") 88 | endif() 89 | 90 | find_library(URING_PATH 91 | NAMES uring 92 | PATHS /usr/lib 93 | ) 94 | 95 | if (URING_PATH) 96 | message(STATUS "Found liburing at ${URING_PATH}") 97 | else() 98 | message(FATAL_ERROR "Could not find liburing") 99 | endif() 100 | 101 | add_subdirectory(third_party) 102 | add_subdirectory(src) 103 | configure_file(${PROJECT_SOURCE_DIR}/config/config.h.in ${PROJECT_SOURCE_DIR}/config/config.h @ONLY) 104 | 105 | add_library(${PROJECT_NAME} ${TINYCORO_SOURCE_FILES}) 106 | set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) 107 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 108 | target_compile_options(${PROJECT_NAME} PRIVATE "-g") 109 | endif() 110 | if(ENABLE_COMPILE_OPTIMIZE) 111 | target_compile_options(${PROJECT_NAME} PUBLIC -O3) 112 | endif() 113 | target_compile_definitions(${PROJECT_NAME} PRIVATE "$<$:DEBUG>") 114 | target_compile_definitions(${PROJECT_NAME} PRIVATE "$<$:RELEASE>") 115 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) 116 | target_include_directories(${PROJECT_NAME} PUBLIC ${SRC_INCLUDE_DIR} ${THIRD_PARTY_INCLUDE_DIR}) 117 | target_link_libraries(${PROJECT_NAME} PRIVATE ${URING_PATH} pthread) 118 | generate_export_header(${PROJECT_NAME} BASE_NAME CORO EXPORT_FILE_NAME include/coro/export.hpp) 119 | 120 | if(ENABLE_UNIT_TESTS) 121 | enable_testing() 122 | endif() 123 | 124 | add_subdirectory(tests) 125 | add_subdirectory(benchtests) 126 | add_subdirectory(examples) 127 | add_subdirectory(benchmark) 128 | 129 | configure_file(tinycoro.pc.in tinycoro.pc @ONLY) 130 | 131 | # TODO: add install command 132 | # install(TARGETS tinycoro) 133 | # install(DIRECTORY include/coro TYPE INCLUDE) 134 | # install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/coro TYPE INCLUDE) 135 | # install(FILES ${CMAKE_CURRENT_BINARY_DIR}/tinycoro.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 136 | -------------------------------------------------------------------------------- /benchtests/bench_helper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define loop_add \ 8 | size_t cnt{0}; \ 9 | for (int i = 0; i < loop_num; i++) \ 10 | { \ 11 | benchmark::DoNotOptimize(cnt += 1); \ 12 | } 13 | 14 | #define CORO_BENCHMARK(bench_name) \ 15 | BENCHMARK(bench_name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::TimeUnit::kMillisecond) 16 | 17 | #define CORO_BENCHMARK1(bench_name, para) \ 18 | BENCHMARK(bench_name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::TimeUnit::kMillisecond)->Arg(para) 19 | 20 | #define CORO_BENCHMARK2(bench_name, para, para2) \ 21 | BENCHMARK(bench_name) \ 22 | ->MeasureProcessCPUTime() \ 23 | ->UseRealTime() \ 24 | ->Unit(benchmark::TimeUnit::kMillisecond) \ 25 | ->Arg(para) \ 26 | ->Arg(para2) 27 | 28 | #define CORO_BENCHMARK3(bench_name, para, para2, para3) \ 29 | BENCHMARK(bench_name) \ 30 | ->MeasureProcessCPUTime() \ 31 | ->UseRealTime() \ 32 | ->Unit(benchmark::TimeUnit::kMillisecond) \ 33 | ->Arg(para) \ 34 | ->Arg(para2) \ 35 | ->Arg(para3) 36 | 37 | /** 38 | * @brief a simple thread pool, must submit task before start 39 | * 40 | */ 41 | class thread_pool 42 | { 43 | public: 44 | using task_type = std::function; 45 | explicit thread_pool(int thread_num = std::thread::hardware_concurrency()) noexcept 46 | : m_thread_num(thread_num), 47 | m_cur_pos(0) 48 | { 49 | m_ques.resize(m_thread_num); 50 | } 51 | 52 | void start() noexcept 53 | { 54 | for (int i = 0; i < m_thread_num; i++) 55 | { 56 | m_threads.push_back(std::thread([&, i]() { this->run(i); })); 57 | } 58 | } 59 | 60 | void submit_task(task_type task) 61 | { 62 | m_ques[m_cur_pos].push(std::move(task)); 63 | m_cur_pos = (m_cur_pos + 1) % m_thread_num; 64 | } 65 | 66 | void run(int id) noexcept 67 | { 68 | while (!m_ques[id].empty()) 69 | { 70 | auto task = m_ques[id].front(); 71 | m_ques[id].pop(); 72 | task(); 73 | } 74 | } 75 | 76 | void join() 77 | { 78 | for (int i = 0; i < m_thread_num; i++) 79 | { 80 | m_threads[i].join(); 81 | } 82 | } 83 | 84 | private: 85 | const int m_thread_num; 86 | int m_cur_pos; 87 | std::vector> m_ques; 88 | std::vector m_threads; 89 | }; 90 | 91 | inline const char* bench_str = 92 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 93 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 94 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 95 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 96 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 97 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 98 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 99 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 100 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 101 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 102 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 103 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 104 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 105 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 106 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 107 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; -------------------------------------------------------------------------------- /benchtests/lab5b_bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "bench_helper.hpp" 7 | #include "benchmark/benchmark.h" 8 | #include "coro/coro.hpp" 9 | 10 | using namespace coro; 11 | 12 | static const int thread_num = std::thread::hardware_concurrency(); 13 | 14 | /************************************************************* 15 | * notifyall * 16 | *************************************************************/ 17 | 18 | template 19 | void cv_notifyall_bench(const int loop_num); 20 | 21 | void notify_all_tp(std::condition_variable& cv, std::mutex& mtx, int& gid, const int id, const int loop_num) 22 | { 23 | std::unique_lock lck(mtx); 24 | cv.wait(lck, [&]() -> bool { return id == gid; }); 25 | loop_add; 26 | gid += 1; 27 | cv.notify_all(); 28 | mtx.unlock(); 29 | } 30 | 31 | static void threadpool_stl_cv_notifyall(benchmark::State& state) 32 | { 33 | for (auto _ : state) 34 | { 35 | const int loop_num = state.range(0); 36 | 37 | thread_pool pool; 38 | std::condition_variable cv; 39 | std::mutex mtx; 40 | int gid = 0; 41 | 42 | for (int i = thread_num - 1; i >= 0; i--) 43 | { 44 | pool.submit_task([&]() { notify_all_tp(cv, mtx, gid, i, loop_num); }); 45 | } 46 | pool.start(); 47 | pool.join(); 48 | } 49 | } 50 | 51 | CORO_BENCHMARK3(threadpool_stl_cv_notifyall, 100, 100000, 100000000); 52 | 53 | task<> notify_all(std::condition_variable& cv, std::mutex& mtx, int& gid, const int id, const int loop_num) 54 | { 55 | std::unique_lock lck(mtx); 56 | cv.wait(lck, [&]() -> bool { return id == gid; }); 57 | loop_add; 58 | gid += 1; 59 | cv.notify_all(); 60 | mtx.unlock(); 61 | co_return; 62 | } 63 | 64 | static void coro_stl_cv_notifyall(benchmark::State& state) 65 | { 66 | for (auto _ : state) 67 | { 68 | const int loop_num = state.range(0); 69 | cv_notifyall_bench(loop_num); 70 | } 71 | } 72 | 73 | CORO_BENCHMARK3(coro_stl_cv_notifyall, 100, 100000, 100000000); 74 | 75 | task<> notify_all(condition_variable& cv, mutex& mtx, int& gid, const int id, const int loop_num) 76 | { 77 | co_await mtx.lock_guard(); 78 | cv.wait(mtx, [&]() { return gid == id; }); 79 | cv.notify_all(); 80 | } 81 | 82 | static void coro_cv_notifyall(benchmark::State& state) 83 | { 84 | for (auto _ : state) 85 | { 86 | const int loop_num = state.range(0); 87 | cv_notifyall_bench(loop_num); 88 | } 89 | } 90 | 91 | CORO_BENCHMARK3(coro_cv_notifyall, 100, 100000, 100000000); 92 | 93 | /************************************************************* 94 | * notifyone * 95 | *************************************************************/ 96 | const int notifyone_run_cnt = 1000; 97 | 98 | template 99 | void cv_notifyone_bench(); 100 | 101 | void notify_one_tp(std::condition_variable& cv, std::mutex& mtx, int& gid, const int id, int run_cnt) 102 | { 103 | while (run_cnt > 0) 104 | { 105 | std::unique_lock lck(mtx); 106 | cv.wait(lck, [&]() -> bool { return id == gid; }); 107 | gid = (gid + 1) % 2; 108 | run_cnt -= 1; 109 | cv.notify_one(); 110 | } 111 | } 112 | 113 | static void threadpool_stl_cv_notifyone(benchmark::State& state) 114 | { 115 | for (auto _ : state) 116 | { 117 | thread_pool pool; 118 | std::condition_variable cv; 119 | std::mutex mtx; 120 | int gid = 0; 121 | 122 | for (int i = 0; i < 2; i++) 123 | { 124 | pool.submit_task([&]() { notify_one_tp(cv, mtx, gid, i, notifyone_run_cnt); }); 125 | } 126 | pool.start(); 127 | pool.join(); 128 | } 129 | } 130 | 131 | CORO_BENCHMARK(threadpool_stl_cv_notifyone); 132 | 133 | task<> notify_one(std::condition_variable& cv, std::mutex& mtx, int& gid, const int id, int run_cnt) 134 | { 135 | while (run_cnt > 0) 136 | { 137 | std::unique_lock lck(mtx); 138 | cv.wait(lck, [&]() -> bool { return id == gid; }); 139 | gid = (gid + 1) % 2; 140 | run_cnt -= 1; 141 | cv.notify_one(); 142 | } 143 | co_return; 144 | } 145 | 146 | static void coro_stl_cv_notifyone(benchmark::State& state) 147 | { 148 | for (auto _ : state) 149 | { 150 | cv_notifyone_bench(); 151 | } 152 | } 153 | 154 | CORO_BENCHMARK(coro_stl_cv_notifyone); 155 | 156 | task<> notify_one(condition_variable& cv, mutex& mtx, int& gid, const int id, int run_cnt) 157 | { 158 | while (run_cnt > 0) 159 | { 160 | co_await mtx.lock_guard(); 161 | cv.wait(mtx, [&]() { return id == gid; }); 162 | gid = (gid + 1) % 2; 163 | run_cnt -= 1; 164 | cv.notify_one(); 165 | } 166 | } 167 | 168 | static void coro_cv_notifyone(benchmark::State& state) 169 | { 170 | for (auto _ : state) 171 | { 172 | cv_notifyone_bench(); 173 | } 174 | } 175 | 176 | CORO_BENCHMARK(coro_cv_notifyone); 177 | 178 | BENCHMARK_MAIN(); 179 | 180 | template 181 | void cv_notifyall_bench(const int loop_num) 182 | { 183 | scheduler::init(); 184 | 185 | cv_type cv; 186 | mtx_type mtx; 187 | 188 | int gid = 0; 189 | 190 | for (int i = thread_num - 1; i >= 0; i--) 191 | { 192 | submit_to_scheduler(notify_all(cv, mtx, gid, i, loop_num)); 193 | } 194 | 195 | scheduler::loop(); 196 | } 197 | 198 | template 199 | void cv_notifyone_bench() 200 | { 201 | scheduler::init(); 202 | 203 | cv_type cv; 204 | mtx_type mtx; 205 | 206 | int gid = 0; 207 | 208 | for (int i = 0; i < 2; i++) 209 | { 210 | submit_to_scheduler(notify_one(cv, mtx, gid, i, notifyone_run_cnt)); 211 | } 212 | 213 | scheduler::loop(); 214 | } -------------------------------------------------------------------------------- /include/coro/timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #ifndef _SYS_TIME_H 5 | #define _SYS_TIME_H 6 | #include 7 | #endif 8 | 9 | #include "coro/attribute.hpp" 10 | #include "coro/concepts/common.hpp" 11 | #include "coro/io/base_awaiter.hpp" 12 | 13 | // TODO: Add time bias: timeout_bias_nanosecond 14 | namespace coro::time 15 | { 16 | 17 | using coro_system_clock = std::chrono::system_clock; 18 | using coro_steady_clock = std::chrono::steady_clock; 19 | using coro_high_resolution_clock = std::chrono::high_resolution_clock; 20 | 21 | #define timeout_abs IORING_TIMEOUT_ABS 22 | #define timeout_boottime IORING_TIMEOUT_BOOTTIME 23 | #define timeout_realtime IORING_TIMEOUT_REALTIME 24 | #define timeout_monotonic 0 25 | #define timeout_etime_success IORING_TIMEOUT_ETIME_SUCCESS 26 | #define timeout_multishot IORING_TIMEOUT_MULTISHOT 27 | 28 | namespace detail 29 | { 30 | /** 31 | * @brief Get the kernel timespec object from std::duration 32 | * 33 | * @tparam Rep: the time period type 34 | * @tparam Period: time period 35 | * @param time_duration 36 | * @return __kernel_timespec 37 | */ 38 | template 39 | inline auto get_kernel_timespec(std::chrono::duration time_duration) -> __kernel_timespec 40 | { 41 | auto seconds = std::chrono::duration_cast(time_duration); 42 | auto nanoseconds = std::chrono::duration_cast(time_duration - seconds); 43 | return __kernel_timespec{.tv_sec = seconds, .tv_nsec = nanoseconds}; 44 | } 45 | 46 | /** 47 | * @brief Get the kernel timespec object from std::time_point 48 | * 49 | * @tparam Clock: only support [system_clock|steady_clock|high_resolution_clock] 50 | * @tparam Duration: C++ std::duration 51 | * @param time_point 52 | * @return __kernel_timespec 53 | */ 54 | template 55 | requires(coro::concepts::in_types< 56 | Clock, 57 | coro_system_clock, 58 | coro_steady_clock, 59 | coro_high_resolution_clock>) inline auto get_kernel_timespec(std::chrono::time_point 60 | time_point) -> __kernel_timespec 61 | { 62 | return get_kernel_timespec(time_point.time_since_epoch()); 63 | } 64 | 65 | }; // namespace detail 66 | 67 | using ::coro::detail::local_engine; 68 | using coro::io::detail::io_info; 69 | using coro::io::detail::io_type; 70 | 71 | /** 72 | * @brief A timer can be co_await and support chained call 73 | * 74 | * @warning The construct flag donesn't work, so just keep default, 75 | * later I will fix it 76 | * 77 | * @note The construct flag only support [timeout_boottime|timeout_realtime|timeout_monotonic], 78 | * which will choose different clock, the default value is timeout_boottime, which mean boottime clock 79 | * is used, just keep the value default 80 | * 81 | * @note More details abut flag please see https://man7.org/linux/man-pages/man3/io_uring_prep_timeout.3.html 82 | */ 83 | class timer 84 | { 85 | struct timer_awaiter : coro::io::detail::base_io_awaiter 86 | { 87 | timer_awaiter(__kernel_timespec ts, int count, unsigned flags) noexcept 88 | { 89 | m_info.type = io_type::timer; 90 | m_info.cb = &timer_awaiter::callback; 91 | m_ts = ts; 92 | 93 | io_uring_prep_timeout(m_urs, &m_ts, count, flags); 94 | io_uring_sqe_set_data(m_urs, &m_info); 95 | local_engine().add_io_submit(); 96 | } 97 | static auto callback(io_info* data, int res) noexcept -> void 98 | { 99 | // ignore timeout error 100 | if (res == -ETIME) 101 | { 102 | res = 0; 103 | } 104 | data->result = res; 105 | submit_to_context(data->handle); 106 | } 107 | 108 | // avoid race conddition, so copy ts 109 | __kernel_timespec m_ts; 110 | }; 111 | 112 | public: 113 | explicit timer(unsigned flag = timeout_monotonic) noexcept : m_flag(flag) {} 114 | 115 | /** 116 | * @brief add seconds 117 | * 118 | * @param seconds 119 | * @return timer& 120 | */ 121 | CORO_INLINE auto add_seconds(uint64_t seconds) -> timer& 122 | { 123 | m_ts.tv_sec += seconds; 124 | return *this; 125 | } 126 | 127 | /** 128 | * @brief add millseconds 129 | * 130 | * @param mseconds 131 | * @return timer& 132 | */ 133 | CORO_INLINE auto add_mseconds(uint64_t mseconds) -> timer& 134 | { 135 | m_ts.tv_nsec += (1000000 * mseconds); 136 | return *this; 137 | } 138 | 139 | /** 140 | * @brief add microseconds 141 | * 142 | * @param useconds 143 | * @return timer& 144 | */ 145 | CORO_INLINE auto add_useconds(uint64_t useconds) -> timer& 146 | { 147 | m_ts.tv_nsec += (1000 * useconds); 148 | return *this; 149 | } 150 | 151 | /** 152 | * @brief add nanoseconds 153 | * 154 | * @param nseconds 155 | * @return timer& 156 | */ 157 | CORO_INLINE auto add_nseconds(uint64_t nseconds) -> timer& 158 | { 159 | m_ts.tv_nsec += nseconds; 160 | return *this; 161 | } 162 | 163 | template 164 | auto set_by_duration(std::chrono::duration time_duration) -> timer& 165 | { 166 | m_ts = detail::get_kernel_timespec(time_duration); 167 | return *this; 168 | } 169 | 170 | /** 171 | * @brief set timer by timepoint 172 | * 173 | * @tparam Clock: only support [system_clock|steady_clock|high_resolution_clock] 174 | * @tparam Duration 175 | */ 176 | template 177 | requires(coro::concepts::in_types< 178 | Clock, 179 | coro_system_clock, 180 | coro_steady_clock, 181 | coro_high_resolution_clock>) auto set_by_timepoint(std::chrono::time_point time_point) 182 | -> timer& 183 | { 184 | m_ts = detail::get_kernel_timespec(time_point); 185 | return *this; 186 | } 187 | 188 | auto operator co_await() noexcept 189 | { 190 | // set count to 1 means we just want to produce 1 cqe 191 | return timer_awaiter(m_ts, 1, 0); 192 | } 193 | 194 | private: 195 | __kernel_timespec m_ts; 196 | unsigned m_flag{0}; 197 | }; 198 | 199 | }; // namespace coro::time 200 | --------------------------------------------------------------------------------