Repository: jbaldwin/libcoro
Pull request #318: Update mac os ci workflow to use newer version of llvm and mac os
Author: login: tglane
Created: 2025-05-09T16:59:15Z | Merged: 2025-05-10T16:34:57Z

This PR should fix the CI for MacOS. I had to add some missing headers and I had to make some changes to the move constructor of `coro::queue` that you should review because I honestly have no idea how you got that to compile prior to my changes (your mutex class is not move constructible but is moved in the move constructor of queue).


Diff:
diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml
index 003b1c7b..84ac7719 100644
--- a/.github/workflows/ci-macos.yml
+++ b/.github/workflows/ci-macos.yml
@@ -1,45 +1,46 @@
-# name: ci-macos
+name: ci-macos
 
-# on: [pull_request, workflow_dispatch]
+on: [pull_request, workflow_dispatch]
 
-# jobs:
-#     macos:
-#         name: macos-12
-#         runs-on: macos-12
-#         strategy:
-#             fail-fast: false
-#             matrix:
-#                 clang_version: [17]
-#                 cxx_standard: [20, 23]
-#                 libcoro_feature_networking: [ {enabled: OFF, tls: OFF} ]
-#                 libcoro_build_shared_libs: [OFF, ON]
-#         steps:
-#             -   name: Install Dependencies
-#                 run: |
-#                     brew update
-#                     brew install llvm@${{ matrix.clang_version }}
-#                     brew install ninja
-#             -   name: Checkout
-#                 uses: actions/checkout@v4
-#                 with:
-#                     submodules: recursive
-#             -   name: Release
-#                 run: |
-#                     brew --prefix llvm@17
-#                     mkdir Release
-#                     cd Release
-#                     cmake \
-#                         -GNinja \
-#                         -DCMAKE_BUILD_TYPE=Release \
-#                         -DCMAKE_C_COMPILER=$(brew --prefix llvm@${{ matrix.clang_version }})/bin/clang-${{ matrix.clang_version }} \
-#                         -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@${{ matrix.clang_version }})/bin/clang-${{ matrix.clang_version }} \
-#                         -DCMAKE_CXX_STANDARD=${{ matrix.cxx_standard }} \
-#                         -DLIBCORO_FEATURE_NETWORKING=${{ matrix.libcoro_feature_networking.enabled }} \
-#                         -DLIBCORO_FEATURE_TLS=${{ matrix.libcoro_feature_networking.tls }} \
-#                         -DLIBCORO_BUILD_SHARED_LIBS=${{ matrix.libcoro_build_shared_libs }} \
-#                         ..
-#                     cmake --build . --config Release
-#             -   name: Test
-#                 run: |
-#                     cd Release
-#                     ctest --build-config Release -VV
+jobs:
+  macos:
+    name: macos-15
+    runs-on: macos-15
+    strategy:
+      fail-fast: false
+      matrix:
+        clang_version: [20]
+        cxx_standard: [20, 23]
+        libcoro_feature_networking: [{ enabled: OFF, tls: OFF }]
+        libcoro_build_shared_libs: [OFF, ON]
+    steps:
+      - name: Install Dependencies
+        run: |
+          brew update
+          brew install llvm@${{ matrix.clang_version }}
+          brew install ninja
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+      - name: Release
+        run: |
+          brew --prefix llvm@${{ matrix.clang_version }}
+          ls $(brew --prefix llvm@${{ matrix.clang_version }})/bin
+          mkdir Release
+          cd Release
+          cmake \
+              -GNinja \
+              -DCMAKE_BUILD_TYPE=Release \
+              -DCMAKE_C_COMPILER=$(brew --prefix llvm@${{ matrix.clang_version }})/bin/clang-${{ matrix.clang_version }} \
+              -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@${{ matrix.clang_version }})/bin/clang-${{ matrix.clang_version }} \
+              -DCMAKE_CXX_STANDARD=${{ matrix.cxx_standard }} \
+              -DLIBCORO_FEATURE_NETWORKING=${{ matrix.libcoro_feature_networking.enabled }} \
+              -DLIBCORO_FEATURE_TLS=${{ matrix.libcoro_feature_networking.tls }} \
+              -DLIBCORO_BUILD_SHARED_LIBS=${{ matrix.libcoro_build_shared_libs }} \
+              ..
+          cmake --build . --config Release
+      - name: Test
+        run: |
+          cd Release
+          ctest --build-config Release -VV
diff --git a/include/coro/queue.hpp b/include/coro/queue.hpp
index c57e12f3..61b7a6d1 100644
--- a/include/coro/queue.hpp
+++ b/include/coro/queue.hpp
@@ -4,7 +4,6 @@
 #include "coro/expected.hpp"
 #include "coro/sync_wait.hpp"
 
