diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..609921a --- /dev/null +++ b/.clang-format @@ -0,0 +1,67 @@ +--- +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveMacros: 'true' +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'true' +AlignEscapedNewlines: Right +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'true' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'true' +AllowShortBlocksOnASingleLine: 'true' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakAfterJavaFieldAnnotations: 'true' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: 'false' +ColumnLimit: '120' +CompactNamespaces: 'false' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +ConstructorInitializerIndentWidth: '4' +ContinuationIndentWidth: '4' +Cpp11BracedListStyle: 'true' +FixNamespaceComments: 'true' +IncludeBlocks: Preserve +IndentCaseLabels: 'true' +IndentPPDirectives: BeforeHash +IndentWidth: '4' +IndentWrappedFunctionNames: 'true' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +Language: Cpp +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: None +PointerAlignment: Left +ReflowComments: 'true' +SortIncludes: 'true' +SortUsingDeclarations: 'true' +SpaceAfterCStyleCast: 'false' +SpaceAfterLogicalNot: 'false' +SpaceAfterTemplateKeyword: 'false' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'false' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Cpp11 +UseTab: Never +... diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a22774c --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +.DEFAULT_GOAL := debug + +# Builds the project and tests in the Debug directory. +debug: + @$(MAKE) compile BUILD_TYPE=Debug --no-print-directory + +# Builds the project and tests in the RelWithDebInfo directory. +release-with-debug-info: + @$(MAKE) compile BUILD_TYPE=RelWithDebInfo --no-print-directory + +# Builds the project and tests in the Release directory. +release: + @$(MAKE) compile BUILD_TYPE=Release --no-print-directory + +# Internal target for all build targets to call. +compile: + mkdir -p ${BUILD_TYPE}; \ + cd ${BUILD_TYPE}; \ + cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ..; \ + cmake --build . -- -j $(nproc) + +# Run Debug tests. +debug-test: + @$(MAKE) test BUILD_TYPE=Debug --no-print-directory + +# Run RelWithDebInfo tests. +release-with-debug-info-test: + @$(MAKE) test BUILD_TYPE=RelWithDebInfo --no-print-directory + +# Run Release tests. +release-test: + @$(MAKE) test BUILD_TYPE=Release --no-print-directory + +# Internal target for all test targets to call. +.PHONY: test +test: + cd ${BUILD_TYPE}; \ + ctest -VV + +# Cleans all build types. +.PHONY: clean +clean: + rm -rf Debug + rm -rf RelWithDebInfo + rm -rf Release + +# Runs clang-format with the project's .clang-format. +format: + # Inlcude *.hpp|*.h|*.cpp but ignore catch lib as well as RelWithDebInfo|Release|Debug|build + find . \( -name '*.hpp' -or -name '*.h' -or -name '*.cpp' \) \ + -and -not -name '*catch*' \ + -and -not -iwholename '*/RelWithDebInfo/*' \ + -and -not -iwholename '*/Release/*' \ + -and -not -iwholename '*/Debug/*' \ + -and -not -iwholename '*/build/*' \ + -exec clang-format -i --style=file {} \; diff --git a/README.md b/README.md index 0f85ae1..44221a1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ libcoro C++20 Coroutines [![language][badge.language]][language] [![license][badge.license]][license] -[badge.language]: https://img.shields.io/badge/language-C%2B%2B17-yellow.svg +[badge.language]: https://img.shields.io/badge/language-C%2B%2B20-yellow.svg [badge.license]: https://img.shields.io/badge/license-Apache--2.0-blue [language]: https://en.wikipedia.org/wiki/C%2B%2B17 @@ -19,3 +19,20 @@ Libcoro is a C++20 coroutine library. So far most inspiration has been gleaned # Goal Libcoro is currently more of a learning experience for myself but ultimately I'd like to turn this into a great linux coroutine base library with an easy to use HTTP scheduler/server. + +# Building +There is a root makefile with various commands to help make building and running tests on this project easier. + +```bash +# Build targets +make debug|release-with-debug-info|release + +# Run tests targets +make debug-test|release-with-debug-info-tests|release-tests + +# Clean all builds. +make clean + +# clang-format the code +make format +``` diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/Testing/Temporary/LastTest.log b/Testing/Temporary/LastTest.log new file mode 100644 index 0000000..de83ee1 --- /dev/null +++ b/Testing/Temporary/LastTest.log @@ -0,0 +1,3 @@ +Start testing: Oct 12 14:19 MST +---------------------------------------------------------- +End testing: Oct 12 14:19 MST diff --git a/inc/coro/scheduler.hpp b/inc/coro/scheduler.hpp index 4950eee..86826bc 100644 --- a/inc/coro/scheduler.hpp +++ b/inc/coro/scheduler.hpp @@ -162,19 +162,19 @@ public: auto operator=(const resume_token&) -> resume_token& = delete; auto operator=(resume_token&&) -> resume_token& = default; - auto resume(return_type result) noexcept -> void; + auto resume(return_type value) noexcept -> void; - auto result() const & -> const return_type& + auto return_value() const & -> const return_type& { - return m_result; + return m_return_value; } - auto result() && -> return_type&& + auto return_value() && -> return_type&& { - return std::move(m_result); + return std::move(m_return_value); } private: - return_type m_result; + return_type m_return_value; }; template<> @@ -578,7 +578,7 @@ public: } else { - co_return token.result(); + co_return token.return_value(); } } @@ -758,7 +758,7 @@ private: } else { - co_return token.result(); + co_return token.return_value(); } } @@ -935,12 +935,12 @@ private: }; template -inline auto resume_token::resume(return_type result) noexcept -> void +inline auto resume_token::resume(return_type value) noexcept -> void { void* old_value = m_state.exchange(this, std::memory_order::acq_rel); if(old_value != this) { - m_result = std::move(result); + m_return_value = std::move(value); auto* waiters = static_cast(old_value); while(waiters != nullptr) diff --git a/inc/coro/sync_wait.hpp b/inc/coro/sync_wait.hpp index e0ab584..3285e2c 100644 --- a/inc/coro/sync_wait.hpp +++ b/inc/coro/sync_wait.hpp @@ -13,7 +13,7 @@ auto sync_wait(task_type&& task) -> decltype(auto) { task.resume(); } - return task.promise().result(); + return task.promise().return_value(); } template diff --git a/inc/coro/task.hpp b/inc/coro/task.hpp index 2f1a375..3463bb8 100644 --- a/inc/coro/task.hpp +++ b/inc/coro/task.hpp @@ -1,10 +1,6 @@ #pragma once -#include #include -#include - -#include namespace coro { @@ -71,70 +67,70 @@ struct promise_base protected: std::coroutine_handle<> m_continuation{nullptr}; - std::optional m_exception_ptr{std::nullopt}; + std::exception_ptr m_exception_ptr{}; }; template struct promise final : public promise_base { using task_type = task; - using coro_handle = std::coroutine_handle>; + using coroutine_handle = std::coroutine_handle>; promise() noexcept = default; ~promise() = default; auto get_return_object() noexcept -> task_type; - auto return_value(return_type result) -> void + auto return_value(return_type value) -> void { - m_result = std::move(result); + m_return_value = std::move(value); } - auto result() const & -> const return_type& + auto return_value() const & -> const return_type& { - if(this->m_exception_ptr.has_value()) + if(m_exception_ptr) { - std::rethrow_exception(this->m_exception_ptr.value()); + std::rethrow_exception(m_exception_ptr); } - return m_result; + return m_return_value; } - auto result() && -> return_type&& + auto return_value() && -> return_type&& { - if(this->m_exception_ptr.has_value()) + if(m_exception_ptr) { - std::rethrow_exception(this->m_exception_ptr.value()); + std::rethrow_exception(m_exception_ptr); } - return std::move(m_result); + return std::move(m_return_value); } private: - return_type m_result; + return_type m_return_value; }; template<> struct promise : public promise_base { using task_type = task; - using coro_handle = std::coroutine_handle>; + using coroutine_handle = std::coroutine_handle>; promise() noexcept = default; ~promise() = default; auto get_return_object() noexcept -> task_type; - auto return_void() -> void + auto return_void() noexcept -> void { } - auto result() const -> void + auto return_value() const -> void { - if(this->m_exception_ptr.has_value()) + if(m_exception_ptr) { - std::rethrow_exception(this->m_exception_ptr.value()); + std::rethrow_exception(m_exception_ptr); } } }; @@ -147,11 +143,11 @@ class task public: using task_type = task; using promise_type = detail::promise; - using coro_handle = std::coroutine_handle; + using coroutine_handle = std::coroutine_handle; struct awaitable_base { - awaitable_base(std::coroutine_handle coroutine) noexcept + awaitable_base(coroutine_handle coroutine) noexcept : m_coroutine(coroutine) { @@ -177,7 +173,7 @@ public: } - task(coro_handle handle) + task(coroutine_handle handle) : m_coroutine(handle) { @@ -249,7 +245,7 @@ public: { auto await_resume() noexcept -> decltype(auto) { - return this->m_coroutine.promise().result(); + return this->m_coroutine.promise().return_value(); } }; @@ -270,13 +266,13 @@ public: return std::move(m_coroutine.promise()); } - auto handle() -> coro_handle + auto handle() -> coroutine_handle { return m_coroutine; } private: - coro_handle m_coroutine{nullptr}; + coroutine_handle m_coroutine{nullptr}; }; namespace detail @@ -285,15 +281,14 @@ namespace detail template inline auto promise::get_return_object() noexcept -> task { - return task{coro_handle::from_promise(*this)}; + return task{coroutine_handle::from_promise(*this)}; } inline auto promise::get_return_object() noexcept -> task<> { - return task<>{coro_handle::from_promise(*this)}; + return task<>{coroutine_handle::from_promise(*this)}; } } // namespace detail - } // namespace coro diff --git a/test/test_event.cpp b/test/test_event.cpp index 45ddbbf..aba2cac 100644 --- a/test/test_event.cpp +++ b/test/test_event.cpp @@ -20,7 +20,7 @@ TEST_CASE("event single awaiter") REQUIRE_FALSE(task.is_ready()); e.set(); // this will automaticaly resume the task that is awaiting the event. REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 42); + REQUIRE(task.promise().return_value() == 42); } @@ -47,7 +47,7 @@ TEST_CASE("event one watcher") producer(e); - REQUIRE(value.promise().result() == 42); + REQUIRE(value.promise().return_value() == 42); } TEST_CASE("event multiple watchers") @@ -66,7 +66,7 @@ TEST_CASE("event multiple watchers") producer(e); - REQUIRE(value1.promise().result() == 42); - REQUIRE(value2.promise().result() == 42); - REQUIRE(value3.promise().result() == 42); + REQUIRE(value1.promise().return_value() == 42); + REQUIRE(value2.promise().return_value() == 42); + REQUIRE(value3.promise().return_value() == 42); } diff --git a/test/test_latch.cpp b/test/test_latch.cpp index b48cb21..d592956 100644 --- a/test/test_latch.cpp +++ b/test/test_latch.cpp @@ -18,7 +18,7 @@ TEST_CASE("latch count=0") task.resume(); REQUIRE(task.is_ready()); // The latch never waits due to zero count. - REQUIRE(task.promise().result() == 42); + REQUIRE(task.promise().return_value() == 42); } TEST_CASE("latch count=1") @@ -37,7 +37,7 @@ TEST_CASE("latch count=1") l.count_down(); REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 1); + REQUIRE(task.promise().return_value() == 1); } TEST_CASE("latch count=1 count_down=5") @@ -56,7 +56,7 @@ TEST_CASE("latch count=1 count_down=5") l.count_down(5); REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 1); + REQUIRE(task.promise().return_value() == 1); } TEST_CASE("latch count=5 count_down=1 x5") @@ -83,7 +83,7 @@ TEST_CASE("latch count=5 count_down=1 x5") REQUIRE_FALSE(task.is_ready()); l.count_down(1); REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 5); + REQUIRE(task.promise().return_value() == 5); } TEST_CASE("latch count=5 count_down=5") @@ -102,5 +102,5 @@ TEST_CASE("latch count=5 count_down=5") l.count_down(5); REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 5); + REQUIRE(task.promise().return_value() == 5); } diff --git a/test/test_scheduler.cpp b/test/test_scheduler.cpp index 007056f..dd37f59 100644 --- a/test/test_scheduler.cpp +++ b/test/test_scheduler.cpp @@ -97,7 +97,7 @@ TEST_CASE("scheduler task with multiple yields on event") std::cerr << "1st suspend\n"; co_await s.yield(token); std::cerr << "1st resume\n"; - counter += token.result(); + counter += token.return_value(); token.reset(); std::cerr << "never suspend\n"; co_await std::suspend_never{}; @@ -105,12 +105,12 @@ TEST_CASE("scheduler task with multiple yields on event") co_await s.yield(token); token.reset(); std::cerr << "2nd resume\n"; - counter += token.result(); + counter += token.return_value(); std::cerr << "3rd suspend\n"; co_await s.yield(token); token.reset(); std::cerr << "3rd resume\n"; - counter += token.result(); + counter += token.return_value(); co_return; }; @@ -485,7 +485,7 @@ TEST_CASE("scheduler yield user event") auto func = [&]() -> coro::task { co_await s.yield(token); - REQUIRE(token.result() == expected_result); + REQUIRE(token.return_value() == expected_result); co_return; }; @@ -551,4 +551,22 @@ TEST_CASE("scheduler manual process events with self generating coroutine (stack while(s.process_events()) ; std::cerr << "Recursive test done.\n"; +} + +TEST_CASE("scheduler task throws") +{ + coro::scheduler s{}; + + auto func = []() -> coro::task + { + // Is it possible to actually notify the user when running a task in a scheduler? + // Seems like the user will need to manually catch. + throw std::runtime_error{"I always throw."}; + co_return; + }; + + s.schedule(func()); + + s.shutdown(); + REQUIRE(s.empty()); } \ No newline at end of file diff --git a/test/test_task.cpp b/test/test_task.cpp index a08e7b7..6a57279 100644 --- a/test/test_task.cpp +++ b/test/test_task.cpp @@ -13,8 +13,8 @@ TEST_CASE("task hello world") auto h = []() -> task_type { co_return "Hello"; }(); auto w = []() -> task_type { co_return "World"; }(); - REQUIRE(h.promise().result().empty()); - REQUIRE(w.promise().result().empty()); + REQUIRE(h.promise().return_value().empty()); + REQUIRE(w.promise().return_value().empty()); h.resume(); // task suspends immediately w.resume(); @@ -22,11 +22,11 @@ TEST_CASE("task hello world") REQUIRE(h.is_ready()); REQUIRE(w.is_ready()); - auto w_value = std::move(w).promise().result(); + auto w_value = std::move(w).promise().return_value(); - REQUIRE(h.promise().result() == "Hello"); + REQUIRE(h.promise().return_value() == "Hello"); REQUIRE(w_value == "World"); - REQUIRE(w.promise().result().empty()); + REQUIRE(w.promise().return_value().empty()); } TEST_CASE("task void") @@ -61,7 +61,7 @@ TEST_CASE("task exception thrown") bool thrown{false}; try { - auto value = task.promise().result(); + auto value = task.promise().return_value(); } catch(const std::exception& e) { @@ -172,7 +172,7 @@ TEST_CASE("task multiple suspends return integer") task.resume(); // third internal suspend REQUIRE(task.is_ready()); - REQUIRE(task.promise().result() == 11); + REQUIRE(task.promise().return_value() == 11); } TEST_CASE("task resume from promise to coroutine handles of different types") @@ -203,8 +203,54 @@ TEST_CASE("task resume from promise to coroutine handles of different types") REQUIRE(task1.is_ready()); REQUIRE(coro_handle1.done()); - REQUIRE(task1.promise().result() == 42); + REQUIRE(task1.promise().return_value() == 42); REQUIRE(task2.is_ready()); REQUIRE(coro_handle2.done()); } + +TEST_CASE("task throws void") +{ + auto task = []() -> coro::task + { + throw std::runtime_error{"I always throw."}; + co_return; + }(); + + task.resume(); + REQUIRE(task.is_ready()); + REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error); +} + +TEST_CASE("task throws non-void l-value") +{ + auto task = []() -> coro::task + { + throw std::runtime_error{"I always throw."}; + co_return 42; + }(); + + task.resume(); + REQUIRE(task.is_ready()); + REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error); +} + +TEST_CASE("task throws non-void r-value") +{ + struct type + { + int m_value; + }; + + auto task = []() -> coro::task + { + type return_value{42}; + + throw std::runtime_error{"I always throw."}; + co_return std::move(return_value); + }(); + + task.resume(); + REQUIRE(task.is_ready()); + REQUIRE_THROWS_AS(task.promise().return_value(), std::runtime_error); +} \ No newline at end of file