From c4da4c0ce623a42e74a742056213afba6c5a72c8 Mon Sep 17 00:00:00 2001 From: Dan Lapid Date: Sat, 10 Feb 2024 15:41:04 +0200 Subject: [PATCH] Added macos support --- .github/workflows/cmake.yml | 8 - include/cppcoro/config.hpp | 12 ++ include/cppcoro/detail/darwin.hpp | 79 ++++++++ include/cppcoro/detail/message_queue.hpp | 5 +- include/cppcoro/detail/platform.hpp | 12 +- include/cppcoro/io_service.hpp | 2 +- .../net/socket_recv_from_operation.hpp | 4 +- include/cppcoro/net/socket_recv_operation.hpp | 4 +- include/cppcoro/net/socket_send_operation.hpp | 4 +- .../cppcoro/net/socket_send_to_operation.hpp | 4 +- lib/CMakeLists.txt | 14 ++ lib/darwin.cpp | 125 ++++++++++++ lib/darwin_message_queue.cpp | 189 ++++++++++++++++++ lib/file.cpp | 6 +- lib/file_read_operation.cpp | 2 +- lib/file_write_operation.cpp | 2 +- lib/io_service.cpp | 33 +++ lib/read_only_file.cpp | 4 +- lib/read_write_file.cpp | 4 +- lib/socket.cpp | 16 +- lib/socket_accept_operation.cpp | 2 +- lib/socket_connect_operation.cpp | 17 +- lib/socket_disconnect_operation.cpp | 2 +- lib/socket_helpers.cpp | 2 +- lib/socket_recv_from_operation.cpp | 38 +++- lib/socket_recv_operation.cpp | 2 +- lib/socket_send_operation.cpp | 2 +- lib/socket_send_to_operation.cpp | 2 +- lib/writable_file.cpp | 6 +- lib/write_only_file.cpp | 4 +- test/CMakeLists.txt | 29 ++- test/generator_tests.cpp | 1 + 32 files changed, 575 insertions(+), 61 deletions(-) create mode 100644 include/cppcoro/detail/darwin.hpp create mode 100644 lib/darwin.cpp create mode 100644 lib/darwin_message_queue.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b6d0cf7b..2c140c25 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -347,14 +347,6 @@ jobs: cxxver: 20, generator: "Unix Makefiles", } - - { - name: "MacOS 11 C++20", - os: macos-11, - buildtype: Release, - cxx: "clang++", - cxxver: 20, - generator: "Unix Makefiles", - } - { name: "MacOS 12 C++20", os: macos-12, diff --git a/include/cppcoro/config.hpp b/include/cppcoro/config.hpp index fde8147c..2340bd30 100644 --- a/include/cppcoro/config.hpp +++ b/include/cppcoro/config.hpp @@ -97,6 +97,18 @@ # define CPPCORO_OS_LINUX 0 #endif +#if defined(__APPLE__) +# define CPPCORO_OS_DARWIN 1 +#else +# define CPPCORO_OS_DARWIN 0 +#endif + +#if defined(__FreeBSD__) +# define CPPCORO_OS_FREEBSD 1 +#else +# define CPPCORO_OS_FREEBSD 0 +#endif + ///////////////////////////////////////////////////////////////////////////// // CPU Detection diff --git a/include/cppcoro/detail/darwin.hpp b/include/cppcoro/detail/darwin.hpp new file mode 100644 index 00000000..cc0024f5 --- /dev/null +++ b/include/cppcoro/detail/darwin.hpp @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft +// Licenced under MIT license. See LICENSE.txt for details. +/////////////////////////////////////////////////////////////////////////////// +#ifndef CPPCORO_DETAIL_DARWIN_HPP_INCLUDED +#define CPPCORO_DETAIL_DARWIN_HPP_INCLUDED + +#include + +#if !CPPCORO_OS_DARWIN +#error is only supported on the Linux platform. +#endif + +#include +#include +#include +#include + +namespace cppcoro +{ + class io_service; + namespace detail + { + namespace darwin + { + using fd_t = int; + + class safe_fd + { + public: + safe_fd(); + + explicit safe_fd(fd_t fd); + ~safe_fd() noexcept; + safe_fd(const safe_fd& other) noexcept; + safe_fd& operator=(const safe_fd& other) noexcept; + safe_fd(safe_fd&& other) noexcept; + safe_fd& operator=(safe_fd&& other) noexcept; + constexpr fd_t fd() const { return m_fd; } + constexpr fd_t handle() const { return m_fd; } + /// Calls close() and sets the fd to -1. + void close() noexcept; + bool operator==(const safe_fd& other) const { return m_fd == other.m_fd; } + bool operator!=(const safe_fd& other) const { return m_fd != other.m_fd; } + bool operator==(fd_t fd) const { return m_fd == fd; } + bool operator!=(fd_t fd) const { return m_fd != fd; } + + private: + fd_t m_fd; + }; + + struct io_state + { + explicit io_state(io_service* ioService) noexcept + : m_ioService(ioService) + , m_fd(-1) + , m_res(0) + , m_completeFunc([] { return 0; }) + { + } + + std::size_t get_result(); + void on_operation_completed_base(); + void cancel() noexcept; + + io_service* m_ioService; + fd_t m_fd; + std::int32_t m_res; + std::function m_completeFunc; + }; + + safe_fd create_timer_fd(); + safe_fd create_kqueue_fd(); + + } // namespace darwin + } // namespace detail +} // namespace cppcoro + +#endif diff --git a/include/cppcoro/detail/message_queue.hpp b/include/cppcoro/detail/message_queue.hpp index 1a2cde6e..a1f56dee 100644 --- a/include/cppcoro/detail/message_queue.hpp +++ b/include/cppcoro/detail/message_queue.hpp @@ -48,9 +48,12 @@ namespace cppcoro void unwatch_handle(file_handle_t handle); bool enqueue_message(message msg); bool dequeue_message(message& msg, bool wait); +#if CPPCORO_OS_DARWIN + void watch_event(struct kevent* event, void* cb); +#endif private: -#if CPPCORO_OS_LINUX +#if CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN int m_pipefd[2]; #endif safe_file_handle_t m_pollfd; diff --git a/include/cppcoro/detail/platform.hpp b/include/cppcoro/detail/platform.hpp index c282cc88..7fcff5c3 100644 --- a/include/cppcoro/detail/platform.hpp +++ b/include/cppcoro/detail/platform.hpp @@ -7,12 +7,13 @@ #include #if CPPCORO_OS_WINNT -# include +#include #elif CPPCORO_OS_LINUX -# include +#include +#elif CPPCORO_OS_DARWIN +#include #endif - namespace cppcoro { namespace detail @@ -27,6 +28,11 @@ namespace cppcoro using safe_file_handle_t = linux::safe_fd; using socket_handle_t = linux::fd_t; using io_state = linux::io_state; +#elif CPPCORO_OS_DARWIN + using file_handle_t = darwin::fd_t; + using safe_file_handle_t = darwin::safe_fd; + using socket_handle_t = darwin::fd_t; + using io_state = darwin::io_state; #endif } } diff --git a/include/cppcoro/io_service.hpp b/include/cppcoro/io_service.hpp index e0756950..1c2e79e2 100644 --- a/include/cppcoro/io_service.hpp +++ b/include/cppcoro/io_service.hpp @@ -241,7 +241,7 @@ namespace cppcoro }; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN class io_service::timed_schedule_operation : public detail::async_operation_cancellable { diff --git a/include/cppcoro/net/socket_recv_from_operation.hpp b/include/cppcoro/net/socket_recv_from_operation.hpp index 4c423a53..19d78e03 100644 --- a/include/cppcoro/net/socket_recv_from_operation.hpp +++ b/include/cppcoro/net/socket_recv_from_operation.hpp @@ -30,7 +30,7 @@ namespace cppcoro::net : m_socket(socket) #if CPPCORO_OS_WINNT , m_buffer(const_cast(buffer), byteCount) -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN , m_buffer(buffer) , m_byteCount(byteCount) #endif @@ -46,7 +46,7 @@ namespace cppcoro::net socket& m_socket; #if CPPCORO_OS_WINNT cppcoro::detail::win32::wsabuf m_buffer; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN void* m_buffer; std::size_t m_byteCount; #endif diff --git a/include/cppcoro/net/socket_recv_operation.hpp b/include/cppcoro/net/socket_recv_operation.hpp index 0538e68a..b094aa25 100644 --- a/include/cppcoro/net/socket_recv_operation.hpp +++ b/include/cppcoro/net/socket_recv_operation.hpp @@ -28,7 +28,7 @@ namespace cppcoro::net : m_socket(s) #if CPPCORO_OS_WINNT , m_buffer(const_cast(buffer), byteCount) -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN , m_buffer(buffer) , m_byteCount(byteCount) #endif @@ -42,7 +42,7 @@ namespace cppcoro::net socket& m_socket; #if CPPCORO_OS_WINNT cppcoro::detail::win32::wsabuf m_buffer; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN void* m_buffer; std::size_t m_byteCount; #endif diff --git a/include/cppcoro/net/socket_send_operation.hpp b/include/cppcoro/net/socket_send_operation.hpp index d700925f..72a2b458 100644 --- a/include/cppcoro/net/socket_send_operation.hpp +++ b/include/cppcoro/net/socket_send_operation.hpp @@ -28,7 +28,7 @@ namespace cppcoro::net : m_socket(s) #if CPPCORO_OS_WINNT , m_buffer(const_cast(buffer), byteCount) -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN , m_buffer(buffer) , m_byteCount(byteCount) #endif @@ -42,7 +42,7 @@ namespace cppcoro::net socket& m_socket; #if CPPCORO_OS_WINNT cppcoro::detail::win32::wsabuf m_buffer; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN const void* m_buffer; std::size_t m_byteCount; #endif diff --git a/include/cppcoro/net/socket_send_to_operation.hpp b/include/cppcoro/net/socket_send_to_operation.hpp index 9839ec4e..fd5249c2 100644 --- a/include/cppcoro/net/socket_send_to_operation.hpp +++ b/include/cppcoro/net/socket_send_to_operation.hpp @@ -31,7 +31,7 @@ namespace cppcoro::net , m_destination(destination) #if CPPCORO_OS_WINNT , m_buffer(const_cast(buffer), byteCount) -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN , m_buffer(buffer) , m_byteCount(byteCount) #endif @@ -46,7 +46,7 @@ namespace cppcoro::net ip_endpoint m_destination; #if CPPCORO_OS_WINNT cppcoro::detail::win32::wsabuf m_buffer; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN const void* m_buffer; std::size_t m_byteCount; #endif diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c24e98d0..09169fee 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -179,6 +179,20 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") io_service.cpp ) list(APPEND sources ${linuxSources} ${fileSources} ${socketNetSources}) +elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") + set(darwinDetailIncludes + darwin.hpp + ) + list(TRANSFORM darwinDetailIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/detail/") + list(APPEND detailIncludes ${darwinDetailIncludes}) + list(APPEND netIncludes ${socketNetIncludes}) + + set(darwinSources + darwin.cpp + darwin_message_queue.cpp + io_service.cpp + ) + list(APPEND sources ${darwinSources} ${fileSources} ${socketNetSources}) endif() add_library(cppcoro diff --git a/lib/darwin.cpp b/lib/darwin.cpp new file mode 100644 index 00000000..8953dce1 --- /dev/null +++ b/lib/darwin.cpp @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft +// Licenced under MIT license. See LICENSE.txt for details. +/////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cppcoro +{ + namespace detail + { + namespace darwin + { + safe_fd create_timer_fd() + { + int fd = kqueue(); + + if (fd == -1) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error creating io_service: timer fd create" }; + } + + return safe_fd{ fd }; + } + + safe_fd create_kqueue_fd() + { + int fd = kqueue(); + + if (fd == -1) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error creating timer thread: kqueue create" }; + } + + return safe_fd{ fd }; + } + + safe_fd::safe_fd() + : m_fd(-1) + { + } + + safe_fd::safe_fd(fd_t fd) + : m_fd(fd) + { + } + + safe_fd::~safe_fd() noexcept + { + close(); + } + + safe_fd::safe_fd(const safe_fd& other) noexcept + : m_fd(dup(other.m_fd)) + { + } + + safe_fd& safe_fd::operator=(const safe_fd& other) noexcept + { + m_fd = dup(other.m_fd); + return *this; + } + + safe_fd::safe_fd(safe_fd&& other) noexcept + : m_fd(std::exchange(other.m_fd, -1)) + { + } + + safe_fd& safe_fd::operator=(safe_fd&& other) noexcept + { + m_fd = std::exchange(other.m_fd, -1); + return *this; + } + + void safe_fd::close() noexcept + { + if (m_fd != -1) + { + ::close(m_fd); + m_fd = -1; + } + } + + std::size_t io_state::get_result() + { + if (m_res < 0) + { + throw std::system_error{ -m_res, std::system_category() }; + } + + return m_res; + } + + void io_state::on_operation_completed_base() + { + m_ioService->get_io_context().unwatch_handle(m_fd); + m_res = m_completeFunc(); + if (m_res < 0) + { + m_res = -errno; + } + } + + void io_state::cancel() noexcept + { + m_ioService->get_io_context().unwatch_handle(m_fd); + m_res = -ECANCELED; + m_ioService->get_io_context().enqueue_message( + { message_type::CALLBACK_TYPE, static_cast(this) }); + } + } // namespace darwin + } // namespace detail +} // namespace cppcoro diff --git a/lib/darwin_message_queue.cpp b/lib/darwin_message_queue.cpp new file mode 100644 index 00000000..824257df --- /dev/null +++ b/lib/darwin_message_queue.cpp @@ -0,0 +1,189 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft +// Licenced under MIT license. See LICENSE.txt for details. +/////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cppcoro +{ + namespace detail + { + message_queue::message_queue(std::uint32_t concurrencyHint) + : m_pollfd(safe_file_handle_t{ darwin::create_kqueue_fd() }) + { + if (pipe(m_pipefd) == -1) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error creating io_service: failed creating pipe" }; + } + if (fcntl(m_pipefd[0], F_SETFL, fcntl(m_pipefd[0], F_GETFL) | O_NONBLOCK) == -1) + { + throw std::system_error{ + static_cast(errno), + std::system_category(), + "Error creating io_service: failed setting pipe to non blocking" + }; + } + if (fcntl(m_pipefd[1], F_SETFL, fcntl(m_pipefd[1], F_GETFL) | O_NONBLOCK) == -1) + { + throw std::system_error{ + static_cast(errno), + std::system_category(), + "Error creating io_service: failed setting pipe to non blocking" + }; + } + watch_handle(m_pipefd[0], reinterpret_cast(m_pipefd[0]), watch_type::readable); + } + + message_queue::~message_queue() + { + unwatch_handle(m_pipefd[0]); + assert(close(m_pipefd[0]) == 0); + assert(close(m_pipefd[1]) == 0); + } + + void message_queue::add_handle(file_handle_t handle) + { + } + void message_queue::remove_handle(file_handle_t handle) + { + } + + void message_queue::watch_handle(file_handle_t handle, void* cb, watch_type events) + { + struct kevent ev; + switch (events) + { + case watch_type::readable: + EV_SET(&ev, handle, EVFILT_READ, EV_ADD, 0, 0, cb); + watch_event(&ev, cb); + break; + case watch_type::writable: + EV_SET(&ev, handle, EVFILT_WRITE, EV_ADD, 0, 0, cb); + watch_event(&ev, cb); + break; + case watch_type::readablewritable: + EV_SET(&ev, handle, EVFILT_READ, EV_ADD, 0, 0, cb); + watch_event(&ev, cb); + EV_SET(&ev, handle, EVFILT_WRITE, EV_ADD, 0, 0, cb); + watch_event(&ev, cb); + break; + } + } + void message_queue::watch_event(struct kevent* event, void* cb) + { + if (kevent(m_pollfd.fd(), event, 1, NULL, 0, NULL) == -1) + { + if (errno == EPERM) + { + // epoll returns EPERM on regular files because they are + // always ready for read/write, we can just queue the callback to run + enqueue_message({ message_type::CALLBACK_TYPE, cb }); + } + else + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "message_queue: watch_handle failed" }; + } + } + } + + void message_queue::unwatch_handle(file_handle_t handle) + { + if (handle == -1) + { + return; + } + struct kevent ev; + EV_SET(&ev, handle, EVFILT_READ, EV_DELETE, 0, 0, NULL); + if (kevent(m_pollfd.fd(), &ev, 1, NULL, 0, NULL) == -1) + { + if (errno != EPERM && errno != ENOENT) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "message_queue: unwatch_handle failed" }; + } + } + EV_SET(&ev, handle, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + if (kevent(m_pollfd.fd(), &ev, 1, NULL, 0, NULL) == -1) + { + if (errno != EPERM && errno != ENOENT) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "message_queue: unwatch_handle failed" }; + } + } + } + + bool message_queue::enqueue_message(message msg) + { + int status = write(m_pipefd[1], (const char*)&msg, sizeof(msg)); + return status == -1 ? false : true; + } + + bool message_queue::dequeue_message(message& msg, bool wait) + { + struct kevent ev; + struct timespec immediate = { 0, 0 }; + int nfds = kevent(m_pollfd.fd(), NULL, 0, &ev, 1, wait ? NULL : &immediate); + if (nfds == -1) + { + if (errno == EINTR || errno == EAGAIN) + { + return false; + } + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error in epoll_wait run loop" }; + } + + if (nfds == 0 && !wait) + { + return false; + } + + if (nfds == 0 && wait) + { + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error in epoll_wait run loop" }; + } + + if (static_cast(ev.ident) == m_pipefd[0]) + { + ssize_t status = read(m_pipefd[0], (char*)&msg, sizeof(msg)); + + if (status == -1) + { + if (errno == EINTR || errno == EAGAIN) + { + return false; + } + throw std::system_error{ static_cast(errno), + std::system_category(), + "Error retrieving message from message queue" }; + } + + return true; + } + else + { + msg.data = ev.udata; + msg.type = message_type::CALLBACK_TYPE; + return true; + } + } + } // namespace detail +} // namespace cppcoro diff --git a/lib/file.cpp b/lib/file.cpp index 7af1dec3..68b43b16 100644 --- a/lib/file.cpp +++ b/lib/file.cpp @@ -14,7 +14,7 @@ # define WIN32_LEAN_AND_MEAN # endif # include -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include @@ -43,7 +43,7 @@ std::uint64_t cppcoro::file::size() const } return size.QuadPart; -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN struct stat sb; if (fstat(m_fileHandle.handle(), &sb) < 0) { @@ -157,7 +157,7 @@ cppcoro::file cppcoro::file::open( return { std::move(fileHandle), &ioService }; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN cppcoro::file cppcoro::file::open( int fileAccess, diff --git a/lib/file_read_operation.cpp b/lib/file_read_operation.cpp index c847d609..58567a0a 100644 --- a/lib/file_read_operation.cpp +++ b/lib/file_read_operation.cpp @@ -62,7 +62,7 @@ bool cppcoro::file_read_operation_impl::try_start( return true; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include bool cppcoro::file_read_operation_impl::try_start( diff --git a/lib/file_write_operation.cpp b/lib/file_write_operation.cpp index 1547689e..05995aab 100644 --- a/lib/file_write_operation.cpp +++ b/lib/file_write_operation.cpp @@ -62,7 +62,7 @@ bool cppcoro::file_write_operation_impl::try_start( return true; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include bool cppcoro::file_write_operation_impl::try_start( diff --git a/lib/io_service.cpp b/lib/io_service.cpp index b042a79a..4113d227 100644 --- a/lib/io_service.cpp +++ b/lib/io_service.cpp @@ -23,6 +23,8 @@ # include #elif CPPCORO_OS_LINUX # include +#elif CPPCORO_OS_DARWIN +# include #endif #if CPPCORO_OS_WINNT @@ -930,4 +932,35 @@ bool cppcoro::io_service::timed_schedule_operation::try_start() noexcept { m_ioService->get_io_context().watch_handle(m_timerfd.fd(), reinterpret_cast(this), detail::watch_type::readable); return true; } +#elif CPPCORO_OS_DARWIN +void cppcoro::io_service::schedule_operation::await_suspend( + cppcoro::coroutine_handle<> awaiter) noexcept +{ + m_awaiter = awaiter; + m_service.schedule_impl(this); +} + +cppcoro::io_service::timed_schedule_operation::timed_schedule_operation( + io_service& service, + std::chrono::high_resolution_clock::time_point resumeTime, + cppcoro::cancellation_token&& ct) noexcept + : cppcoro::detail::async_operation_cancellable( + &service, std::move(ct)) + , m_resumeTime(resumeTime) + , m_timerfd(detail::darwin::create_timer_fd()) +{ +} + +bool cppcoro::io_service::timed_schedule_operation::try_start() noexcept +{ + std::chrono::high_resolution_clock::time_point currentTime = + std::chrono::high_resolution_clock::now(); + auto waitTime = m_resumeTime - currentTime; + auto miliseconds = std::chrono::duration_cast(waitTime); + struct kevent event; + EV_SET(&event, m_timerfd.fd(), EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, miliseconds.count(), reinterpret_cast(this)); + m_completeFunc = [&]() { return 0; }; + m_ioService->get_io_context().watch_event(&event, reinterpret_cast(this)); + return true; +} #endif diff --git a/lib/read_only_file.cpp b/lib/read_only_file.cpp index 86a47380..13bcaed1 100644 --- a/lib/read_only_file.cpp +++ b/lib/read_only_file.cpp @@ -10,7 +10,7 @@ # define WIN32_LEAN_AND_MEAN # endif # include -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include #endif @@ -23,7 +23,7 @@ cppcoro::read_only_file cppcoro::read_only_file::open( return read_only_file(file::open( #if CPPCORO_OS_WINNT GENERIC_READ, -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN O_RDONLY, #endif ioService, diff --git a/lib/read_write_file.cpp b/lib/read_write_file.cpp index 34e43f22..f42f31f9 100644 --- a/lib/read_write_file.cpp +++ b/lib/read_write_file.cpp @@ -10,7 +10,7 @@ # define WIN32_LEAN_AND_MEAN # endif # include -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include #endif @@ -24,7 +24,7 @@ cppcoro::read_write_file cppcoro::read_write_file::open( return read_write_file(file::open( #if CPPCORO_OS_WINNT GENERIC_READ | GENERIC_WRITE, -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN O_RDWR, #endif ioService, diff --git a/lib/socket.cpp b/lib/socket.cpp index 04d8db35..ebad86d1 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -27,13 +27,14 @@ # include # include int get_error() {return ::WSAGetLastError();} -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include # include # include # include +# include #define closesocket close #define INVALID_SOCKET (-1) #define SOCKET_ERROR (-1) @@ -176,7 +177,7 @@ namespace return cppcoro::net::socket(socketHandle, &ioSvc); } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN cppcoro::net::socket create_socket( int addressFamily, int socketType, @@ -184,7 +185,7 @@ namespace cppcoro::io_service& ioSvc) { - const int socketHandle = ::socket(addressFamily, socketType | SOCK_NONBLOCK, protocol); + const int socketHandle = ::socket(addressFamily, socketType, protocol); if (socketHandle == INVALID_SOCKET) { const int errorCode = get_error(); @@ -193,6 +194,13 @@ namespace std::system_category(), "Error creating socket"); } + if (fcntl(socketHandle, F_SETFL, fcntl(socketHandle, F_GETFL, 0) | O_NONBLOCK) == -1) { + const int errorCode = get_error(); + throw std::system_error( + errorCode, + std::system_category(), + "Error creating socket: Failed setting socket to nonblocking"); + } auto closeSocketOnFailure = cppcoro::on_scope_failure([&] { @@ -460,7 +468,7 @@ cppcoro::detail::socket_handle_t duplicate_socket(const cppcoro::detail::socket_ WSAPROTOCOL_INFO wsa_pi; WSADuplicateSocket(handle, GetCurrentProcessId(), &wsa_pi); return WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0); -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN return dup(handle); #endif } diff --git a/lib/socket_accept_operation.cpp b/lib/socket_accept_operation.cpp index 6770caf1..bdec8e38 100644 --- a/lib/socket_accept_operation.cpp +++ b/lib/socket_accept_operation.cpp @@ -127,7 +127,7 @@ void cppcoro::net::socket_accept_operation_impl::get_result( } } } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/socket_connect_operation.cpp b/lib/socket_connect_operation.cpp index 44b72eaa..561b4886 100644 --- a/lib/socket_connect_operation.cpp +++ b/lib/socket_connect_operation.cpp @@ -173,7 +173,7 @@ void cppcoro::net::socket_connect_operation_impl::get_result( } } } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include @@ -194,9 +194,24 @@ bool cppcoro::net::socket_connect_operation_impl::try_start( return false; } operation.m_completeFunc = [&, remoteSockaddrStorage, sockaddrNameLength]() { +#if CPPCORO_OS_LINUX return connect(m_socket.native_handle(), reinterpret_cast(&remoteSockaddrStorage), sockaddrNameLength); +#elif CPPCORO_OS_DARWIN + // code to get socket error via getsockopt and SO_ERROR + int error = 0; + socklen_t len = sizeof(error); + int res = getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_ERROR, &error, &len); + if (res < 0) { + return -errno; + } + return error; +#endif }; +#if CPPCORO_OS_LINUX operation.m_ioService->get_io_context().watch_handle(m_socket.native_handle(), reinterpret_cast(&operation), cppcoro::detail::watch_type::writable); +#elif CPPCORO_OS_DARWIN + operation.m_ioService->get_io_context().watch_handle(m_socket.native_handle(), reinterpret_cast(&operation), cppcoro::detail::watch_type::readablewritable); +#endif return true; } diff --git a/lib/socket_disconnect_operation.cpp b/lib/socket_disconnect_operation.cpp index f17be37b..00ce8353 100644 --- a/lib/socket_disconnect_operation.cpp +++ b/lib/socket_disconnect_operation.cpp @@ -102,7 +102,7 @@ void cppcoro::net::socket_disconnect_operation_impl::get_result( }; } } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/socket_helpers.cpp b/lib/socket_helpers.cpp index a2bcf136..37bb8ac1 100644 --- a/lib/socket_helpers.cpp +++ b/lib/socket_helpers.cpp @@ -20,7 +20,7 @@ # include # include # include -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/socket_recv_from_operation.cpp b/lib/socket_recv_from_operation.cpp index 653ec6a3..bae90b44 100644 --- a/lib/socket_recv_from_operation.cpp +++ b/lib/socket_recv_from_operation.cpp @@ -93,7 +93,7 @@ cppcoro::net::socket_recv_from_operation_impl::get_result( *reinterpret_cast(&m_sourceSockaddrStorage))); } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include @@ -111,13 +111,39 @@ bool cppcoro::net::socket_recv_from_operation_impl::try_start( m_sourceSockaddrLength = sizeof(m_sourceSockaddrStorage); operation.m_completeFunc = [&]() { - return recvfrom( - m_socket.native_handle(), m_buffer, m_byteCount, MSG_TRUNC, +#if !CPPCORO_OS_LINUX + // NB: recvfrom(..., MSG_TRUNC) would be a more reliable way to do this on + // Linux, but isn't supported by POSIX. + int available; + socklen_t optlen = sizeof(available); + int err = getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_NREAD, &available, &optlen); + if (err != 0) { + return -1; + } + if (available > m_byteCount) { + errno = ENOMEM; + return -1; + } +#endif + int res = recvfrom( + m_socket.native_handle(), + m_buffer, + m_byteCount, + MSG_TRUNC, reinterpret_cast(&m_sourceSockaddrStorage), - reinterpret_cast(&m_sourceSockaddrLength) - ); + reinterpret_cast(&m_sourceSockaddrLength)); +#if !CPPCORO_OS_LINUX + if (res > 0) + { + res = available; + } +#endif + return res; }; - operation.m_ioService->get_io_context().watch_handle(m_socket.native_handle(), reinterpret_cast(&operation), cppcoro::detail::watch_type::readable); + operation.m_ioService->get_io_context().watch_handle( + m_socket.native_handle(), + reinterpret_cast(&operation), + cppcoro::detail::watch_type::readable); return true; } diff --git a/lib/socket_recv_operation.cpp b/lib/socket_recv_operation.cpp index 29f97247..e2a77680 100644 --- a/lib/socket_recv_operation.cpp +++ b/lib/socket_recv_operation.cpp @@ -60,7 +60,7 @@ bool cppcoro::net::socket_recv_operation_impl::try_start( return true; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/socket_send_operation.cpp b/lib/socket_send_operation.cpp index b494d28a..f9fb25ef 100644 --- a/lib/socket_send_operation.cpp +++ b/lib/socket_send_operation.cpp @@ -59,7 +59,7 @@ bool cppcoro::net::socket_send_operation_impl::try_start( return true; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/socket_send_to_operation.cpp b/lib/socket_send_to_operation.cpp index 2551af3b..4ce76d19 100644 --- a/lib/socket_send_to_operation.cpp +++ b/lib/socket_send_to_operation.cpp @@ -67,7 +67,7 @@ bool cppcoro::net::socket_send_to_operation_impl::try_start( return true; } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN # include # include # include diff --git a/lib/writable_file.cpp b/lib/writable_file.cpp index 1cffa427..9eaf92d0 100644 --- a/lib/writable_file.cpp +++ b/lib/writable_file.cpp @@ -43,13 +43,17 @@ void cppcoro::writable_file::set_size( }; } } -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include void cppcoro::writable_file::set_size( std::uint64_t fileSize) { +#if CPPCORO_OS_LINUX if (ftruncate64(m_fileHandle.handle(), fileSize) < 0) +#else + if (ftruncate(m_fileHandle.handle(), fileSize) < 0) +#endif { throw std::system_error { diff --git a/lib/write_only_file.cpp b/lib/write_only_file.cpp index 96bc9787..92a15fcb 100644 --- a/lib/write_only_file.cpp +++ b/lib/write_only_file.cpp @@ -10,7 +10,7 @@ # define WIN32_LEAN_AND_MEAN # endif # include -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN #include #endif @@ -24,7 +24,7 @@ cppcoro::write_only_file cppcoro::write_only_file::open( return write_only_file(file::open( #if CPPCORO_OS_WINNT GENERIC_WRITE, -#elif CPPCORO_OS_LINUX +#elif CPPCORO_OS_LINUX || CPPCORO_OS_DARWIN O_WRONLY, #endif ioService, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 528292bf..b6f6573f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,19 +45,26 @@ if(WIN32) file_tests.cpp socket_tests.cpp ) -else() - if(CMAKE_SYSTEM_NAME MATCHES "Linux") - list(APPEND tests - scheduling_operator_tests.cpp - io_service_tests.cpp - file_tests.cpp - socket_tests.cpp - ) - endif() - # let more time for some tests - set(async_auto_reset_event_tests_TIMEOUT 60) +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") + list(APPEND tests + scheduling_operator_tests.cpp + io_service_tests.cpp + file_tests.cpp + socket_tests.cpp + ) +elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") + list(APPEND tests + scheduling_operator_tests.cpp + io_service_tests.cpp + file_tests.cpp + socket_tests.cpp + ) endif() + +# let more time for some tests +set(async_auto_reset_event_tests_TIMEOUT 60) + foreach(test ${tests}) get_filename_component(test_name ${test} NAME_WE) add_executable(${test_name} ${test}) diff --git a/test/generator_tests.cpp b/test/generator_tests.cpp index e34f21a6..e7dc01bb 100644 --- a/test/generator_tests.cpp +++ b/test/generator_tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include