-#include <mutex>
 #include <queue>
 
 namespace coro
@@ -30,7 +29,6 @@ enum class queue_consume_result
     queue_stopped
 };
 
-
 /**
  * @brief An unbounded queue. If the queue is empty and there are waiters to consume then
  *        there are no allocations and the coroutine context will simply be passed to the
@@ -52,10 +50,7 @@ class queue
          *
          * @return coro::task<scoped_lock>
          */
-        auto make_acquire_lock_task() -> coro::task<scoped_lock>
-        {
-            co_return co_await m_queue.m_mutex.lock();
-        }
+        auto make_acquire_lock_task() -> coro::task<scoped_lock> { co_return co_await m_queue.m_mutex.lock(); }
 
         auto await_ready() noexcept -> bool
         {
@@ -146,30 +141,11 @@ class queue
     queue() {}
     ~queue() {}
 
-    queue(const queue&) = delete;
-    queue(queue&& other)
-    {
-        m_waiters  = std::exchange(other.m_waiters, nullptr);
-        m_mutex    = std::move(other.m_mutex);
-        m_elements = std::move(other.m_elements);
-        m_shutting_down = std::move(other.m_shutting_down);
-        m_stopped = std::move(other.m_stopped);
-    }
-
-    auto operator=(const queue&) -> queue& = delete;
-    auto operator=(queue&& other) -> queue&
-    {
-        if (std::addressof(other) != this)
-        {
-            m_waiters  = std::exchange(other.m_waiters, nullptr);
-            m_mutex    = std::move(other.m_mutex);
-            m_elements = std::move(other.m_elements);
-            m_shutting_down = std::move(other.m_shutting_down);
-            m_stopped = std::move(other.m_stopped);
-        }
+    queue(const queue&)  = delete;
+    queue(queue&& other) = delete;
 
-        return *this;
-    }
+    auto operator=(const queue&) -> queue&  = delete;
+    auto operator=(queue&& other) -> queue& = delete;
 
     /**
      * @brief Determines if the queue is empty.
@@ -312,7 +288,8 @@ class queue
     auto shutdown_notify_waiters() -> coro::task<void>
     {
         auto expected = false;
-        if (!m_shutting_down.compare_exchange_strong(expected, true, std::memory_order::acq_rel, std::memory_order::relaxed))
+        if (!m_shutting_down.compare_exchange_strong(
+                expected, true, std::memory_order::acq_rel, std::memory_order::relaxed))
         {
             co_return;
         }
@@ -345,12 +322,13 @@ class queue
     auto shutdown_notify_waiters_drain(executor_t& e) -> coro::task<void>
     {
         auto expected = false;
-        if (!m_shutting_down.compare_exchange_strong(expected, true, std::memory_order::acq_rel, std::memory_order::relaxed))
+        if (!m_shutting_down.compare_exchange_strong(
+                expected, true, std::memory_order::acq_rel, std::memory_order::relaxed))
         {
             co_return;
         }
 
-        while(!empty())
+        while (!empty())
         {
             co_await e.yield();
         }
diff --git a/include/coro/sync_wait.hpp b/include/coro/sync_wait.hpp
index b403e953..36c93ea6 100644
--- a/include/coro/sync_wait.hpp
+++ b/include/coro/sync_wait.hpp
@@ -7,6 +7,7 @@
 #include <condition_variable>
 #include <exception>
 #include <mutex>
+#include <stdexcept>
 #include <variant>
 
 namespace coro
@@ -67,9 +68,9 @@ class sync_wait_task_promise : public sync_wait_task_promise_base
 
     static constexpr bool return_type_is_reference = std::is_reference_v<return_type>;
     using stored_type                              = std::conditional_t<
-        return_type_is_reference,
-        std::remove_reference_t<return_type>*,
-        std::remove_const_t<return_type>>;
+                                     return_type_is_reference,
+                                     std::remove_reference_t<return_type>*,
+                                     std::remove_const_t<return_type>>;
     using variant_type = std::variant<unset_return_value, stored_type, std::exception_ptr>;
 
     sync_wait_task_promise() noexcept                                        = default;
@@ -88,9 +89,9 @@ class sync_wait_task_promise : public sync_wait_task_promise_base
     auto get_return_object() noexcept { return coroutine_type::from_promise(*this); }
 
     template<typename value_type>
-    requires(return_type_is_reference and std::is_constructible_v<return_type, value_type&&>) or
-        (not return_type_is_reference and
-         std::is_constructible_v<stored_type, value_type&&>) auto return_value(value_type&& value) -> void
+        requires(return_type_is_reference and std::is_constructible_v<return_type, value_type &&>) or
+                (not return_type_is_reference and std::is_constructible_v<stored_type, value_type &&>)
+    auto return_value(value_type&& value) -> void
     {
         if constexpr (return_type_is_reference)
         {
@@ -103,7 +104,8 @@ class sync_wait_task_promise : public sync_wait_task_promise_base
         }
     }
 
-    auto return_value(stored_type value) -> void requires(not return_type_is_reference)
+    auto return_value(stored_type value) -> void
+        requires(not return_type_is_reference)
     {
         if constexpr (std::is_move_constructible_v<stored_type>)
         {


Reviews:
user: login: jbaldwin
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-05-09T18:06:06Z
user: login: tglane
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T22:23:36Z
user: login: jbaldwin
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-10T13:34:42Z

Review comments:
diff_hunk: @@ -147,25 +142,17 @@ class queue
     ~queue() {}
 
     queue(const queue&) = delete;
-    queue(queue&& other)
-    {
-        m_waiters  = std::exchange(other.m_waiters, nullptr);
-        m_mutex    = std::move(other.m_mutex);
-        m_elements = std::move(other.m_elements);
-        m_shutting_down = std::move(other.m_shutting_down);
-        m_stopped = std::move(other.m_stopped);
-    }
+    queue(queue&& other) { *this = std::move(other); }
 
     auto operator=(const queue&) -> queue& = delete;
     auto operator=(queue&& other) -> queue&
     {
         if (std::addressof(other) != this)
         {
-            m_waiters  = std::exchange(other.m_waiters, nullptr);
-            m_mutex    = std::move(other.m_mutex);
-            m_elements = std::move(other.m_elements);
-            m_shutting_down = std::move(other.m_shutting_down);
-            m_stopped = std::move(other.m_stopped);
+            m_waiters       = std::exchange(other.m_waiters, nullptr);
path: include/coro/queue.hpp
user: login: jbaldwin
body: Unfortunately I think the move constructor and move assignment operator are broken without being able to move the `coro::mutex`, we could put it in a `std::unique_ptr<coro::mutex.` to make it properly move-able at the cost of an extra allocation OR just disable moving and force the user to place it into a `std::unique_ptr` if they need to be able to move it around. I really don't know why this compiled on all platforms previously because it should absolutely not have been allowed and I definitely missed it.
created_at: 2025-05-09T18:06:01Z
author_association: OWNER
diff_hunk: @@ -147,25 +142,17 @@ class queue
     ~queue() {}
 
     queue(const queue&) = delete;
-    queue(queue&& other)
-    {
-        m_waiters  = std::exchange(other.m_waiters, nullptr);
-        m_mutex    = std::move(other.m_mutex);
-        m_elements = std::move(other.m_elements);
-        m_shutting_down = std::move(other.m_shutting_down);
-        m_stopped = std::move(other.m_stopped);
-    }
+    queue(queue&& other) { *this = std::move(other); }
 
     auto operator=(const queue&) -> queue& = delete;
     auto operator=(queue&& other) -> queue&
     {
         if (std::addressof(other) != this)
         {
-            m_waiters  = std::exchange(other.m_waiters, nullptr);
-            m_mutex    = std::move(other.m_mutex);
-            m_elements = std::move(other.m_elements);
-            m_shutting_down = std::move(other.m_shutting_down);
-            m_stopped = std::move(other.m_stopped);
+            m_waiters       = std::exchange(other.m_waiters, nullptr);
path: include/coro/queue.hpp
user: login: tglane
body: Yeah, you are right. Just creating a new mutex in the move constructor will break the queue.
I think the main question to answer is whether it is important that the `coro::queue` type is movable?

Since your other collection type - `coro::ring_buffer` - is not movable too, I would tend to deleting the move semantics for `coro::queue` as well.
created_at: 2025-05-09T22:23:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: jbaldwin
created_at: 2025-05-09T18:06:55Z
body: You are absolutely correct.. that should not have compiled before 😬 I left a note on the move constructor/operator.. preference is probably to just `delete` them?
author_association: OWNER
user: login: tglane
created_at: 2025-05-10T16:05:26Z
body: Hmm seems like one test timed out without actually running the tests? I think this is not related to the changes made in this PR.
author_association: CONTRIBUTOR