First
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
set(includes
|
||||
awaitable_traits.hpp
|
||||
is_awaitable.hpp
|
||||
async_auto_reset_event.hpp
|
||||
async_manual_reset_event.hpp
|
||||
async_generator.hpp
|
||||
async_mutex.hpp
|
||||
async_latch.hpp
|
||||
async_scope.hpp
|
||||
broken_promise.hpp
|
||||
cancellation_registration.hpp
|
||||
cancellation_source.hpp
|
||||
cancellation_token.hpp
|
||||
task.hpp
|
||||
sequence_barrier.hpp
|
||||
sequence_traits.hpp
|
||||
single_producer_sequencer.hpp
|
||||
multi_producer_sequencer.hpp
|
||||
shared_task.hpp
|
||||
shared_task.hpp
|
||||
single_consumer_event.hpp
|
||||
single_consumer_async_auto_reset_event.hpp
|
||||
sync_wait.hpp
|
||||
task.hpp
|
||||
io_service.hpp
|
||||
config.hpp
|
||||
on_scope_exit.hpp
|
||||
file_share_mode.hpp
|
||||
file_open_mode.hpp
|
||||
file_buffering_mode.hpp
|
||||
file.hpp
|
||||
fmap.hpp
|
||||
when_all.hpp
|
||||
when_all_ready.hpp
|
||||
resume_on.hpp
|
||||
schedule_on.hpp
|
||||
generator.hpp
|
||||
readable_file.hpp
|
||||
recursive_generator.hpp
|
||||
writable_file.hpp
|
||||
read_only_file.hpp
|
||||
write_only_file.hpp
|
||||
read_write_file.hpp
|
||||
file_read_operation.hpp
|
||||
file_write_operation.hpp
|
||||
static_thread_pool.hpp
|
||||
)
|
||||
list(TRANSFORM includes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/")
|
||||
|
||||
set(netIncludes
|
||||
ip_address.hpp
|
||||
ip_endpoint.hpp
|
||||
ipv4_address.hpp
|
||||
ipv4_endpoint.hpp
|
||||
ipv6_address.hpp
|
||||
ipv6_endpoint.hpp
|
||||
socket.hpp
|
||||
)
|
||||
list(TRANSFORM netIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/net/")
|
||||
|
||||
set(detailIncludes
|
||||
void_value.hpp
|
||||
when_all_ready_awaitable.hpp
|
||||
when_all_counter.hpp
|
||||
when_all_task.hpp
|
||||
get_awaiter.hpp
|
||||
is_awaiter.hpp
|
||||
any.hpp
|
||||
sync_wait_task.hpp
|
||||
unwrap_reference.hpp
|
||||
lightweight_manual_reset_event.hpp
|
||||
)
|
||||
list(TRANSFORM detailIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/detail/")
|
||||
|
||||
set(privateHeaders
|
||||
cancellation_state.hpp
|
||||
socket_helpers.hpp
|
||||
auto_reset_event.hpp
|
||||
spin_wait.hpp
|
||||
spin_mutex.hpp
|
||||
)
|
||||
|
||||
set(sources
|
||||
async_auto_reset_event.cpp
|
||||
async_manual_reset_event.cpp
|
||||
async_mutex.cpp
|
||||
cancellation_state.cpp
|
||||
cancellation_token.cpp
|
||||
cancellation_source.cpp
|
||||
cancellation_registration.cpp
|
||||
lightweight_manual_reset_event.cpp
|
||||
ip_address.cpp
|
||||
ip_endpoint.cpp
|
||||
ipv4_address.cpp
|
||||
ipv4_endpoint.cpp
|
||||
ipv6_address.cpp
|
||||
ipv6_endpoint.cpp
|
||||
static_thread_pool.cpp
|
||||
auto_reset_event.cpp
|
||||
spin_wait.cpp
|
||||
spin_mutex.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(win32DetailIncludes
|
||||
win32.hpp
|
||||
win32_overlapped_operation.hpp
|
||||
)
|
||||
list(TRANSFORM win32DetailIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/detail/")
|
||||
list(APPEND detailIncludes ${win32DetailIncludes})
|
||||
|
||||
set(win32NetIncludes
|
||||
socket.hpp
|
||||
socket_accept_operation.hpp
|
||||
socket_connect_operation.hpp
|
||||
socket_disconnect_operation.hpp
|
||||
socket_recv_operation.hpp
|
||||
socket_recv_from_operation.hpp
|
||||
socket_send_operation.hpp
|
||||
socket_send_to_operation.hpp
|
||||
)
|
||||
list(TRANSFORM win32NetIncludes PREPEND "${PROJECT_SOURCE_DIR}/include/cppcoro/net/")
|
||||
list(APPEND netIncludes ${win32NetIncludes})
|
||||
|
||||
set(win32Sources
|
||||
win32.cpp
|
||||
io_service.cpp
|
||||
file.cpp
|
||||
readable_file.cpp
|
||||
writable_file.cpp
|
||||
read_only_file.cpp
|
||||
write_only_file.cpp
|
||||
read_write_file.cpp
|
||||
file_read_operation.cpp
|
||||
file_write_operation.cpp
|
||||
socket_helpers.cpp
|
||||
socket.cpp
|
||||
socket_accept_operation.cpp
|
||||
socket_connect_operation.cpp
|
||||
socket_disconnect_operation.cpp
|
||||
socket_send_operation.cpp
|
||||
socket_send_to_operation.cpp
|
||||
socket_recv_operation.cpp
|
||||
socket_recv_from_operation.cpp
|
||||
)
|
||||
list(APPEND sources ${win32Sources})
|
||||
|
||||
list(APPEND libraries Ws2_32 Mswsock Synchronization)
|
||||
list(APPEND compile_options /EHsc)
|
||||
|
||||
if("${MSVC_VERSION}" VERSION_GREATER_EQUAL 1900)
|
||||
# TODO remove this when experimental/non-experimental include are fixed
|
||||
list(APPEND compile_definition _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING=1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(cppcoro
|
||||
${includes}
|
||||
${netIncludes}
|
||||
${detailIncludes}
|
||||
${privateHeaders}
|
||||
${sources}
|
||||
)
|
||||
|
||||
target_include_directories(cppcoro PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
target_compile_definitions(cppcoro PUBLIC ${compile_definition})
|
||||
target_compile_options(cppcoro PUBLIC ${compile_options})
|
||||
|
||||
#find_package(Coroutines COMPONENTS Experimental Final REQUIRED)
|
||||
#target_link_libraries(cppcoro PUBLIC std::coroutines ${libraries})
|
||||
|
||||
install(TARGETS cppcoro EXPORT cppcoroTargets
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
@@ -0,0 +1,285 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/async_auto_reset_event.hpp>
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
// Some helpers for manipulating the 'm_state' value.
|
||||
|
||||
constexpr std::uint64_t set_increment = 1;
|
||||
constexpr std::uint64_t waiter_increment = std::uint64_t(1) << 32;
|
||||
|
||||
constexpr std::uint32_t get_set_count(std::uint64_t state)
|
||||
{
|
||||
return static_cast<std::uint32_t>(state);
|
||||
}
|
||||
|
||||
constexpr std::uint32_t get_waiter_count(std::uint64_t state)
|
||||
{
|
||||
return static_cast<std::uint32_t>(state >> 32);
|
||||
}
|
||||
|
||||
constexpr std::uint32_t get_resumable_waiter_count(std::uint64_t state)
|
||||
{
|
||||
return std::min(get_set_count(state), get_waiter_count(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::async_auto_reset_event::async_auto_reset_event(bool initiallySet) noexcept
|
||||
: m_state(initiallySet ? local::set_increment : 0)
|
||||
, m_newWaiters(nullptr)
|
||||
, m_waiters(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
cppcoro::async_auto_reset_event::~async_auto_reset_event()
|
||||
{
|
||||
assert(m_newWaiters.load(std::memory_order_relaxed) == nullptr);
|
||||
assert(m_waiters == nullptr);
|
||||
}
|
||||
|
||||
cppcoro::async_auto_reset_event_operation
|
||||
cppcoro::async_auto_reset_event::operator co_await() const noexcept
|
||||
{
|
||||
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
|
||||
if (local::get_set_count(oldState) > local::get_waiter_count(oldState))
|
||||
{
|
||||
// Try to synchronously acquire the event.
|
||||
if (m_state.compare_exchange_strong(
|
||||
oldState,
|
||||
oldState - local::set_increment,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
// Acquired the event, return an operation object that
|
||||
// won't suspend.
|
||||
return async_auto_reset_event_operation{};
|
||||
}
|
||||
}
|
||||
|
||||
return async_auto_reset_event_operation{ *this };
|
||||
}
|
||||
|
||||
void cppcoro::async_auto_reset_event::set() noexcept
|
||||
{
|
||||
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
|
||||
do
|
||||
{
|
||||
if (local::get_set_count(oldState) > local::get_waiter_count(oldState))
|
||||
{
|
||||
// Already set.
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment the set-count
|
||||
} while (!m_state.compare_exchange_weak(
|
||||
oldState,
|
||||
oldState + local::set_increment,
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire));
|
||||
|
||||
// Did we transition from non-zero waiters and zero set-count
|
||||
// to non-zero set-count?
|
||||
// If so then we acquired the lock and are responsible for resuming waiters.
|
||||
if (oldState != 0 && local::get_set_count(oldState) == 0)
|
||||
{
|
||||
// We acquired the lock.
|
||||
resume_waiters(oldState + local::set_increment);
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::async_auto_reset_event::reset() noexcept
|
||||
{
|
||||
std::uint64_t oldState = m_state.load(std::memory_order_relaxed);
|
||||
while (local::get_set_count(oldState) > local::get_waiter_count(oldState))
|
||||
{
|
||||
if (m_state.compare_exchange_weak(
|
||||
oldState,
|
||||
oldState - local::set_increment,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
// Successfully reset.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not set. Nothing to do.
|
||||
}
|
||||
|
||||
void cppcoro::async_auto_reset_event::resume_waiters(
|
||||
std::uint64_t initialState) const noexcept
|
||||
{
|
||||
async_auto_reset_event_operation* waitersToResumeList = nullptr;
|
||||
async_auto_reset_event_operation** waitersToResumeListEnd = &waitersToResumeList;
|
||||
|
||||
std::uint32_t waiterCountToResume = local::get_resumable_waiter_count(initialState);
|
||||
|
||||
assert(waiterCountToResume > 0);
|
||||
|
||||
do
|
||||
{
|
||||
// Dequeue 'waiterCountToResume' from m_waiters/m_newWaiters and
|
||||
// push them onto 'waitersToResumeList'.
|
||||
for (std::uint32_t i = 0; i < waiterCountToResume; ++i)
|
||||
{
|
||||
if (m_waiters == nullptr)
|
||||
{
|
||||
// We've run out of of waiters that we can consume without synchronisation
|
||||
// Dequeue the list of new waiters atomically.
|
||||
auto* newWaiters = m_newWaiters.exchange(nullptr, std::memory_order_acquire);
|
||||
|
||||
// There should always be enough waiters in the list as
|
||||
// the waiters are queued before the waiter-count is incremented.
|
||||
assert(newWaiters != nullptr);
|
||||
CPPCORO_ASSUME(newWaiters != nullptr);
|
||||
|
||||
// Reverse order of new waiters so they are resumed in FIFO.
|
||||
// This ensures fairness.
|
||||
//
|
||||
// The alternative would be to not reverse the list and instead
|
||||
// resume waiters in the reverse order they were queued in.
|
||||
// This might result in better cache locality (most recently
|
||||
// suspended coroutine might still be in cache).
|
||||
// It should still provide a bounded wait time as well since we
|
||||
// are guaranteed to process all waiters in this list before
|
||||
// looking at any waiters newly queued after this point.
|
||||
// Something to consider.
|
||||
do
|
||||
{
|
||||
auto* next = newWaiters->m_next;
|
||||
newWaiters->m_next = m_waiters;
|
||||
m_waiters = newWaiters;
|
||||
newWaiters = next;
|
||||
} while (newWaiters != nullptr);
|
||||
}
|
||||
|
||||
assert(m_waiters != nullptr);
|
||||
|
||||
// Pop the next waiter off the list
|
||||
auto* waiterToResume = m_waiters;
|
||||
m_waiters = m_waiters->m_next;
|
||||
|
||||
// Push it onto the end of the list of waiters to resume
|
||||
waiterToResume->m_next = nullptr;
|
||||
*waitersToResumeListEnd = waiterToResume;
|
||||
waitersToResumeListEnd = &waiterToResume->m_next;
|
||||
}
|
||||
|
||||
// We've now removed 'waiterCountToResume' waiters from the list
|
||||
// so we can now decrement both the waiter and set count.
|
||||
//
|
||||
// However, there might have been more waiters or more calls to
|
||||
// set() since we last checked so we need to go around again if
|
||||
// there are still waiters that are ready to resume after decrementing
|
||||
// both the 'waiter count' and 'set count' by 'waiterCountToResume'.
|
||||
const std::uint64_t delta =
|
||||
std::uint64_t(waiterCountToResume) |
|
||||
std::uint64_t(waiterCountToResume) << 32;
|
||||
|
||||
// Needs to be 'release' as we're releasing the lock and anyone that
|
||||
// subsequently acquires the lock needs to see our prior writes to
|
||||
// m_waiters.
|
||||
// Needs to be 'acquire' in the case that new waiters were added so
|
||||
// that we see their prior writes to 'm_newWaiters'.
|
||||
const std::uint64_t newState =
|
||||
m_state.fetch_sub(delta, std::memory_order_acq_rel) - delta;
|
||||
|
||||
waiterCountToResume = local::get_resumable_waiter_count(newState);
|
||||
} while (waiterCountToResume > 0);
|
||||
|
||||
// Now resume all of the waiters we've dequeued.
|
||||
// There should be at least one.
|
||||
assert(waitersToResumeList != nullptr);
|
||||
CPPCORO_ASSUME(waitersToResumeList != nullptr);
|
||||
|
||||
do
|
||||
{
|
||||
auto* const waiter = waitersToResumeList;
|
||||
|
||||
// Read 'next' before resuming since resuming the waiter is
|
||||
// likely to destroy the waiter object.
|
||||
auto* const next = waitersToResumeList->m_next;
|
||||
|
||||
// Decrement reference count and see if we decremented the last
|
||||
// reference and if so then we are responsible for resuming.
|
||||
// If not, then await_suspend() is responsible for resuming by
|
||||
// returning 'false' and not suspending.
|
||||
if (waiter->m_refCount.fetch_sub(1, std::memory_order_release) == 1)
|
||||
{
|
||||
waiter->m_awaiter.resume();
|
||||
}
|
||||
|
||||
waitersToResumeList = next;
|
||||
} while (waitersToResumeList != nullptr);
|
||||
}
|
||||
|
||||
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation() noexcept
|
||||
: m_event(nullptr)
|
||||
{}
|
||||
|
||||
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation(
|
||||
const async_auto_reset_event& event) noexcept
|
||||
: m_event(&event)
|
||||
, m_refCount(2)
|
||||
{}
|
||||
|
||||
cppcoro::async_auto_reset_event_operation::async_auto_reset_event_operation(
|
||||
const async_auto_reset_event_operation& other) noexcept
|
||||
: m_event(other.m_event)
|
||||
, m_refCount(2)
|
||||
{}
|
||||
|
||||
bool cppcoro::async_auto_reset_event_operation::await_suspend(
|
||||
cppcoro::coroutine_handle<> awaiter) noexcept
|
||||
{
|
||||
m_awaiter = awaiter;
|
||||
|
||||
// Queue the waiter to the m_newWaiters list.
|
||||
async_auto_reset_event_operation* head = m_event->m_newWaiters.load(std::memory_order_relaxed);
|
||||
do
|
||||
{
|
||||
m_next = head;
|
||||
} while (!m_event->m_newWaiters.compare_exchange_weak(
|
||||
head,
|
||||
this,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed));
|
||||
|
||||
// Increment the waiter count.
|
||||
// Needs to be 'release' so that our prior write to m_newWaiters is
|
||||
// visible to anyone that acquires the lock.
|
||||
// Needs to be 'acquire' in case we acquired the lock so we can see
|
||||
// others' writes to m_newWaiters and writes prior to set() calls.
|
||||
const std::uint64_t oldState =
|
||||
m_event->m_state.fetch_add(local::waiter_increment, std::memory_order_acq_rel);
|
||||
|
||||
if (oldState != 0 && local::get_waiter_count(oldState) == 0)
|
||||
{
|
||||
// We transitioned from non-zero set and zero waiters to
|
||||
// non-zero set and non-zero waiters, so we acquired the lock
|
||||
// and thus responsibility for resuming waiters.
|
||||
m_event->resume_waiters(oldState + local::waiter_increment);
|
||||
}
|
||||
|
||||
// Decrement the ref-count to indicate that this waiter is now safe
|
||||
// to resume. We don't want it to resume while we're still accessing the
|
||||
// m_event object as resuming it might cause the event object to be
|
||||
// destructed.
|
||||
//
|
||||
// Need 'acquire' semantics here in the case that another thread has
|
||||
// concurrently dequeued us and scheduled us for resumption by decrementing
|
||||
// the ref-count with 'release' semantics so that we see the writes prior
|
||||
// to the 'set()' call that released this waiter.
|
||||
return m_refCount.fetch_sub(1, std::memory_order_acquire) != 1;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/async_manual_reset_event.hpp>
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
cppcoro::async_manual_reset_event::async_manual_reset_event(bool initiallySet) noexcept
|
||||
: m_state(initiallySet ? static_cast<void*>(this) : nullptr)
|
||||
{}
|
||||
|
||||
cppcoro::async_manual_reset_event::~async_manual_reset_event()
|
||||
{
|
||||
// There should be no coroutines still awaiting the event.
|
||||
assert(
|
||||
m_state.load(std::memory_order_relaxed) == nullptr ||
|
||||
m_state.load(std::memory_order_relaxed) == static_cast<void*>(this));
|
||||
}
|
||||
|
||||
bool cppcoro::async_manual_reset_event::is_set() const noexcept
|
||||
{
|
||||
return m_state.load(std::memory_order_acquire) == static_cast<const void*>(this);
|
||||
}
|
||||
|
||||
cppcoro::async_manual_reset_event_operation
|
||||
cppcoro::async_manual_reset_event::operator co_await() const noexcept
|
||||
{
|
||||
return async_manual_reset_event_operation{ *this };
|
||||
}
|
||||
|
||||
void cppcoro::async_manual_reset_event::set() noexcept
|
||||
{
|
||||
void* const setState = static_cast<void*>(this);
|
||||
|
||||
// Needs 'release' semantics so that prior writes are visible to event awaiters
|
||||
// that synchronise either via 'is_set()' or 'operator co_await()'.
|
||||
// Needs 'acquire' semantics in case there are any waiters so that we see
|
||||
// prior writes to the waiting coroutine's state and to the contents of
|
||||
// the queued async_manual_reset_event_operation objects.
|
||||
void* oldState = m_state.exchange(setState, std::memory_order_acq_rel);
|
||||
if (oldState != setState)
|
||||
{
|
||||
auto* current = static_cast<async_manual_reset_event_operation*>(oldState);
|
||||
while (current != nullptr)
|
||||
{
|
||||
auto* next = current->m_next;
|
||||
current->m_awaiter.resume();
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::async_manual_reset_event::reset() noexcept
|
||||
{
|
||||
void* oldState = static_cast<void*>(this);
|
||||
m_state.compare_exchange_strong(oldState, nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
cppcoro::async_manual_reset_event_operation::async_manual_reset_event_operation(
|
||||
const async_manual_reset_event& event) noexcept
|
||||
: m_event(event)
|
||||
{
|
||||
}
|
||||
|
||||
bool cppcoro::async_manual_reset_event_operation::await_ready() const noexcept
|
||||
{
|
||||
return m_event.is_set();
|
||||
}
|
||||
|
||||
bool cppcoro::async_manual_reset_event_operation::await_suspend(
|
||||
cppcoro::coroutine_handle<> awaiter) noexcept
|
||||
{
|
||||
m_awaiter = awaiter;
|
||||
|
||||
const void* const setState = static_cast<const void*>(&m_event);
|
||||
|
||||
void* oldState = m_event.m_state.load(std::memory_order_acquire);
|
||||
do
|
||||
{
|
||||
if (oldState == setState)
|
||||
{
|
||||
// State is now 'set' no need to suspend.
|
||||
return false;
|
||||
}
|
||||
|
||||
m_next = static_cast<async_manual_reset_event_operation*>(oldState);
|
||||
} while (!m_event.m_state.compare_exchange_weak(
|
||||
oldState,
|
||||
static_cast<void*>(this),
|
||||
std::memory_order_release,
|
||||
std::memory_order_acquire));
|
||||
|
||||
// Successfully queued this waiter to the list.
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/async_mutex.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
cppcoro::async_mutex::async_mutex() noexcept
|
||||
: m_state(not_locked)
|
||||
, m_waiters(nullptr)
|
||||
{}
|
||||
|
||||
cppcoro::async_mutex::~async_mutex()
|
||||
{
|
||||
[[maybe_unused]] auto state = m_state.load(std::memory_order_relaxed);
|
||||
assert(state == not_locked || state == locked_no_waiters);
|
||||
assert(m_waiters == nullptr);
|
||||
}
|
||||
|
||||
bool cppcoro::async_mutex::try_lock() noexcept
|
||||
{
|
||||
// Try to atomically transition from nullptr (not-locked) -> this (locked-no-waiters).
|
||||
auto oldState = not_locked;
|
||||
return m_state.compare_exchange_strong(
|
||||
oldState,
|
||||
locked_no_waiters,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
cppcoro::async_mutex_lock_operation cppcoro::async_mutex::lock_async() noexcept
|
||||
{
|
||||
return async_mutex_lock_operation{ *this };
|
||||
}
|
||||
|
||||
cppcoro::async_mutex_scoped_lock_operation cppcoro::async_mutex::scoped_lock_async() noexcept
|
||||
{
|
||||
return async_mutex_scoped_lock_operation{ *this };
|
||||
}
|
||||
|
||||
void cppcoro::async_mutex::unlock()
|
||||
{
|
||||
assert(m_state.load(std::memory_order_relaxed) != not_locked);
|
||||
|
||||
async_mutex_lock_operation* waitersHead = m_waiters;
|
||||
if (waitersHead == nullptr)
|
||||
{
|
||||
auto oldState = locked_no_waiters;
|
||||
const bool releasedLock = m_state.compare_exchange_strong(
|
||||
oldState,
|
||||
not_locked,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed);
|
||||
if (releasedLock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// At least one new waiter.
|
||||
// Acquire the list of new waiter operations atomically.
|
||||
oldState = m_state.exchange(locked_no_waiters, std::memory_order_acquire);
|
||||
|
||||
assert(oldState != locked_no_waiters && oldState != not_locked);
|
||||
|
||||
// Transfer the list to m_waiters, reversing the list in the process so
|
||||
// that the head of the list is the first to be resumed.
|
||||
auto* next = reinterpret_cast<async_mutex_lock_operation*>(oldState);
|
||||
do
|
||||
{
|
||||
auto* temp = next->m_next;
|
||||
next->m_next = waitersHead;
|
||||
waitersHead = next;
|
||||
next = temp;
|
||||
} while (next != nullptr);
|
||||
}
|
||||
|
||||
assert(waitersHead != nullptr);
|
||||
|
||||
m_waiters = waitersHead->m_next;
|
||||
|
||||
// Resume the waiter.
|
||||
// This will pass the ownership of the lock on to that operation/coroutine.
|
||||
waitersHead->m_awaiter.resume();
|
||||
}
|
||||
|
||||
bool cppcoro::async_mutex_lock_operation::await_suspend(cppcoro::coroutine_handle<> awaiter) noexcept
|
||||
{
|
||||
m_awaiter = awaiter;
|
||||
|
||||
std::uintptr_t oldState = m_mutex.m_state.load(std::memory_order_acquire);
|
||||
while (true)
|
||||
{
|
||||
if (oldState == async_mutex::not_locked)
|
||||
{
|
||||
if (m_mutex.m_state.compare_exchange_weak(
|
||||
oldState,
|
||||
async_mutex::locked_no_waiters,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
// Acquired lock, don't suspend.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to push this operation onto the head of the waiter stack.
|
||||
m_next = reinterpret_cast<async_mutex_lock_operation*>(oldState);
|
||||
if (m_mutex.m_state.compare_exchange_weak(
|
||||
oldState,
|
||||
reinterpret_cast<std::uintptr_t>(this),
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
// Queued operation to waiters list, suspend now.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "auto_reset_event.hpp"
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <Windows.h>
|
||||
# include <system_error>
|
||||
#endif
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
#if CPPCORO_OS_WINNT
|
||||
|
||||
auto_reset_event::auto_reset_event(bool initiallySet)
|
||||
: m_event(::CreateEventW(NULL, FALSE, initiallySet ? TRUE : FALSE, NULL))
|
||||
{
|
||||
if (m_event.handle() == NULL)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"auto_reset_event: CreateEvent failed"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
auto_reset_event::~auto_reset_event()
|
||||
{
|
||||
}
|
||||
|
||||
void auto_reset_event::set()
|
||||
{
|
||||
BOOL ok =::SetEvent(m_event.handle());
|
||||
if (!ok)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"auto_reset_event: SetEvent failed"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void auto_reset_event::wait()
|
||||
{
|
||||
DWORD result = ::WaitForSingleObjectEx(m_event.handle(), INFINITE, FALSE);
|
||||
if (result != WAIT_OBJECT_0)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"auto_reset_event: WaitForSingleObjectEx failed"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
auto_reset_event::auto_reset_event(bool initiallySet)
|
||||
: m_isSet(initiallySet)
|
||||
{}
|
||||
|
||||
auto_reset_event::~auto_reset_event()
|
||||
{}
|
||||
|
||||
void auto_reset_event::set()
|
||||
{
|
||||
std::unique_lock lock{ m_mutex };
|
||||
if (!m_isSet)
|
||||
{
|
||||
m_isSet = true;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void auto_reset_event::wait()
|
||||
{
|
||||
std::unique_lock lock{ m_mutex };
|
||||
while (!m_isSet)
|
||||
{
|
||||
m_cv.wait(lock);
|
||||
}
|
||||
m_isSet = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED
|
||||
#define CPPCORO_AUTO_RESET_EVENT_HPP_INCLUDED
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <cppcoro/detail/win32.hpp>
|
||||
#else
|
||||
# include <mutex>
|
||||
# include <condition_variable>
|
||||
#endif
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
class auto_reset_event
|
||||
{
|
||||
public:
|
||||
|
||||
auto_reset_event(bool initiallySet = false);
|
||||
|
||||
~auto_reset_event();
|
||||
|
||||
void set();
|
||||
|
||||
void wait();
|
||||
|
||||
private:
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
cppcoro::detail::win32::safe_handle m_event;
|
||||
#else
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
bool m_isSet;
|
||||
#endif
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
###############################################################################
|
||||
# Copyright Lewis Baker
|
||||
# Licenced under MIT license. See LICENSE.txt for details.
|
||||
###############################################################################
|
||||
|
||||
import cake.path
|
||||
|
||||
from cake.tools import compiler, script, env, project, variant
|
||||
|
||||
includes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', [
|
||||
'awaitable_traits.hpp',
|
||||
'is_awaitable.hpp',
|
||||
'async_auto_reset_event.hpp',
|
||||
'async_manual_reset_event.hpp',
|
||||
'async_generator.hpp',
|
||||
'async_mutex.hpp',
|
||||
'async_latch.hpp',
|
||||
'async_scope.hpp',
|
||||
'broken_promise.hpp',
|
||||
'cancellation_registration.hpp',
|
||||
'cancellation_source.hpp',
|
||||
'cancellation_token.hpp',
|
||||
'task.hpp',
|
||||
'sequence_barrier.hpp',
|
||||
'sequence_traits.hpp',
|
||||
'single_producer_sequencer.hpp',
|
||||
'multi_producer_sequencer.hpp',
|
||||
'shared_task.hpp',
|
||||
'single_consumer_event.hpp',
|
||||
'single_consumer_async_auto_reset_event.hpp',
|
||||
'sync_wait.hpp',
|
||||
'task.hpp',
|
||||
'io_service.hpp',
|
||||
'config.hpp',
|
||||
'on_scope_exit.hpp',
|
||||
'file_share_mode.hpp',
|
||||
'file_open_mode.hpp',
|
||||
'file_buffering_mode.hpp',
|
||||
'file.hpp',
|
||||
'fmap.hpp',
|
||||
'when_all.hpp',
|
||||
'when_all_ready.hpp',
|
||||
'resume_on.hpp',
|
||||
'schedule_on.hpp',
|
||||
'generator.hpp',
|
||||
'readable_file.hpp',
|
||||
'recursive_generator.hpp',
|
||||
'writable_file.hpp',
|
||||
'read_only_file.hpp',
|
||||
'write_only_file.hpp',
|
||||
'read_write_file.hpp',
|
||||
'file_read_operation.hpp',
|
||||
'file_write_operation.hpp',
|
||||
'static_thread_pool.hpp',
|
||||
])
|
||||
|
||||
netIncludes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'net', [
|
||||
'ip_address.hpp',
|
||||
'ip_endpoint.hpp',
|
||||
'ipv4_address.hpp',
|
||||
'ipv4_endpoint.hpp',
|
||||
'ipv6_address.hpp',
|
||||
'ipv6_endpoint.hpp',
|
||||
'socket.hpp',
|
||||
])
|
||||
|
||||
detailIncludes = cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'detail', [
|
||||
'void_value.hpp',
|
||||
'when_all_ready_awaitable.hpp',
|
||||
'when_all_counter.hpp',
|
||||
'when_all_task.hpp',
|
||||
'get_awaiter.hpp',
|
||||
'is_awaiter.hpp',
|
||||
'any.hpp',
|
||||
'sync_wait_task.hpp',
|
||||
'unwrap_reference.hpp',
|
||||
'lightweight_manual_reset_event.hpp',
|
||||
])
|
||||
|
||||
privateHeaders = script.cwd([
|
||||
'cancellation_state.hpp',
|
||||
'socket_helpers.hpp',
|
||||
'auto_reset_event.hpp',
|
||||
'spin_wait.hpp',
|
||||
'spin_mutex.hpp',
|
||||
])
|
||||
|
||||
sources = script.cwd([
|
||||
'async_auto_reset_event.cpp',
|
||||
'async_manual_reset_event.cpp',
|
||||
'async_mutex.cpp',
|
||||
'cancellation_state.cpp',
|
||||
'cancellation_token.cpp',
|
||||
'cancellation_source.cpp',
|
||||
'cancellation_registration.cpp',
|
||||
'lightweight_manual_reset_event.cpp',
|
||||
'ip_address.cpp',
|
||||
'ip_endpoint.cpp',
|
||||
'ipv4_address.cpp',
|
||||
'ipv4_endpoint.cpp',
|
||||
'ipv6_address.cpp',
|
||||
'ipv6_endpoint.cpp',
|
||||
'static_thread_pool.cpp',
|
||||
'auto_reset_event.cpp',
|
||||
'spin_wait.cpp',
|
||||
'spin_mutex.cpp',
|
||||
])
|
||||
|
||||
extras = script.cwd([
|
||||
'build.cake',
|
||||
'use.cake',
|
||||
])
|
||||
|
||||
if variant.platform == "windows":
|
||||
detailIncludes.extend(cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'detail', [
|
||||
'win32.hpp',
|
||||
'win32_overlapped_operation.hpp',
|
||||
]))
|
||||
netIncludes.extend(cake.path.join(env.expand('${CPPCORO}'), 'include', 'cppcoro', 'net', [
|
||||
'socket.hpp',
|
||||
'socket_accept_operation.hpp',
|
||||
'socket_connect_operation.hpp',
|
||||
'socket_disconnect_operation.hpp',
|
||||
'socket_recv_operation.hpp',
|
||||
'socket_recv_from_operation.hpp',
|
||||
'socket_send_operation.hpp',
|
||||
'socket_send_to_operation.hpp',
|
||||
]))
|
||||
sources.extend(script.cwd([
|
||||
'win32.cpp',
|
||||
'io_service.cpp',
|
||||
'file.cpp',
|
||||
'readable_file.cpp',
|
||||
'writable_file.cpp',
|
||||
'read_only_file.cpp',
|
||||
'write_only_file.cpp',
|
||||
'read_write_file.cpp',
|
||||
'file_read_operation.cpp',
|
||||
'file_write_operation.cpp',
|
||||
'socket_helpers.cpp',
|
||||
'socket.cpp',
|
||||
'socket_accept_operation.cpp',
|
||||
'socket_connect_operation.cpp',
|
||||
'socket_disconnect_operation.cpp',
|
||||
'socket_send_operation.cpp',
|
||||
'socket_send_to_operation.cpp',
|
||||
'socket_recv_operation.cpp',
|
||||
'socket_recv_from_operation.cpp',
|
||||
]))
|
||||
|
||||
buildDir = env.expand('${CPPCORO_BUILD}')
|
||||
|
||||
compiler.addIncludePath(env.expand('${CPPCORO}/include'))
|
||||
|
||||
objects = compiler.objects(
|
||||
targetDir=env.expand('${CPPCORO_BUILD}/obj'),
|
||||
sources=sources,
|
||||
)
|
||||
|
||||
lib = compiler.library(
|
||||
target=env.expand('${CPPCORO_LIB}/cppcoro'),
|
||||
sources=objects,
|
||||
)
|
||||
|
||||
vcproj = project.project(
|
||||
target=env.expand('${CPPCORO_PROJECT}/cppcoro'),
|
||||
items={
|
||||
'Include': {
|
||||
'Detail': detailIncludes,
|
||||
'Net': netIncludes,
|
||||
'': includes,
|
||||
},
|
||||
'Source': sources + privateHeaders,
|
||||
'': extras
|
||||
},
|
||||
output=lib,
|
||||
)
|
||||
|
||||
script.setResult(
|
||||
project=vcproj,
|
||||
library=lib,
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/cancellation_registration.hpp>
|
||||
|
||||
#include "cancellation_state.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
cppcoro::cancellation_registration::~cancellation_registration()
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->deregister_callback(this);
|
||||
m_state->release_token_ref();
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::cancellation_registration::register_callback(cancellation_token&& token)
|
||||
{
|
||||
auto* state = token.m_state;
|
||||
if (state != nullptr && state->can_be_cancelled())
|
||||
{
|
||||
m_state = state;
|
||||
if (state->try_register_callback(this))
|
||||
{
|
||||
token.m_state = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = nullptr;
|
||||
m_callback();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/cancellation_source.hpp>
|
||||
|
||||
#include "cancellation_state.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
cppcoro::cancellation_source::cancellation_source()
|
||||
: m_state(detail::cancellation_state::create())
|
||||
{
|
||||
}
|
||||
|
||||
cppcoro::cancellation_source::cancellation_source(const cancellation_source& other) noexcept
|
||||
: m_state(other.m_state)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->add_source_ref();
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::cancellation_source::cancellation_source(cancellation_source&& other) noexcept
|
||||
: m_state(other.m_state)
|
||||
{
|
||||
other.m_state = nullptr;
|
||||
}
|
||||
|
||||
cppcoro::cancellation_source::~cancellation_source()
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_source_ref();
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(const cancellation_source& other) noexcept
|
||||
{
|
||||
if (m_state != other.m_state)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_source_ref();
|
||||
}
|
||||
|
||||
m_state = other.m_state;
|
||||
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->add_source_ref();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
cppcoro::cancellation_source& cppcoro::cancellation_source::operator=(cancellation_source&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_source_ref();
|
||||
}
|
||||
|
||||
m_state = other.m_state;
|
||||
other.m_state = nullptr;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool cppcoro::cancellation_source::can_be_cancelled() const noexcept
|
||||
{
|
||||
return m_state != nullptr;
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token cppcoro::cancellation_source::token() const noexcept
|
||||
{
|
||||
return cancellation_token(m_state);
|
||||
}
|
||||
|
||||
void cppcoro::cancellation_source::request_cancellation()
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->request_cancellation();
|
||||
}
|
||||
}
|
||||
|
||||
bool cppcoro::cancellation_source::is_cancellation_requested() const noexcept
|
||||
{
|
||||
return m_state != nullptr && m_state->is_cancellation_requested();
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cancellation_state.hpp"
|
||||
|
||||
#include "cppcoro/config.hpp"
|
||||
|
||||
#include <cppcoro/cancellation_registration.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct cancellation_registration_list_chunk
|
||||
{
|
||||
static cancellation_registration_list_chunk* allocate(std::uint32_t entryCount);
|
||||
static void free(cancellation_registration_list_chunk* chunk) noexcept;
|
||||
|
||||
std::atomic<cancellation_registration_list_chunk*> m_nextChunk;
|
||||
cancellation_registration_list_chunk* m_prevChunk;
|
||||
std::atomic<std::int32_t> m_approximateFreeCount;
|
||||
std::uint32_t m_entryCount;
|
||||
std::atomic<cancellation_registration*> m_entries[1];
|
||||
};
|
||||
|
||||
struct cancellation_registration_list
|
||||
{
|
||||
static cancellation_registration_list* allocate();
|
||||
static void free(cancellation_registration_list* bucket) noexcept;
|
||||
|
||||
std::atomic<cancellation_registration_list_chunk*> m_approximateTail;
|
||||
cancellation_registration_list_chunk m_headChunk;
|
||||
};
|
||||
|
||||
struct cancellation_registration_result
|
||||
{
|
||||
cancellation_registration_result(
|
||||
cancellation_registration_list_chunk* chunk,
|
||||
std::uint32_t entryIndex)
|
||||
: m_chunk(chunk)
|
||||
, m_entryIndex(entryIndex)
|
||||
{}
|
||||
|
||||
cancellation_registration_list_chunk* m_chunk;
|
||||
std::uint32_t m_entryIndex;
|
||||
};
|
||||
|
||||
struct cancellation_registration_state
|
||||
{
|
||||
static cancellation_registration_state* allocate();
|
||||
static void free(cancellation_registration_state* list) noexcept;
|
||||
|
||||
cancellation_registration_result add_registration(
|
||||
cppcoro::cancellation_registration* registration);
|
||||
|
||||
std::thread::id m_notificationThreadId;
|
||||
|
||||
// Store N separate lists and randomly apportion threads to a given
|
||||
// list to reduce chance of contention.
|
||||
std::uint32_t m_listCount;
|
||||
std::atomic<cancellation_registration_list*> m_lists[1];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_registration_list_chunk*
|
||||
cppcoro::detail::cancellation_registration_list_chunk::allocate(std::uint32_t entryCount)
|
||||
{
|
||||
auto* chunk = static_cast<cancellation_registration_list_chunk*>(std::malloc(
|
||||
sizeof(cancellation_registration_list_chunk) +
|
||||
(entryCount - 1) * sizeof(cancellation_registration_list_chunk::m_entries[0])));
|
||||
if (chunk == nullptr)
|
||||
{
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
::new (&chunk->m_nextChunk) std::atomic<cancellation_registration_list_chunk*>(nullptr);
|
||||
chunk->m_prevChunk = nullptr;
|
||||
::new (&chunk->m_approximateFreeCount) std::atomic<int32_t>(static_cast<std::int32_t>(entryCount - 1));
|
||||
chunk->m_entryCount = entryCount;
|
||||
for (std::uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
::new (&chunk->m_entries[i]) std::atomic<cancellation_registration*>(nullptr);
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_registration_list_chunk::free(
|
||||
cancellation_registration_list_chunk* chunk) noexcept
|
||||
{
|
||||
std::free(chunk);
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_registration_list*
|
||||
cppcoro::detail::cancellation_registration_list::allocate()
|
||||
{
|
||||
constexpr std::uint32_t initialChunkSize = 16;
|
||||
|
||||
const std::size_t bufferSize =
|
||||
sizeof(cancellation_registration_list) +
|
||||
(initialChunkSize - 1) * sizeof(cancellation_registration_list_chunk::m_entries[0]);
|
||||
|
||||
auto* bucket = static_cast<cancellation_registration_list*>(std::malloc(bufferSize));
|
||||
if (bucket == nullptr)
|
||||
{
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
::new (&bucket->m_approximateTail) std::atomic<cancellation_registration_list_chunk*>(&bucket->m_headChunk);
|
||||
::new (&bucket->m_headChunk.m_nextChunk) std::atomic<cancellation_registration_list_chunk*>(nullptr);
|
||||
bucket->m_headChunk.m_prevChunk = nullptr;
|
||||
::new (&bucket->m_headChunk.m_approximateFreeCount)
|
||||
std::atomic<int32_t>(static_cast<std::int32_t>(initialChunkSize - 1));
|
||||
bucket->m_headChunk.m_entryCount = initialChunkSize;
|
||||
for (std::uint32_t i = 0; i < initialChunkSize; ++i)
|
||||
{
|
||||
::new (&bucket->m_headChunk.m_entries[i]) std::atomic<cancellation_registration*>(nullptr);
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_registration_list::free(cancellation_registration_list* list) noexcept
|
||||
{
|
||||
std::free(list);
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_registration_state*
|
||||
cppcoro::detail::cancellation_registration_state::allocate()
|
||||
{
|
||||
constexpr std::uint32_t maxListCount = 16;
|
||||
|
||||
auto listCount = std::thread::hardware_concurrency();
|
||||
if (listCount > maxListCount)
|
||||
{
|
||||
listCount = maxListCount;
|
||||
}
|
||||
else if (listCount == 0)
|
||||
{
|
||||
listCount = 1;
|
||||
}
|
||||
|
||||
const std::size_t bufferSize =
|
||||
sizeof(cancellation_registration_state) +
|
||||
(listCount - 1) * sizeof(cancellation_registration_state::m_lists[0]);
|
||||
|
||||
auto* state = static_cast<cancellation_registration_state*>(std::malloc(bufferSize));
|
||||
if (state == nullptr)
|
||||
{
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
state->m_listCount = listCount;
|
||||
for (std::uint32_t i = 0; i < listCount; ++i)
|
||||
{
|
||||
::new (&state->m_lists[i]) std::atomic<cancellation_registration_list*>(nullptr);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_registration_state::free(cancellation_registration_state* state) noexcept
|
||||
{
|
||||
std::free(state);
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_registration_result
|
||||
cppcoro::detail::cancellation_registration_state::add_registration(
|
||||
cppcoro::cancellation_registration* registration)
|
||||
{
|
||||
// Pick a list to add to based on the current thread to reduce the
|
||||
// chance of contention with multiple threads concurrently registering
|
||||
// callbacks.
|
||||
const auto threadIdHashCode = std::hash<std::thread::id>{}(std::this_thread::get_id());
|
||||
auto& listPtr = m_lists[threadIdHashCode % m_listCount];
|
||||
|
||||
auto* list = listPtr.load(std::memory_order_acquire);
|
||||
if (list == nullptr)
|
||||
{
|
||||
auto* newList = cancellation_registration_list::allocate();
|
||||
|
||||
// Pre-claim the first slot.
|
||||
registration->m_chunk = &newList->m_headChunk;
|
||||
registration->m_entryIndex = 0;
|
||||
::new (&newList->m_headChunk.m_entries[0]) std::atomic<cancellation_registration*>(registration);
|
||||
|
||||
if (listPtr.compare_exchange_strong(
|
||||
list,
|
||||
newList,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_acquire))
|
||||
{
|
||||
return cancellation_registration_result(&newList->m_headChunk, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cancellation_registration_list::free(newList);
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Navigate to the end of the chain of chunks and work backwards looking for a free slot.
|
||||
auto* const originalLastChunk = list->m_approximateTail.load(std::memory_order_acquire);
|
||||
|
||||
auto* lastChunk = originalLastChunk;
|
||||
for (auto* next = lastChunk->m_nextChunk.load(std::memory_order_acquire);
|
||||
next != nullptr;
|
||||
next = next->m_nextChunk.load(std::memory_order_acquire))
|
||||
{
|
||||
lastChunk = next;
|
||||
}
|
||||
|
||||
// Work around false-warning raised by MSVC static analysis complaining that
|
||||
// warning C28182: Dereferencing NULL pointer. 'lastChunk' contains the same NULL value as 'chunk' did.
|
||||
// on statement initialising 'elementCount' below.
|
||||
CPPCORO_ASSUME(lastChunk != nullptr);
|
||||
|
||||
if (lastChunk != originalLastChunk)
|
||||
{
|
||||
// Update the cache of last chunk pointer so that subsequent
|
||||
// registration requests can start there instead.
|
||||
// Doesn't matter if these writes race as it will eventually
|
||||
// converge to the true last chunk.
|
||||
list->m_approximateTail.store(lastChunk, std::memory_order_release);
|
||||
}
|
||||
|
||||
for (auto* chunk = lastChunk;
|
||||
chunk != nullptr;
|
||||
chunk = chunk->m_prevChunk)
|
||||
{
|
||||
auto freeCount = chunk->m_approximateFreeCount.load(std::memory_order_relaxed);
|
||||
|
||||
// If it looks like there are no free slots then decrement the count again
|
||||
// to force it to re-search every so-often, just in case the count has gotten
|
||||
// out-of-sync with the true free count and is reporting none free even though
|
||||
// there are some (or possibly all) free slots.
|
||||
if (freeCount < 1)
|
||||
{
|
||||
--freeCount;
|
||||
chunk->m_approximateFreeCount.store(freeCount, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
constexpr std::int32_t forcedSearchThreshold = -10;
|
||||
if (freeCount > 0 || freeCount < forcedSearchThreshold)
|
||||
{
|
||||
const std::uint32_t entryCount = chunk->m_entryCount;
|
||||
const std::uint32_t indexMask = entryCount - 1;
|
||||
const std::uint32_t startIndex = entryCount - freeCount;
|
||||
|
||||
registration->m_chunk = chunk;
|
||||
|
||||
for (std::uint32_t i = 0; i < entryCount; ++i)
|
||||
{
|
||||
const std::uint32_t entryIndex = (startIndex + i) & indexMask;
|
||||
auto& entry = chunk->m_entries[entryIndex];
|
||||
|
||||
// Do a cheap initial read of the entry value to see if the
|
||||
// entry is likely free. This can potentially read stale values
|
||||
// and so may lead to falsely thinking it's free or falsely
|
||||
// thinking it's occupied. But approximate is good enough here.
|
||||
auto* entryValue = entry.load(std::memory_order_relaxed);
|
||||
if (entryValue == nullptr)
|
||||
{
|
||||
registration->m_entryIndex = entryIndex;
|
||||
|
||||
if (entry.compare_exchange_strong(
|
||||
entryValue,
|
||||
registration,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
// Successfully claimed the slot.
|
||||
const std::int32_t newFreeCount = freeCount < 0 ? 0 : freeCount - 1;
|
||||
chunk->m_approximateFreeCount.store(newFreeCount, std::memory_order_relaxed);
|
||||
return cancellation_registration_result(chunk, entryIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read through all elements of chunk with no success.
|
||||
// Clear free-count back to 0.
|
||||
chunk->m_approximateFreeCount.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// We've traversed through all of the chunks and found no free slots.
|
||||
// So try and allocate a new chunk and append it to the list.
|
||||
|
||||
constexpr std::uint32_t maxElementCount = 1024;
|
||||
|
||||
const std::uint32_t elementCount =
|
||||
lastChunk->m_entryCount < maxElementCount ?
|
||||
lastChunk->m_entryCount * 2 : maxElementCount;
|
||||
|
||||
// May throw std::bad_alloc if out of memory.
|
||||
auto* newChunk = cancellation_registration_list_chunk::allocate(elementCount);
|
||||
newChunk->m_prevChunk = lastChunk;
|
||||
|
||||
// Pre-allocate first slot.
|
||||
registration->m_chunk = newChunk;
|
||||
registration->m_entryIndex = 0;
|
||||
::new (&newChunk->m_entries[0]) std::atomic<cancellation_registration*>(registration);
|
||||
|
||||
cancellation_registration_list_chunk* oldNext = nullptr;
|
||||
if (lastChunk->m_nextChunk.compare_exchange_strong(
|
||||
oldNext,
|
||||
newChunk,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
list->m_approximateTail.store(newChunk, std::memory_order_release);
|
||||
return cancellation_registration_result(newChunk, 0);
|
||||
}
|
||||
|
||||
// Some other thread published a new chunk to the end of the list
|
||||
// concurrently. Free our chunk and go around the loop again, hopefully
|
||||
// allocating a slot from the chunk the other thread just allocated.
|
||||
cancellation_registration_list_chunk::free(newChunk);
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_state* cppcoro::detail::cancellation_state::create()
|
||||
{
|
||||
return new cancellation_state();
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_state::~cancellation_state()
|
||||
{
|
||||
assert((m_state.load(std::memory_order_relaxed) & cancellation_ref_count_mask) == 0);
|
||||
|
||||
// Use relaxed memory order in reads here since we should already have visibility
|
||||
// to all writes as the ref-count decrement that preceded the call to the destructor
|
||||
// has acquire-release semantics.
|
||||
|
||||
auto* registrationState = m_registrationState.load(std::memory_order_relaxed);
|
||||
if (registrationState != nullptr)
|
||||
{
|
||||
for (std::uint32_t i = 0; i < registrationState->m_listCount; ++i)
|
||||
{
|
||||
auto* list = registrationState->m_lists[i].load(std::memory_order_relaxed);
|
||||
if (list != nullptr)
|
||||
{
|
||||
auto* chunk = list->m_headChunk.m_nextChunk.load(std::memory_order_relaxed);
|
||||
cancellation_registration_list::free(list);
|
||||
|
||||
while (chunk != nullptr)
|
||||
{
|
||||
auto* next = chunk->m_nextChunk.load(std::memory_order_relaxed);
|
||||
cancellation_registration_list_chunk::free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancellation_registration_state::free(registrationState);
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::add_token_ref() noexcept
|
||||
{
|
||||
m_state.fetch_add(cancellation_token_ref_increment, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::release_token_ref() noexcept
|
||||
{
|
||||
const std::uint64_t oldState = m_state.fetch_sub(cancellation_token_ref_increment, std::memory_order_acq_rel);
|
||||
if ((oldState & cancellation_ref_count_mask) == cancellation_token_ref_increment)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::add_source_ref() noexcept
|
||||
{
|
||||
m_state.fetch_add(cancellation_source_ref_increment, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::release_source_ref() noexcept
|
||||
{
|
||||
const std::uint64_t oldState = m_state.fetch_sub(cancellation_source_ref_increment, std::memory_order_acq_rel);
|
||||
if ((oldState & cancellation_ref_count_mask) == cancellation_source_ref_increment)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
bool cppcoro::detail::cancellation_state::can_be_cancelled() const noexcept
|
||||
{
|
||||
return (m_state.load(std::memory_order_acquire) & can_be_cancelled_mask) != 0;
|
||||
}
|
||||
|
||||
bool cppcoro::detail::cancellation_state::is_cancellation_requested() const noexcept
|
||||
{
|
||||
return (m_state.load(std::memory_order_acquire) & cancellation_requested_flag) != 0;
|
||||
}
|
||||
|
||||
bool cppcoro::detail::cancellation_state::is_cancellation_notification_complete() const noexcept
|
||||
{
|
||||
return (m_state.load(std::memory_order_acquire) & cancellation_notification_complete_flag) != 0;
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::request_cancellation()
|
||||
{
|
||||
const auto oldState = m_state.fetch_or(cancellation_requested_flag, std::memory_order_seq_cst);
|
||||
if ((oldState & cancellation_requested_flag) != 0)
|
||||
{
|
||||
// Some thread has already called request_cancellation().
|
||||
return;
|
||||
}
|
||||
|
||||
// We are the first caller of request_cancellation.
|
||||
// Need to execute any registered callbacks to notify them of cancellation.
|
||||
|
||||
// NOTE: We need to use sequentially-consistent operations here to ensure
|
||||
// that if there is a concurrent call to try_register_callback() on another
|
||||
// thread that either the other thread will read the prior write to m_state
|
||||
// after they write to a registration slot or we will read their write to the
|
||||
// registration slot after the prior write to m_state.
|
||||
|
||||
auto* const registrationState = m_registrationState.load(std::memory_order_seq_cst);
|
||||
if (registrationState != nullptr)
|
||||
{
|
||||
// Note that there should be no data-race in writing to this value here
|
||||
// as another thread will only read it if they are trying to deregister
|
||||
// a callback and that fails because we have acquired the pointer to
|
||||
// the registration inside the loop below. In this case the atomic
|
||||
// exchange that acquires the pointer below acts as a release-operation
|
||||
// that synchronises with the failed exchange operation in deregister_callback()
|
||||
// which has acquire semantics and thus will have visibility of the write to
|
||||
// the m_notificationThreadId value.
|
||||
registrationState->m_notificationThreadId = std::this_thread::get_id();
|
||||
|
||||
for (std::uint32_t listIndex = 0, listCount = registrationState->m_listCount;
|
||||
listIndex < listCount;
|
||||
++listIndex)
|
||||
{
|
||||
auto* list = registrationState->m_lists[listIndex].load(std::memory_order_seq_cst);
|
||||
if (list == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* chunk = &list->m_headChunk;
|
||||
do
|
||||
{
|
||||
for (std::uint32_t entryIndex = 0, entryCount = chunk->m_entryCount;
|
||||
entryIndex < entryCount;
|
||||
++entryIndex)
|
||||
{
|
||||
auto& entry = chunk->m_entries[entryIndex];
|
||||
|
||||
// Quick read-only operation to check if any registration
|
||||
// is present.
|
||||
auto* registration = entry.load(std::memory_order_seq_cst);
|
||||
if (registration != nullptr)
|
||||
{
|
||||
// Try to acquire ownership of the registration by replacing its
|
||||
// slot with nullptr atomically. This resolves the race between
|
||||
// a concurrent call to deregister_callback() from the registration's
|
||||
// destructor.
|
||||
registration = entry.exchange(nullptr, std::memory_order_seq_cst);
|
||||
if (registration != nullptr)
|
||||
{
|
||||
try
|
||||
{
|
||||
registration->m_callback();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// TODO: What should behaviour of unhandled exception in a callback be here?
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chunk = chunk->m_nextChunk.load(std::memory_order_seq_cst);
|
||||
} while (chunk != nullptr);
|
||||
}
|
||||
|
||||
m_state.fetch_add(cancellation_notification_complete_flag, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
bool cppcoro::detail::cancellation_state::try_register_callback(
|
||||
cancellation_registration* registration)
|
||||
{
|
||||
if (is_cancellation_requested())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* registrationState = m_registrationState.load(std::memory_order_acquire);
|
||||
if (registrationState == nullptr)
|
||||
{
|
||||
// Could throw std::bad_alloc
|
||||
auto* newRegistrationState = cancellation_registration_state::allocate();
|
||||
|
||||
// Need to use 'sequentially consistent' on the write here to ensure that if
|
||||
// we subsequently read a value from m_state at the end of this function that
|
||||
// doesn't have the cancellation_requested_flag bit set that a subsequent call
|
||||
// in another thread to request_cancellation() will see this write.
|
||||
if (m_registrationState.compare_exchange_strong(
|
||||
registrationState,
|
||||
newRegistrationState,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_acquire))
|
||||
{
|
||||
registrationState = newRegistrationState;
|
||||
}
|
||||
else
|
||||
{
|
||||
cancellation_registration_state::free(newRegistrationState);
|
||||
}
|
||||
}
|
||||
|
||||
// Could throw std::bad_alloc
|
||||
auto result = registrationState->add_registration(registration);
|
||||
|
||||
// Need to check status again to handle the case where
|
||||
// another thread calls request_cancellation() concurrently
|
||||
// but doesn't see our write to the registration list.
|
||||
//
|
||||
// Note, we don't call IsCancellationRequested() here since that
|
||||
// only provides 'acquire' memory semantics and we need 'seq_cst'
|
||||
// semantics.
|
||||
if ((m_state.load(std::memory_order_seq_cst) & cancellation_requested_flag) != 0)
|
||||
{
|
||||
// Cancellation was requested concurrently with adding the
|
||||
// registration to the list. Try to remove the registration.
|
||||
// If successful we return false to indicate that the callback
|
||||
// has not been registered and the caller should execute the
|
||||
// callback. If it fails it means that the thread that requested
|
||||
// cancellation will execute our callback and we need to wait
|
||||
// until it finishes before returning.
|
||||
auto& entry = result.m_chunk->m_entries[result.m_entryIndex];
|
||||
|
||||
// Need to use compare_exchange here rather than just exchange since
|
||||
// it may be possible that the thread calling request_cancellation()
|
||||
// acquired our registration and executed the callback, freeing up
|
||||
// the slot and then a third thread registers a new registration
|
||||
// that gets allocated to this slot.
|
||||
//
|
||||
// Can use relaxed memory order here since in the case that this succeeds
|
||||
// no other thread will have written to the cancellation_registration record
|
||||
// so we can safely read from the record without synchronisation.
|
||||
auto* oldValue = registration;
|
||||
const bool deregisteredSuccessfully =
|
||||
entry.compare_exchange_strong(oldValue, nullptr, std::memory_order_relaxed);
|
||||
if (deregisteredSuccessfully)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, the cancelling thread has taken ownership for executing
|
||||
// the callback and we can just act as if the registration succeeded.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::detail::cancellation_state::deregister_callback(cancellation_registration* registration) noexcept
|
||||
{
|
||||
auto* chunk = registration->m_chunk;
|
||||
auto& entry = chunk->m_entries[registration->m_entryIndex];
|
||||
|
||||
// Use 'acquire' memory order on failure case so that we synchronise with the write
|
||||
// to the slot inside request_cancellation() that acquired the registration such that
|
||||
// we have visibility of its prior write to m_notifyingThreadId.
|
||||
//
|
||||
// Could use 'relaxed' memory order on success case as if this succeeds it means that
|
||||
// no thread will have written to the registration object.
|
||||
auto* oldValue = registration;
|
||||
bool deregisteredSuccessfully = entry.compare_exchange_strong(
|
||||
oldValue,
|
||||
nullptr,
|
||||
std::memory_order_acquire);
|
||||
if (deregisteredSuccessfully)
|
||||
{
|
||||
// Increment free-count if it won't make it larger than entry count.
|
||||
const std::int32_t oldFreeCount = chunk->m_approximateFreeCount.load(std::memory_order_relaxed);
|
||||
if (oldFreeCount < static_cast<std::int32_t>(chunk->m_entryCount))
|
||||
{
|
||||
const std::int32_t newFreeCount = oldFreeCount < 0 ? 1 : oldFreeCount + 1;
|
||||
chunk->m_approximateFreeCount.store(newFreeCount, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// A thread executing request_cancellation() has acquired this callback and
|
||||
// is executing it. Need to wait until it finishes executing before we return
|
||||
// and the registration object is destructed.
|
||||
//
|
||||
// However, we also need to handle the case where the registration is being
|
||||
// removed from within a callback which would otherwise deadlock waiting
|
||||
// for the callbacks to finish executing.
|
||||
|
||||
// Use relaxed memory order here as we should already have visibility
|
||||
// of the write to m_registrationState from when the registration was first
|
||||
// registered.
|
||||
auto* registrationState = m_registrationState.load(std::memory_order_relaxed);
|
||||
if (std::this_thread::get_id() != registrationState->m_notificationThreadId)
|
||||
{
|
||||
// TODO: More efficient busy-wait backoff strategy
|
||||
while (!is_cancellation_notification_complete())
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::detail::cancellation_state::cancellation_state() noexcept
|
||||
: m_state(cancellation_source_ref_increment)
|
||||
, m_registrationState(nullptr)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef CPPCORO_CANCELLATION_STATE_HPP_INCLUDED
|
||||
#define CPPCORO_CANCELLATION_STATE_HPP_INCLUDED
|
||||
|
||||
#include <cppcoro/cancellation_token.hpp>
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct cancellation_registration_state;
|
||||
|
||||
class cancellation_state
|
||||
{
|
||||
public:
|
||||
|
||||
/// Allocates a new cancellation_state object.
|
||||
///
|
||||
/// \throw std::bad_alloc
|
||||
/// If there was insufficient memory to allocate one.
|
||||
static cancellation_state* create();
|
||||
|
||||
~cancellation_state();
|
||||
|
||||
/// Increment the reference count of cancellation_token and
|
||||
/// cancellation_registration objects referencing this state.
|
||||
void add_token_ref() noexcept;
|
||||
|
||||
/// Decrement the reference count of cancellation_token and
|
||||
/// cancellation_registration objects referencing this state.
|
||||
void release_token_ref() noexcept;
|
||||
|
||||
/// Increment the reference count of cancellation_source objects.
|
||||
void add_source_ref() noexcept;
|
||||
|
||||
/// Decrement the reference count of cancellation_souce objects.
|
||||
///
|
||||
/// The cancellation_state will no longer be cancellable once the
|
||||
/// cancellation_source ref count reaches zero.
|
||||
void release_source_ref() noexcept;
|
||||
|
||||
/// Query if the cancellation_state can have cancellation requested.
|
||||
///
|
||||
/// \return
|
||||
/// Returns true if there are no more references to a cancellation_source
|
||||
/// object.
|
||||
bool can_be_cancelled() const noexcept;
|
||||
|
||||
/// Query if some thread has called request_cancellation().
|
||||
bool is_cancellation_requested() const noexcept;
|
||||
|
||||
/// Flag state has having cancellation_requested and execute any
|
||||
/// registered callbacks.
|
||||
void request_cancellation();
|
||||
|
||||
/// Try to register the cancellation_registration as a callback to be executed
|
||||
/// when cancellation is requested.
|
||||
///
|
||||
/// \return
|
||||
/// true if the callback was successfully registered, false if the callback was
|
||||
/// not registered because cancellation had already been requested.
|
||||
///
|
||||
/// \throw std::bad_alloc
|
||||
/// If callback was unable to be registered due to insufficient memory.
|
||||
bool try_register_callback(cancellation_registration* registration);
|
||||
|
||||
/// Deregister a callback previously registered successfully in a call to try_register_callback().
|
||||
///
|
||||
/// If the callback is currently being executed on another
|
||||
/// thread that is concurrently calling request_cancellation()
|
||||
/// then this call will block until the callback has finished executing.
|
||||
void deregister_callback(cancellation_registration* registration) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
cancellation_state() noexcept;
|
||||
|
||||
bool is_cancellation_notification_complete() const noexcept;
|
||||
|
||||
static constexpr std::uint64_t cancellation_requested_flag = 1;
|
||||
static constexpr std::uint64_t cancellation_notification_complete_flag = 2;
|
||||
static constexpr std::uint64_t cancellation_source_ref_increment = 4;
|
||||
static constexpr std::uint64_t cancellation_token_ref_increment = UINT64_C(1) << 33;
|
||||
static constexpr std::uint64_t can_be_cancelled_mask = cancellation_token_ref_increment - 1;
|
||||
static constexpr std::uint64_t cancellation_ref_count_mask =
|
||||
~(cancellation_requested_flag | cancellation_notification_complete_flag);
|
||||
|
||||
// A value that has:
|
||||
// - bit 0 - indicates whether cancellation has been requested.
|
||||
// - bit 1 - indicates whether cancellation notification is complete.
|
||||
// - bits 2-32 - ref-count for cancellation_source instances.
|
||||
// - bits 33-63 - ref-count for cancellation_token/cancellation_registration instances.
|
||||
std::atomic<std::uint64_t> m_state;
|
||||
|
||||
std::atomic<cancellation_registration_state*> m_registrationState;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/cancellation_token.hpp>
|
||||
#include <cppcoro/operation_cancelled.hpp>
|
||||
|
||||
#include "cancellation_state.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
cppcoro::cancellation_token::cancellation_token() noexcept
|
||||
: m_state(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token::cancellation_token(const cancellation_token& other) noexcept
|
||||
: m_state(other.m_state)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->add_token_ref();
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token::cancellation_token(cancellation_token&& other) noexcept
|
||||
: m_state(other.m_state)
|
||||
{
|
||||
other.m_state = nullptr;
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token::~cancellation_token()
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_token_ref();
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(const cancellation_token& other) noexcept
|
||||
{
|
||||
if (other.m_state != m_state)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_token_ref();
|
||||
}
|
||||
|
||||
m_state = other.m_state;
|
||||
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->add_token_ref();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token& cppcoro::cancellation_token::operator=(cancellation_token&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->release_token_ref();
|
||||
}
|
||||
|
||||
m_state = other.m_state;
|
||||
other.m_state = nullptr;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void cppcoro::cancellation_token::swap(cancellation_token& other) noexcept
|
||||
{
|
||||
std::swap(m_state, other.m_state);
|
||||
}
|
||||
|
||||
bool cppcoro::cancellation_token::can_be_cancelled() const noexcept
|
||||
{
|
||||
return m_state != nullptr && m_state->can_be_cancelled();
|
||||
}
|
||||
|
||||
bool cppcoro::cancellation_token::is_cancellation_requested() const noexcept
|
||||
{
|
||||
return m_state != nullptr && m_state->is_cancellation_requested();
|
||||
}
|
||||
|
||||
void cppcoro::cancellation_token::throw_if_cancellation_requested() const
|
||||
{
|
||||
if (is_cancellation_requested())
|
||||
{
|
||||
throw operation_cancelled{};
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::cancellation_token::cancellation_token(detail::cancellation_state* state) noexcept
|
||||
: m_state(state)
|
||||
{
|
||||
if (m_state != nullptr)
|
||||
{
|
||||
m_state->add_token_ref();
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/file.hpp>
|
||||
#include <cppcoro/io_service.hpp>
|
||||
|
||||
#include <system_error>
|
||||
#include <cassert>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
#endif
|
||||
|
||||
cppcoro::file::~file()
|
||||
{}
|
||||
|
||||
std::uint64_t cppcoro::file::size() const
|
||||
{
|
||||
#if CPPCORO_OS_WINNT
|
||||
LARGE_INTEGER size;
|
||||
BOOL ok = ::GetFileSizeEx(m_fileHandle.handle(), &size);
|
||||
if (!ok)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error getting file size: GetFileSizeEx"
|
||||
};
|
||||
}
|
||||
|
||||
return size.QuadPart;
|
||||
#endif
|
||||
}
|
||||
|
||||
cppcoro::file::file(detail::win32::safe_handle&& fileHandle) noexcept
|
||||
: m_fileHandle(std::move(fileHandle))
|
||||
{
|
||||
}
|
||||
|
||||
cppcoro::detail::win32::safe_handle cppcoro::file::open(
|
||||
detail::win32::dword_t fileAccess,
|
||||
io_service& ioService,
|
||||
const cppcoro::filesystem::path& path,
|
||||
file_open_mode openMode,
|
||||
file_share_mode shareMode,
|
||||
file_buffering_mode bufferingMode)
|
||||
{
|
||||
DWORD flags = FILE_FLAG_OVERLAPPED;
|
||||
if ((bufferingMode & file_buffering_mode::random_access) == file_buffering_mode::random_access)
|
||||
{
|
||||
flags |= FILE_FLAG_RANDOM_ACCESS;
|
||||
}
|
||||
if ((bufferingMode & file_buffering_mode::sequential) == file_buffering_mode::sequential)
|
||||
{
|
||||
flags |= FILE_FLAG_SEQUENTIAL_SCAN;
|
||||
}
|
||||
if ((bufferingMode & file_buffering_mode::write_through) == file_buffering_mode::write_through)
|
||||
{
|
||||
flags |= FILE_FLAG_WRITE_THROUGH;
|
||||
}
|
||||
if ((bufferingMode & file_buffering_mode::temporary) == file_buffering_mode::temporary)
|
||||
{
|
||||
flags |= FILE_ATTRIBUTE_TEMPORARY;
|
||||
}
|
||||
if ((bufferingMode & file_buffering_mode::unbuffered) == file_buffering_mode::unbuffered)
|
||||
{
|
||||
flags |= FILE_FLAG_NO_BUFFERING;
|
||||
}
|
||||
|
||||
DWORD shareFlags = 0;
|
||||
if ((shareMode & file_share_mode::read) == file_share_mode::read)
|
||||
{
|
||||
shareFlags |= FILE_SHARE_READ;
|
||||
}
|
||||
if ((shareMode & file_share_mode::write) == file_share_mode::write)
|
||||
{
|
||||
shareFlags |= FILE_SHARE_WRITE;
|
||||
}
|
||||
if ((shareMode & file_share_mode::delete_) == file_share_mode::delete_)
|
||||
{
|
||||
shareFlags |= FILE_SHARE_DELETE;
|
||||
}
|
||||
|
||||
DWORD creationDisposition = 0;
|
||||
switch (openMode)
|
||||
{
|
||||
case file_open_mode::create_or_open:
|
||||
creationDisposition = OPEN_ALWAYS;
|
||||
break;
|
||||
case file_open_mode::create_always:
|
||||
creationDisposition = CREATE_ALWAYS;
|
||||
break;
|
||||
case file_open_mode::create_new:
|
||||
creationDisposition = CREATE_NEW;
|
||||
break;
|
||||
case file_open_mode::open_existing:
|
||||
creationDisposition = OPEN_EXISTING;
|
||||
break;
|
||||
case file_open_mode::truncate_existing:
|
||||
creationDisposition = TRUNCATE_EXISTING;
|
||||
break;
|
||||
}
|
||||
|
||||
// Open the file
|
||||
detail::win32::safe_handle fileHandle(
|
||||
::CreateFileW(
|
||||
path.wstring().c_str(),
|
||||
fileAccess,
|
||||
shareFlags,
|
||||
nullptr,
|
||||
creationDisposition,
|
||||
flags,
|
||||
nullptr));
|
||||
if (fileHandle.handle() == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error opening file: CreateFileW"
|
||||
};
|
||||
}
|
||||
|
||||
// Associate with the I/O service's completion port.
|
||||
const HANDLE result = ::CreateIoCompletionPort(
|
||||
fileHandle.handle(),
|
||||
ioService.native_iocp_handle(),
|
||||
0,
|
||||
0);
|
||||
if (result == nullptr)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error opening file: CreateIoCompletionPort"
|
||||
};
|
||||
}
|
||||
|
||||
// Configure I/O operations to avoid dispatching a completion event
|
||||
// to the I/O service if the operation completes synchronously.
|
||||
// This avoids unnecessary suspension/resuption of the awaiting coroutine.
|
||||
const BOOL ok = ::SetFileCompletionNotificationModes(
|
||||
fileHandle.handle(),
|
||||
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS |
|
||||
FILE_SKIP_SET_EVENT_ON_HANDLE);
|
||||
if (!ok)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error opening file: SetFileCompletionNotificationModes"
|
||||
};
|
||||
}
|
||||
|
||||
return std::move(fileHandle);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/file_read_operation.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::file_read_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
const DWORD numberOfBytesToRead =
|
||||
m_byteCount <= 0xFFFFFFFF ?
|
||||
static_cast<DWORD>(m_byteCount) : DWORD(0xFFFFFFFF);
|
||||
|
||||
DWORD numberOfBytesRead = 0;
|
||||
BOOL ok = ::ReadFile(
|
||||
m_fileHandle,
|
||||
m_buffer,
|
||||
numberOfBytesToRead,
|
||||
&numberOfBytesRead,
|
||||
operation.get_overlapped());
|
||||
const DWORD errorCode = ok ? ERROR_SUCCESS : ::GetLastError();
|
||||
if (errorCode != ERROR_IO_PENDING)
|
||||
{
|
||||
// Completed synchronously.
|
||||
//
|
||||
// We are assuming that the file-handle has been set to the
|
||||
// mode where synchronous completions do not post a completion
|
||||
// event to the I/O completion port and thus can return without
|
||||
// suspending here.
|
||||
|
||||
operation.m_errorCode = errorCode;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesRead;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::file_read_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(m_fileHandle, operation.get_overlapped());
|
||||
}
|
||||
|
||||
#endif // CPPCORO_OS_WINNT
|
||||
@@ -0,0 +1,53 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/file_write_operation.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::file_write_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
const DWORD numberOfBytesToWrite =
|
||||
m_byteCount <= 0xFFFFFFFF ?
|
||||
static_cast<DWORD>(m_byteCount) : DWORD(0xFFFFFFFF);
|
||||
|
||||
DWORD numberOfBytesWritten = 0;
|
||||
BOOL ok = ::WriteFile(
|
||||
m_fileHandle,
|
||||
m_buffer,
|
||||
numberOfBytesToWrite,
|
||||
&numberOfBytesWritten,
|
||||
operation.get_overlapped());
|
||||
const DWORD errorCode = ok ? ERROR_SUCCESS : ::GetLastError();
|
||||
if (errorCode != ERROR_IO_PENDING)
|
||||
{
|
||||
// Completed synchronously.
|
||||
//
|
||||
// We are assuming that the file-handle has been set to the
|
||||
// mode where synchronous completions do not post a completion
|
||||
// event to the I/O completion port and thus can return without
|
||||
// suspending here.
|
||||
|
||||
operation.m_errorCode = errorCode;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesWritten;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::file_write_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(m_fileHandle, operation.get_overlapped());
|
||||
}
|
||||
|
||||
#endif // CPPCORO_OS_WINNT
|
||||
+1020
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ip_address.hpp>
|
||||
|
||||
std::string cppcoro::net::ip_address::to_string() const
|
||||
{
|
||||
return is_ipv4() ? m_ipv4.to_string() : m_ipv6.to_string();
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ip_address>
|
||||
cppcoro::net::ip_address::from_string(std::string_view string) noexcept
|
||||
{
|
||||
if (auto ipv4 = ipv4_address::from_string(string); ipv4)
|
||||
{
|
||||
return *ipv4;
|
||||
}
|
||||
|
||||
if (auto ipv6 = ipv6_address::from_string(string); ipv6)
|
||||
{
|
||||
return *ipv6;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ip_endpoint.hpp>
|
||||
|
||||
std::string cppcoro::net::ip_endpoint::to_string() const
|
||||
{
|
||||
return is_ipv4() ? m_ipv4.to_string() : m_ipv6.to_string();
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ip_endpoint>
|
||||
cppcoro::net::ip_endpoint::from_string(std::string_view string) noexcept
|
||||
{
|
||||
if (auto ipv4 = ipv4_endpoint::from_string(string); ipv4)
|
||||
{
|
||||
return *ipv4;
|
||||
}
|
||||
|
||||
if (auto ipv6 = ipv6_endpoint::from_string(string); ipv6)
|
||||
{
|
||||
return *ipv6;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ipv4_address.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
constexpr bool is_digit(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
constexpr std::uint8_t digit_value(char c)
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ipv4_address>
|
||||
cppcoro::net::ipv4_address::from_string(std::string_view string) noexcept
|
||||
{
|
||||
if (string.empty()) return std::nullopt;
|
||||
|
||||
if (!local::is_digit(string[0]))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto length = string.length();
|
||||
|
||||
std::uint8_t partValues[4];
|
||||
|
||||
if (string[0] == '0' && length > 1)
|
||||
{
|
||||
if (local::is_digit(string[1]))
|
||||
{
|
||||
// Octal format (not supported)
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (string[1] == 'x')
|
||||
{
|
||||
// Hexadecimal format (not supported)
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the first integer.
|
||||
// Could be a single 32-bit integer or first integer in a dotted decimal string.
|
||||
|
||||
std::size_t pos = 0;
|
||||
|
||||
{
|
||||
constexpr std::uint32_t maxValue = 0xFFFFFFFFu / 10;
|
||||
constexpr std::uint32_t maxDigit = 0xFFFFFFFFu % 10;
|
||||
|
||||
std::uint32_t partValue = local::digit_value(string[pos]);
|
||||
++pos;
|
||||
|
||||
while (pos < length && local::is_digit(string[pos]))
|
||||
{
|
||||
const auto digitValue = local::digit_value(string[pos]);
|
||||
++pos;
|
||||
|
||||
// Check if this digit would overflow the 32-bit integer
|
||||
if (partValue > maxValue || (partValue == maxValue && digitValue > maxDigit))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
partValue = (partValue * 10) + digitValue;
|
||||
}
|
||||
|
||||
if (pos == length)
|
||||
{
|
||||
// A single-integer string
|
||||
return ipv4_address{ partValue };
|
||||
}
|
||||
else if (partValue > 255)
|
||||
{
|
||||
// Not a valid first component of dotted decimal
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
partValues[0] = static_cast<std::uint8_t>(partValue);
|
||||
}
|
||||
|
||||
for (int part = 1; part < 4; ++part)
|
||||
{
|
||||
if ((pos + 1) >= length || string[pos] != '.' || !local::is_digit(string[pos + 1]))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Skip the '.'
|
||||
++pos;
|
||||
|
||||
// Check for an octal format (not yet supported)
|
||||
const bool isPartOctal =
|
||||
(pos + 1) < length &&
|
||||
string[pos] == '0' &&
|
||||
local::is_digit(string[pos + 1]);
|
||||
if (isPartOctal)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::uint32_t partValue = local::digit_value(string[pos]);
|
||||
++pos;
|
||||
if (pos < length && local::is_digit(string[pos]))
|
||||
{
|
||||
partValue = (partValue * 10) + local::digit_value(string[pos]);
|
||||
++pos;
|
||||
if (pos < length && local::is_digit(string[pos]))
|
||||
{
|
||||
partValue = (partValue * 10) + local::digit_value(string[pos]);
|
||||
if (partValue > 255)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
partValues[part] = static_cast<std::uint8_t>(partValue);
|
||||
}
|
||||
|
||||
if (pos < length)
|
||||
{
|
||||
// Extra chars after end of a valid IPv4 string
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return ipv4_address{ partValues };
|
||||
}
|
||||
|
||||
std::string cppcoro::net::ipv4_address::to_string() const
|
||||
{
|
||||
// Buffer is large enough to hold larges ip address
|
||||
// "xxx.xxx.xxx.xxx"
|
||||
char buffer[15];
|
||||
|
||||
char* c = &buffer[0];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
*c++ = '.';
|
||||
}
|
||||
|
||||
if (m_bytes[i] >= 100)
|
||||
{
|
||||
*c++ = '0' + (m_bytes[i] / 100);
|
||||
*c++ = '0' + (m_bytes[i] % 100) / 10;
|
||||
*c++ = '0' + (m_bytes[i] % 10);
|
||||
}
|
||||
else if (m_bytes[i] >= 10)
|
||||
{
|
||||
*c++ = '0' + (m_bytes[i] / 10);
|
||||
*c++ = '0' + (m_bytes[i] % 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
*c++ = '0' + m_bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return std::string{ &buffer[0], c };
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Kt C++ Library
|
||||
// Copyright (c) 2015 Lewis Baker
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ipv4_endpoint.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
bool is_digit(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
std::uint8_t digit_value(char c)
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - '0');
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t> parse_port(std::string_view string)
|
||||
{
|
||||
if (string.empty()) return std::nullopt;
|
||||
|
||||
std::uint32_t value = 0;
|
||||
for (auto c : string)
|
||||
{
|
||||
if (!is_digit(c)) return std::nullopt;
|
||||
value = value * 10 + digit_value(c);
|
||||
if (value > 0xFFFFu) return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<std::uint16_t>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string cppcoro::net::ipv4_endpoint::to_string() const
|
||||
{
|
||||
auto s = m_address.to_string();
|
||||
s.push_back(':');
|
||||
s.append(std::to_string(m_port));
|
||||
return s;
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ipv4_endpoint>
|
||||
cppcoro::net::ipv4_endpoint::from_string(std::string_view string) noexcept
|
||||
{
|
||||
auto colonPos = string.find(':');
|
||||
if (colonPos == std::string_view::npos)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto address = ipv4_address::from_string(string.substr(0, colonPos));
|
||||
if (!address)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto port = local::parse_port(string.substr(colonPos + 1));
|
||||
if (!port)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return ipv4_endpoint{ *address, *port };
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ipv6_address.hpp>
|
||||
#include <cppcoro/config.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
constexpr bool is_digit(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
constexpr std::uint8_t digit_value(char c)
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - '0');
|
||||
}
|
||||
|
||||
std::optional<std::uint8_t> try_parse_hex_digit(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - '0');
|
||||
}
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - 'a' + 10);
|
||||
}
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - 'A' + 10);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char hex_char(std::uint8_t value)
|
||||
{
|
||||
return value < 10 ?
|
||||
static_cast<char>('0' + value) :
|
||||
static_cast<char>('a' + value - 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ipv6_address>
|
||||
cppcoro::net::ipv6_address::from_string(std::string_view string) noexcept
|
||||
{
|
||||
// Longest possible valid IPv6 string is
|
||||
// "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:nnn.nnn.nnn.nnn"
|
||||
constexpr std::size_t maxLength = 45;
|
||||
|
||||
if (string.empty() || string.length() > maxLength)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::size_t length = string.length();
|
||||
|
||||
std::optional<int> doubleColonPos;
|
||||
|
||||
std::size_t pos = 0;
|
||||
|
||||
if (length >= 2 && string[0] == ':' && string[1] == ':')
|
||||
{
|
||||
doubleColonPos = 0;
|
||||
pos = 2;
|
||||
}
|
||||
|
||||
int partCount = 0;
|
||||
std::uint16_t parts[8] = { 0 };
|
||||
|
||||
while (pos < length && partCount < 8)
|
||||
{
|
||||
std::uint8_t digits[4];
|
||||
int digitCount = 0;
|
||||
auto digit = local::try_parse_hex_digit(string[pos]);
|
||||
if (!digit)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
digits[digitCount] = *digit;
|
||||
++digitCount;
|
||||
++pos;
|
||||
} while (digitCount < 4 && pos < length && (digit = local::try_parse_hex_digit(string[pos])));
|
||||
|
||||
// If we're not at the end of the string then there must either be a ':' or a '.' next
|
||||
// followed by the next part.
|
||||
if (pos < length)
|
||||
{
|
||||
// Check if there's room for anything after the separator.
|
||||
if ((pos + 1) == length)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (string[pos] == ':')
|
||||
{
|
||||
++pos;
|
||||
if (string[pos] == ':')
|
||||
{
|
||||
if (doubleColonPos)
|
||||
{
|
||||
// This is a second double-colon, which is invalid.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
doubleColonPos = partCount + 1;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
else if (string[pos] == '.')
|
||||
{
|
||||
// Treat the current set of digits as decimal digits and parse
|
||||
// the remaining three groups as dotted decimal notation.
|
||||
|
||||
// Decimal notation produces two 16-bit parts.
|
||||
// If we already have more than 6 parts then we'll end up
|
||||
// with too many.
|
||||
if (partCount > 6)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Check for over-long or octal notation.
|
||||
if (digitCount > 3 || (digitCount > 1 && digits[0] == 0))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Check that digits are valid decimal digits
|
||||
if (digits[0] > 9 ||
|
||||
(digitCount > 1 && digits[1] > 9) ||
|
||||
(digitCount == 3 && digits[2] > 9))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::uint16_t decimalParts[4];
|
||||
|
||||
{
|
||||
decimalParts[0] = digits[0];
|
||||
for (int i = 1; i < digitCount; ++i)
|
||||
{
|
||||
decimalParts[0] *= 10;
|
||||
decimalParts[0] += digits[i];
|
||||
}
|
||||
|
||||
if (decimalParts[0] > 255)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
for (int decimalPart = 1; decimalPart < 4; ++decimalPart)
|
||||
{
|
||||
if (string[pos] != '.')
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
++pos;
|
||||
|
||||
if (pos == length || !local::is_digit(string[pos]))
|
||||
{
|
||||
// Expected a number after a dot.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const bool hasLeadingZero = string[pos] == '0';
|
||||
|
||||
decimalParts[decimalPart] = local::digit_value(string[pos]);
|
||||
++pos;
|
||||
digitCount = 1;
|
||||
while (digitCount < 3 && pos < length && local::is_digit(string[pos]))
|
||||
{
|
||||
decimalParts[decimalPart] *= 10;
|
||||
decimalParts[decimalPart] += local::digit_value(string[pos]);
|
||||
++pos;
|
||||
++digitCount;
|
||||
}
|
||||
|
||||
if (decimalParts[decimalPart] > 255)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Detect octal-style number (redundant leading zero)
|
||||
if (digitCount > 1 && hasLeadingZero)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
parts[partCount] = (decimalParts[0] << 8) + decimalParts[1];
|
||||
parts[partCount + 1] = (decimalParts[2] << 8) + decimalParts[3];
|
||||
partCount += 2;
|
||||
|
||||
// Dotted decimal notation only appears at end.
|
||||
// Don't parse any more of the string.
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid separator.
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Current part was made up of hex-digits.
|
||||
std::uint16_t partValue = digits[0];
|
||||
for (int i = 1; i < digitCount; ++i)
|
||||
{
|
||||
partValue = partValue * 16 + digits[i];
|
||||
}
|
||||
|
||||
parts[partCount] = partValue;
|
||||
++partCount;
|
||||
}
|
||||
|
||||
// Finished parsing the IPv6 address, we should have consumed all of the string.
|
||||
if (pos < length)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (partCount < 8)
|
||||
{
|
||||
if (!doubleColonPos)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const int preCount = *doubleColonPos;
|
||||
|
||||
//CPPCORO_ASSUME(preCount <= partCount);
|
||||
|
||||
const int postCount = partCount - preCount;
|
||||
const int zeroCount = 8 - preCount - postCount;
|
||||
|
||||
// Move parts after double colon down to the end.
|
||||
for (int i = 0; i < postCount; ++i)
|
||||
{
|
||||
parts[7 - i] = parts[7 - zeroCount - i];
|
||||
}
|
||||
|
||||
// Fill gap with zeroes.
|
||||
for (int i = 0; i < zeroCount; ++i)
|
||||
{
|
||||
parts[preCount + i] = 0;
|
||||
}
|
||||
}
|
||||
else if (doubleColonPos)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return ipv6_address{ parts };
|
||||
}
|
||||
|
||||
std::string cppcoro::net::ipv6_address::to_string() const
|
||||
{
|
||||
std::uint32_t longestZeroRunStart = 0;
|
||||
std::uint32_t longestZeroRunLength = 0;
|
||||
for (std::uint32_t i = 0; i < 8; )
|
||||
{
|
||||
if (m_bytes[2 * i] == 0 && m_bytes[2 * i + 1] == 0)
|
||||
{
|
||||
const std::uint32_t zeroRunStart = i;
|
||||
++i;
|
||||
while (i < 8 && m_bytes[2 * i] == 0 && m_bytes[2 * i + 1] == 0)
|
||||
{
|
||||
++i;
|
||||
}
|
||||
|
||||
std::uint32_t zeroRunLength = i - zeroRunStart;
|
||||
if (zeroRunLength > longestZeroRunLength)
|
||||
{
|
||||
longestZeroRunLength = zeroRunLength;
|
||||
longestZeroRunStart = zeroRunStart;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Longest string will be 8 x 4 digits + 7 ':' separators
|
||||
char buffer[40];
|
||||
|
||||
char* c = &buffer[0];
|
||||
|
||||
auto appendPart = [&](std::uint32_t index)
|
||||
{
|
||||
const std::uint8_t highByte = m_bytes[index * 2];
|
||||
const std::uint8_t lowByte = m_bytes[index * 2 + 1];
|
||||
|
||||
// Don't output leading zero hex digits in the part string.
|
||||
if (highByte > 0 || lowByte > 15)
|
||||
{
|
||||
if (highByte > 0)
|
||||
{
|
||||
if (highByte > 15)
|
||||
{
|
||||
*c++ = local::hex_char(highByte >> 4);
|
||||
}
|
||||
*c++ = local::hex_char(highByte & 0xF);
|
||||
}
|
||||
*c++ = local::hex_char(lowByte >> 4);
|
||||
}
|
||||
*c++ = local::hex_char(lowByte & 0xF);
|
||||
};
|
||||
|
||||
if (longestZeroRunLength >= 2)
|
||||
{
|
||||
for (std::uint32_t i = 0; i < longestZeroRunStart; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
*c++ = ':';
|
||||
}
|
||||
|
||||
appendPart(i);
|
||||
}
|
||||
|
||||
*c++ = ':';
|
||||
*c++ = ':';
|
||||
|
||||
for (std::uint32_t i = longestZeroRunStart + longestZeroRunLength; i < 8; ++i)
|
||||
{
|
||||
appendPart(i);
|
||||
|
||||
if (i < 7)
|
||||
{
|
||||
*c++ = ':';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
appendPart(0);
|
||||
for (std::uint32_t i = 1; i < 8; ++i)
|
||||
{
|
||||
*c++ = ':';
|
||||
appendPart(i);
|
||||
}
|
||||
}
|
||||
|
||||
assert((c - &buffer[0]) <= sizeof(buffer));
|
||||
|
||||
return std::string{ &buffer[0], c };
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Kt C++ Library
|
||||
// Copyright (c) 2015 Lewis Baker
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/ipv6_endpoint.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
bool is_digit(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
std::uint8_t digit_value(char c)
|
||||
{
|
||||
return static_cast<std::uint8_t>(c - '0');
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t> parse_port(std::string_view string)
|
||||
{
|
||||
if (string.empty()) return std::nullopt;
|
||||
|
||||
std::uint32_t value = 0;
|
||||
for (auto c : string)
|
||||
{
|
||||
if (!is_digit(c)) return std::nullopt;
|
||||
value = value * 10 + digit_value(c);
|
||||
if (value > 0xFFFFu) return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<std::uint16_t>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string cppcoro::net::ipv6_endpoint::to_string() const
|
||||
{
|
||||
std::string result;
|
||||
result.push_back('[');
|
||||
result += m_address.to_string();
|
||||
result += "]:";
|
||||
result += std::to_string(m_port);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<cppcoro::net::ipv6_endpoint>
|
||||
cppcoro::net::ipv6_endpoint::from_string(std::string_view string) noexcept
|
||||
{
|
||||
// Shortest valid endpoint is "[::]:0"
|
||||
if (string.size() < 6)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (string[0] != '[')
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto closeBracketPos = string.find("]:", 1);
|
||||
if (closeBracketPos == std::string_view::npos)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto address = ipv6_address::from_string(string.substr(1, closeBracketPos - 1));
|
||||
if (!address)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto port = local::parse_port(string.substr(closeBracketPos + 2));
|
||||
if (!port)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return ipv6_endpoint{ *address, *port };
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/io_service.hpp>
|
||||
#include <cppcoro/detail/lightweight_manual_reset_event.hpp>
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
# if CPPCORO_OS_WINNT >= 0x0602
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::lightweight_manual_reset_event(bool initiallySet)
|
||||
: m_value(initiallySet ? 1 : 0)
|
||||
{}
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::~lightweight_manual_reset_event()
|
||||
{
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::set() noexcept
|
||||
{
|
||||
m_value.store(1, std::memory_order_release);
|
||||
::WakeByAddressAll(&m_value);
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::reset() noexcept
|
||||
{
|
||||
m_value.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::wait() noexcept
|
||||
{
|
||||
wait({}, std::chrono::milliseconds(0));
|
||||
}
|
||||
void cppcoro::detail::lightweight_manual_reset_event::wait(
|
||||
std::span<io_service> srvs, std::chrono::system_clock::duration step) noexcept
|
||||
{
|
||||
const DWORD stepMs = static_cast<DWORD>(std::chrono::duration_cast<std::chrono::milliseconds>(step).count());
|
||||
DWORD delay = srvs.empty() ? INFINITE : stepMs;
|
||||
|
||||
// Wait in a loop as WaitOnAddress() can have spurious wake-ups.
|
||||
int value = m_value.load(std::memory_order_acquire);
|
||||
BOOL ok = TRUE;
|
||||
while (value == 0)
|
||||
{
|
||||
if (!srvs.empty())
|
||||
{
|
||||
//if there was one processed event, pass 0 timeout so we get a chance to process more, quickly
|
||||
//otherwise, wait the full step
|
||||
uint64_t tasks = 0;
|
||||
for (auto& srv: srvs)
|
||||
tasks += srv.process_one_pending_event();
|
||||
|
||||
delay = tasks > 0 ? 0 : stepMs;
|
||||
}
|
||||
else if (!ok)
|
||||
{
|
||||
// Previous call to WaitOnAddress() failed for some reason.
|
||||
// Put thread to sleep to avoid sitting in a busy loop if it keeps failing.
|
||||
::Sleep(1);
|
||||
}
|
||||
|
||||
ok = ::WaitOnAddress(&m_value, &value, sizeof(m_value), delay);
|
||||
value = m_value.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
|
||||
# else
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::lightweight_manual_reset_event(bool initiallySet)
|
||||
: m_eventHandle(::CreateEventW(nullptr, TRUE, initiallySet, nullptr))
|
||||
{
|
||||
if (m_eventHandle == NULL)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::~lightweight_manual_reset_event()
|
||||
{
|
||||
// Ignore failure to close the object.
|
||||
// We can't do much here as we want destructor to be noexcept.
|
||||
(void)::CloseHandle(m_eventHandle);
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::set() noexcept
|
||||
{
|
||||
if (!::SetEvent(m_eventHandle))
|
||||
{
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::reset() noexcept
|
||||
{
|
||||
if (!::ResetEvent(m_eventHandle))
|
||||
{
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::wait() noexcept
|
||||
{
|
||||
constexpr BOOL alertable = FALSE;
|
||||
DWORD waitResult = ::WaitForSingleObjectEx(m_eventHandle, INFINITE, alertable);
|
||||
if (waitResult == WAIT_FAILED)
|
||||
{
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
# endif
|
||||
|
||||
#elif CPPCORO_OS_LINUX
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <linux/futex.h>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cassert>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
// No futex() function provided by libc.
|
||||
// Wrap the syscall ourselves here.
|
||||
int futex(
|
||||
int* UserAddress,
|
||||
int FutexOperation,
|
||||
int Value,
|
||||
const struct timespec* timeout,
|
||||
int* UserAddress2,
|
||||
int Value3)
|
||||
{
|
||||
return syscall(
|
||||
SYS_futex,
|
||||
UserAddress,
|
||||
FutexOperation,
|
||||
Value,
|
||||
timeout,
|
||||
UserAddress2,
|
||||
Value3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::lightweight_manual_reset_event(bool initiallySet)
|
||||
: m_value(initiallySet ? 1 : 0)
|
||||
{}
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::~lightweight_manual_reset_event()
|
||||
{
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::set() noexcept
|
||||
{
|
||||
m_value.store(1, std::memory_order_release);
|
||||
|
||||
constexpr int numberOfWaitersToWakeUp = INT_MAX;
|
||||
|
||||
[[maybe_unused]] int numberOfWaitersWokenUp = local::futex(
|
||||
reinterpret_cast<int*>(&m_value),
|
||||
FUTEX_WAKE_PRIVATE,
|
||||
numberOfWaitersToWakeUp,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
// There are no errors expected here unless this class (or the caller)
|
||||
// has done something wrong.
|
||||
assert(numberOfWaitersWokenUp != -1);
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::reset() noexcept
|
||||
{
|
||||
m_value.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::wait() noexcept
|
||||
{
|
||||
// Wait in a loop as futex() can have spurious wake-ups.
|
||||
int oldValue = m_value.load(std::memory_order_acquire);
|
||||
while (oldValue == 0)
|
||||
{
|
||||
int result = local::futex(
|
||||
reinterpret_cast<int*>(&m_value),
|
||||
FUTEX_WAIT_PRIVATE,
|
||||
oldValue,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
if (result == -1)
|
||||
{
|
||||
if (errno == EAGAIN)
|
||||
{
|
||||
// The state was changed from zero before we could wait.
|
||||
// Must have been changed to 1.
|
||||
return;
|
||||
}
|
||||
|
||||
// Other errors we'll treat as transient and just read the
|
||||
// value and go around the loop again.
|
||||
}
|
||||
|
||||
oldValue = m_value.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::lightweight_manual_reset_event(bool initiallySet)
|
||||
: m_isSet(initiallySet)
|
||||
{
|
||||
}
|
||||
|
||||
cppcoro::detail::lightweight_manual_reset_event::~lightweight_manual_reset_event()
|
||||
{
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::set() noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isSet = true;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::reset() noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isSet = false;
|
||||
}
|
||||
|
||||
void cppcoro::detail::lightweight_manual_reset_event::wait() noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_cv.wait(lock, [this] { return m_isSet; });
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro\read_only_file.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
cppcoro::read_only_file cppcoro::read_only_file::open(
|
||||
io_service& ioService,
|
||||
const cppcoro::filesystem::path& path,
|
||||
file_share_mode shareMode,
|
||||
file_buffering_mode bufferingMode)
|
||||
{
|
||||
return read_only_file(file::open(
|
||||
GENERIC_READ,
|
||||
ioService,
|
||||
path,
|
||||
file_open_mode::open_existing,
|
||||
shareMode,
|
||||
bufferingMode));
|
||||
}
|
||||
|
||||
cppcoro::read_only_file::read_only_file(
|
||||
detail::win32::safe_handle&& fileHandle) noexcept
|
||||
: file(std::move(fileHandle))
|
||||
, readable_file(detail::win32::safe_handle{})
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro\read_write_file.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
cppcoro::read_write_file cppcoro::read_write_file::open(
|
||||
io_service& ioService,
|
||||
const cppcoro::filesystem::path& path,
|
||||
file_open_mode openMode,
|
||||
file_share_mode shareMode,
|
||||
file_buffering_mode bufferingMode)
|
||||
{
|
||||
return read_write_file(file::open(
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
ioService,
|
||||
path,
|
||||
openMode,
|
||||
shareMode,
|
||||
bufferingMode));
|
||||
}
|
||||
|
||||
cppcoro::read_write_file::read_write_file(
|
||||
detail::win32::safe_handle&& fileHandle) noexcept
|
||||
: file(std::move(fileHandle))
|
||||
, readable_file(detail::win32::safe_handle{})
|
||||
, writable_file(detail::win32::safe_handle{})
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/readable_file.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
|
||||
cppcoro::file_read_operation cppcoro::readable_file::read(
|
||||
std::uint64_t offset,
|
||||
void* buffer,
|
||||
std::size_t byteCount) const noexcept
|
||||
{
|
||||
return file_read_operation(
|
||||
m_fileHandle.handle(),
|
||||
offset,
|
||||
buffer,
|
||||
byteCount);
|
||||
}
|
||||
|
||||
cppcoro::file_read_operation_cancellable cppcoro::readable_file::read(
|
||||
std::uint64_t offset,
|
||||
void* buffer,
|
||||
std::size_t byteCount,
|
||||
cancellation_token ct) const noexcept
|
||||
{
|
||||
return file_read_operation_cancellable(
|
||||
m_fileHandle.handle(),
|
||||
offset,
|
||||
buffer,
|
||||
byteCount,
|
||||
std::move(ct));
|
||||
}
|
||||
|
||||
#endif
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#include <cppcoro/net/socket_accept_operation.hpp>
|
||||
#include <cppcoro/net/socket_connect_operation.hpp>
|
||||
#include <cppcoro/net/socket_disconnect_operation.hpp>
|
||||
#include <cppcoro/net/socket_recv_operation.hpp>
|
||||
#include <cppcoro/net/socket_send_operation.hpp>
|
||||
|
||||
#include <cppcoro/io_service.hpp>
|
||||
#include <cppcoro/on_scope_exit.hpp>
|
||||
|
||||
#include "socket_helpers.hpp"
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
std::tuple<SOCKET, bool> create_socket(
|
||||
int addressFamily,
|
||||
int socketType,
|
||||
int protocol,
|
||||
HANDLE ioCompletionPort)
|
||||
{
|
||||
// Enumerate available protocol providers for the specified socket type.
|
||||
|
||||
WSAPROTOCOL_INFOW stackInfos[4];
|
||||
std::unique_ptr<WSAPROTOCOL_INFOW[]> heapInfos;
|
||||
WSAPROTOCOL_INFOW* selectedProtocolInfo = nullptr;
|
||||
|
||||
{
|
||||
INT protocols[] = { protocol, 0 };
|
||||
DWORD bufferSize = sizeof(stackInfos);
|
||||
WSAPROTOCOL_INFOW* infos = stackInfos;
|
||||
|
||||
int protocolCount = ::WSAEnumProtocolsW(protocols, infos, &bufferSize);
|
||||
if (protocolCount == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode == WSAENOBUFS)
|
||||
{
|
||||
DWORD requiredElementCount = bufferSize / sizeof(WSAPROTOCOL_INFOW);
|
||||
heapInfos = std::make_unique<WSAPROTOCOL_INFOW[]>(requiredElementCount);
|
||||
bufferSize = requiredElementCount * sizeof(WSAPROTOCOL_INFOW);
|
||||
infos = heapInfos.get();
|
||||
protocolCount = ::WSAEnumProtocolsW(protocols, infos, &bufferSize);
|
||||
if (protocolCount == SOCKET_ERROR)
|
||||
{
|
||||
errorCode = ::WSAGetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
if (protocolCount == SOCKET_ERROR)
|
||||
{
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Error creating socket: WSAEnumProtocolsW");
|
||||
}
|
||||
}
|
||||
|
||||
if (protocolCount == 0)
|
||||
{
|
||||
throw std::system_error(
|
||||
std::make_error_code(std::errc::protocol_not_supported));
|
||||
}
|
||||
|
||||
for (int i = 0; i < protocolCount; ++i)
|
||||
{
|
||||
auto& info = infos[i];
|
||||
if (info.iAddressFamily == addressFamily && info.iProtocol == protocol && info.iSocketType == socketType)
|
||||
{
|
||||
selectedProtocolInfo = &info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedProtocolInfo == nullptr)
|
||||
{
|
||||
throw std::system_error(
|
||||
std::make_error_code(std::errc::address_family_not_supported));
|
||||
}
|
||||
}
|
||||
|
||||
// WSA_FLAG_NO_HANDLE_INHERIT for SDKs earlier than Windows 7.
|
||||
constexpr DWORD flagNoInherit = 0x80;
|
||||
|
||||
const DWORD flags = WSA_FLAG_OVERLAPPED | flagNoInherit;
|
||||
|
||||
const SOCKET socketHandle = ::WSASocketW(
|
||||
addressFamily, socketType, protocol, selectedProtocolInfo, 0, flags);
|
||||
if (socketHandle == INVALID_SOCKET)
|
||||
{
|
||||
const int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Error creating socket: WSASocketW");
|
||||
}
|
||||
|
||||
auto closeSocketOnFailure = cppcoro::on_scope_failure([&]
|
||||
{
|
||||
::closesocket(socketHandle);
|
||||
});
|
||||
|
||||
// This is needed on operating systems earlier than Windows 7 to prevent
|
||||
// socket handles from being inherited. On Windows 7 or later this is
|
||||
// redundant as the WSA_FLAG_NO_HANDLE_INHERIT flag passed to creation
|
||||
// above causes the socket to be atomically created with this flag cleared.
|
||||
if (!::SetHandleInformation((HANDLE)socketHandle, HANDLE_FLAG_INHERIT, 0))
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Error creating socket: SetHandleInformation");
|
||||
}
|
||||
|
||||
// Associate the socket with the I/O completion port.
|
||||
{
|
||||
const HANDLE result = ::CreateIoCompletionPort(
|
||||
(HANDLE)socketHandle,
|
||||
ioCompletionPort,
|
||||
ULONG_PTR(0),
|
||||
DWORD(0));
|
||||
if (result == nullptr)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error(
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"Error creating socket: CreateIoCompletionPort");
|
||||
}
|
||||
}
|
||||
|
||||
const bool skipCompletionPortOnSuccess =
|
||||
(selectedProtocolInfo->dwServiceFlags1 & XP1_IFS_HANDLES) != 0;
|
||||
|
||||
{
|
||||
UCHAR completionModeFlags = FILE_SKIP_SET_EVENT_ON_HANDLE;
|
||||
if (skipCompletionPortOnSuccess)
|
||||
{
|
||||
completionModeFlags |= FILE_SKIP_COMPLETION_PORT_ON_SUCCESS;
|
||||
}
|
||||
|
||||
const BOOL ok = ::SetFileCompletionNotificationModes(
|
||||
(HANDLE)socketHandle,
|
||||
completionModeFlags);
|
||||
if (!ok)
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error(
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"Error creating socket: SetFileCompletionNotificationModes");
|
||||
}
|
||||
}
|
||||
|
||||
if (socketType == SOCK_STREAM)
|
||||
{
|
||||
// Turn off linger so that the destructor doesn't block while closing
|
||||
// the socket or silently continue to flush remaining data in the
|
||||
// background after ::closesocket() is called, which could fail and
|
||||
// we'd never know about it.
|
||||
// We expect clients to call Disconnect() or use CloseSend() to cleanly
|
||||
// shut-down connections instead.
|
||||
BOOL value = TRUE;
|
||||
const int result = ::setsockopt(socketHandle,
|
||||
SOL_SOCKET,
|
||||
SO_DONTLINGER,
|
||||
reinterpret_cast<const char*>(&value),
|
||||
sizeof(value));
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
const int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Error creating socket: setsockopt(SO_DONTLINGER)");
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(socketHandle, skipCompletionPortOnSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::net::socket cppcoro::net::socket::create_tcpv4(io_service& ioSvc)
|
||||
{
|
||||
ioSvc.ensure_winsock_initialised();
|
||||
|
||||
auto[socketHandle, skipCompletionPortOnSuccess] = local::create_socket(
|
||||
AF_INET, SOCK_STREAM, IPPROTO_TCP, ioSvc.native_iocp_handle());
|
||||
|
||||
socket result(socketHandle, skipCompletionPortOnSuccess);
|
||||
result.m_localEndPoint = ipv4_endpoint();
|
||||
result.m_remoteEndPoint = ipv4_endpoint();
|
||||
return result;
|
||||
}
|
||||
|
||||
cppcoro::net::socket cppcoro::net::socket::create_tcpv6(io_service& ioSvc)
|
||||
{
|
||||
ioSvc.ensure_winsock_initialised();
|
||||
|
||||
auto[socketHandle, skipCompletionPortOnSuccess] = local::create_socket(
|
||||
AF_INET6, SOCK_STREAM, IPPROTO_TCP, ioSvc.native_iocp_handle());
|
||||
|
||||
socket result(socketHandle, skipCompletionPortOnSuccess);
|
||||
result.m_localEndPoint = ipv6_endpoint();
|
||||
result.m_remoteEndPoint = ipv6_endpoint();
|
||||
return result;
|
||||
}
|
||||
|
||||
cppcoro::net::socket cppcoro::net::socket::create_udpv4(io_service& ioSvc)
|
||||
{
|
||||
ioSvc.ensure_winsock_initialised();
|
||||
|
||||
auto[socketHandle, skipCompletionPortOnSuccess] = local::create_socket(
|
||||
AF_INET, SOCK_DGRAM, IPPROTO_UDP, ioSvc.native_iocp_handle());
|
||||
|
||||
socket result(socketHandle, skipCompletionPortOnSuccess);
|
||||
result.m_localEndPoint = ipv4_endpoint();
|
||||
result.m_remoteEndPoint = ipv4_endpoint();
|
||||
return result;
|
||||
}
|
||||
|
||||
cppcoro::net::socket cppcoro::net::socket::create_udpv6(io_service& ioSvc)
|
||||
{
|
||||
ioSvc.ensure_winsock_initialised();
|
||||
|
||||
auto[socketHandle, skipCompletionPortOnSuccess] = local::create_socket(
|
||||
AF_INET6, SOCK_DGRAM, IPPROTO_UDP, ioSvc.native_iocp_handle());
|
||||
|
||||
socket result(socketHandle, skipCompletionPortOnSuccess);
|
||||
result.m_localEndPoint = ipv6_endpoint();
|
||||
result.m_remoteEndPoint = ipv6_endpoint();
|
||||
return result;
|
||||
}
|
||||
|
||||
cppcoro::net::socket::socket(socket&& other) noexcept
|
||||
: m_handle(std::exchange(other.m_handle, INVALID_SOCKET))
|
||||
, m_skipCompletionOnSuccess(other.m_skipCompletionOnSuccess)
|
||||
, m_localEndPoint(std::move(other.m_localEndPoint))
|
||||
, m_remoteEndPoint(std::move(other.m_remoteEndPoint))
|
||||
{}
|
||||
|
||||
cppcoro::net::socket::~socket()
|
||||
{
|
||||
if (m_handle != INVALID_SOCKET)
|
||||
{
|
||||
::closesocket(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::net::socket&
|
||||
cppcoro::net::socket::operator=(socket&& other) noexcept
|
||||
{
|
||||
auto handle = std::exchange(other.m_handle, INVALID_SOCKET);
|
||||
if (m_handle != INVALID_SOCKET)
|
||||
{
|
||||
::closesocket(m_handle);
|
||||
}
|
||||
|
||||
m_handle = handle;
|
||||
m_skipCompletionOnSuccess = other.m_skipCompletionOnSuccess;
|
||||
m_localEndPoint = other.m_localEndPoint;
|
||||
m_remoteEndPoint = other.m_remoteEndPoint;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket::bind(const ip_endpoint& localEndPoint)
|
||||
{
|
||||
SOCKADDR_STORAGE sockaddrStorage = { 0 };
|
||||
SOCKADDR* sockaddr = reinterpret_cast<SOCKADDR*>(&sockaddrStorage);
|
||||
if (localEndPoint.is_ipv4())
|
||||
{
|
||||
SOCKADDR_IN& ipv4Sockaddr = *reinterpret_cast<SOCKADDR_IN*>(sockaddr);
|
||||
ipv4Sockaddr.sin_family = AF_INET;
|
||||
std::memcpy(&ipv4Sockaddr.sin_addr, localEndPoint.to_ipv4().address().bytes(), 4);
|
||||
ipv4Sockaddr.sin_port = localEndPoint.to_ipv4().port();
|
||||
}
|
||||
else
|
||||
{
|
||||
SOCKADDR_IN6& ipv6Sockaddr = *reinterpret_cast<SOCKADDR_IN6*>(sockaddr);
|
||||
ipv6Sockaddr.sin6_family = AF_INET6;
|
||||
std::memcpy(&ipv6Sockaddr.sin6_addr, localEndPoint.to_ipv6().address().bytes(), 16);
|
||||
ipv6Sockaddr.sin6_port = localEndPoint.to_ipv6().port();
|
||||
}
|
||||
|
||||
int result = ::bind(m_handle, sockaddr, sizeof(sockaddrStorage));
|
||||
if (result != 0)
|
||||
{
|
||||
// WSANOTINITIALISED: WSAStartup not called
|
||||
// WSAENETDOWN: network subsystem failed
|
||||
// WSAEACCES: access denied
|
||||
// WSAEADDRINUSE: port in use
|
||||
// WSAEADDRNOTAVAIL: address is not an address that can be bound to
|
||||
// WSAEFAULT: invalid pointer passed to bind()
|
||||
// WSAEINPROGRESS: a callback is in progress
|
||||
// WSAEINVAL: socket already bound
|
||||
// WSAENOBUFS: system failed to allocate memory
|
||||
// WSAENOTSOCK: socket was not a valid socket.
|
||||
int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Error binding to endpoint: bind()");
|
||||
}
|
||||
|
||||
int sockaddrLen = sizeof(sockaddrStorage);
|
||||
result = ::getsockname(m_handle, sockaddr, &sockaddrLen);
|
||||
if (result == 0)
|
||||
{
|
||||
m_localEndPoint = cppcoro::net::detail::sockaddr_to_ip_endpoint(*sockaddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_localEndPoint = localEndPoint;
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::net::socket::listen()
|
||||
{
|
||||
int result = ::listen(m_handle, SOMAXCONN);
|
||||
if (result != 0)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Failed to start listening on bound endpoint: listen");
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::net::socket::listen(std::uint32_t backlog)
|
||||
{
|
||||
if (backlog > 0x7FFFFFFF)
|
||||
{
|
||||
backlog = 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
int result = ::listen(m_handle, (int)backlog);
|
||||
if (result != 0)
|
||||
{
|
||||
// WSANOTINITIALISED: WSAStartup not called
|
||||
// WSAENETDOWN: network subsystem failed
|
||||
// WSAEADDRINUSE: port in use
|
||||
// WSAEINPROGRESS: a callback is in progress
|
||||
// WSAEINVAL: socket not yet bound
|
||||
// WSAEISCONN: socket already connected
|
||||
// WSAEMFILE: no more socket descriptors available
|
||||
// WSAENOBUFS: system failed to allocate memory
|
||||
// WSAENOTSOCK: socket was not a valid socket.
|
||||
// WSAEOPNOTSUPP: The socket does not support listening
|
||||
|
||||
int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Failed to start listening on bound endpoint: listen");
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::net::socket_accept_operation
|
||||
cppcoro::net::socket::accept(socket& acceptingSocket) noexcept
|
||||
{
|
||||
return socket_accept_operation{ *this, acceptingSocket };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_accept_operation_cancellable
|
||||
cppcoro::net::socket::accept(socket& acceptingSocket, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_accept_operation_cancellable{ *this, acceptingSocket, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_connect_operation
|
||||
cppcoro::net::socket::connect(const ip_endpoint& remoteEndPoint) noexcept
|
||||
{
|
||||
return socket_connect_operation{ *this, remoteEndPoint };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_connect_operation_cancellable
|
||||
cppcoro::net::socket::connect(const ip_endpoint& remoteEndPoint, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_connect_operation_cancellable{ *this, remoteEndPoint, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_disconnect_operation
|
||||
cppcoro::net::socket::disconnect() noexcept
|
||||
{
|
||||
return socket_disconnect_operation(*this);
|
||||
}
|
||||
|
||||
cppcoro::net::socket_disconnect_operation_cancellable
|
||||
cppcoro::net::socket::disconnect(cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_disconnect_operation_cancellable{ *this, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_send_operation
|
||||
cppcoro::net::socket::send(const void* buffer, std::size_t byteCount) noexcept
|
||||
{
|
||||
return socket_send_operation{ *this, buffer, byteCount };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_send_operation_cancellable
|
||||
cppcoro::net::socket::send(const void* buffer, std::size_t byteCount, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_send_operation_cancellable{ *this, buffer, byteCount, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_recv_operation
|
||||
cppcoro::net::socket::recv(void* buffer, std::size_t byteCount) noexcept
|
||||
{
|
||||
return socket_recv_operation{ *this, buffer, byteCount };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_recv_operation_cancellable
|
||||
cppcoro::net::socket::recv(void* buffer, std::size_t byteCount, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_recv_operation_cancellable{ *this, buffer, byteCount, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_recv_from_operation
|
||||
cppcoro::net::socket::recv_from(void* buffer, std::size_t byteCount) noexcept
|
||||
{
|
||||
return socket_recv_from_operation{ *this, buffer, byteCount };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_recv_from_operation_cancellable
|
||||
cppcoro::net::socket::recv_from(void* buffer, std::size_t byteCount, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_recv_from_operation_cancellable{ *this, buffer, byteCount, std::move(ct) };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_send_to_operation
|
||||
cppcoro::net::socket::send_to(const ip_endpoint& destination, const void* buffer, std::size_t byteCount) noexcept
|
||||
{
|
||||
return socket_send_to_operation{ *this, destination, buffer, byteCount };
|
||||
}
|
||||
|
||||
cppcoro::net::socket_send_to_operation_cancellable
|
||||
cppcoro::net::socket::send_to(const ip_endpoint& destination, const void* buffer, std::size_t byteCount, cancellation_token ct) noexcept
|
||||
{
|
||||
return socket_send_to_operation_cancellable{ *this, destination, buffer, byteCount, std::move(ct) };
|
||||
}
|
||||
|
||||
void cppcoro::net::socket::close_send()
|
||||
{
|
||||
int result = ::shutdown(m_handle, SD_SEND);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"failed to close socket send stream: shutdown(SD_SEND)");
|
||||
}
|
||||
}
|
||||
|
||||
void cppcoro::net::socket::close_recv()
|
||||
{
|
||||
int result = ::shutdown(m_handle, SD_RECEIVE);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error(
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"failed to close socket receive stream: shutdown(SD_RECEIVE)");
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::net::socket::socket(
|
||||
cppcoro::detail::win32::socket_t handle,
|
||||
bool skipCompletionOnSuccess) noexcept
|
||||
: m_handle(handle)
|
||||
, m_skipCompletionOnSuccess(skipCompletionOnSuccess)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,129 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_accept_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#include "socket_helpers.hpp"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
// TODO: Eliminate duplication of implementation between socket_accept_operation
|
||||
// and socket_accept_operation_cancellable.
|
||||
|
||||
bool cppcoro::net::socket_accept_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
static_assert(
|
||||
(sizeof(m_addressBuffer) / 2) >= (16 + sizeof(SOCKADDR_IN)) &&
|
||||
(sizeof(m_addressBuffer) / 2) >= (16 + sizeof(SOCKADDR_IN6)),
|
||||
"AcceptEx requires address buffer to be at least 16 bytes more than largest address.");
|
||||
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_listeningSocket.skip_completion_on_success();
|
||||
|
||||
DWORD bytesReceived = 0;
|
||||
BOOL ok = ::AcceptEx(
|
||||
m_listeningSocket.native_handle(),
|
||||
m_acceptingSocket.native_handle(),
|
||||
m_addressBuffer,
|
||||
0,
|
||||
sizeof(m_addressBuffer) / 2,
|
||||
sizeof(m_addressBuffer) / 2,
|
||||
&bytesReceived,
|
||||
operation.get_overlapped());
|
||||
if (!ok)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != ERROR_IO_PENDING)
|
||||
{
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_accept_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_listeningSocket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_accept_operation_impl::get_result(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation)
|
||||
{
|
||||
if (operation.m_errorCode != ERROR_SUCCESS)
|
||||
{
|
||||
throw std::system_error{
|
||||
static_cast<int>(operation.m_errorCode),
|
||||
std::system_category(),
|
||||
"Accepting a connection failed: AcceptEx"
|
||||
};
|
||||
}
|
||||
|
||||
sockaddr* localSockaddr = nullptr;
|
||||
sockaddr* remoteSockaddr = nullptr;
|
||||
|
||||
INT localSockaddrLength;
|
||||
INT remoteSockaddrLength;
|
||||
|
||||
::GetAcceptExSockaddrs(
|
||||
m_addressBuffer,
|
||||
0,
|
||||
sizeof(m_addressBuffer) / 2,
|
||||
sizeof(m_addressBuffer) / 2,
|
||||
&localSockaddr,
|
||||
&localSockaddrLength,
|
||||
&remoteSockaddr,
|
||||
&remoteSockaddrLength);
|
||||
|
||||
m_acceptingSocket.m_localEndPoint =
|
||||
detail::sockaddr_to_ip_endpoint(*localSockaddr);
|
||||
|
||||
m_acceptingSocket.m_remoteEndPoint =
|
||||
detail::sockaddr_to_ip_endpoint(*remoteSockaddr);
|
||||
|
||||
{
|
||||
// Need to set SO_UPDATE_ACCEPT_CONTEXT after the accept completes
|
||||
// to ensure that ::shutdown() and ::setsockopt() calls work on the
|
||||
// accepted socket.
|
||||
SOCKET listenSocket = m_listeningSocket.native_handle();
|
||||
const int result = ::setsockopt(
|
||||
m_acceptingSocket.native_handle(),
|
||||
SOL_SOCKET,
|
||||
SO_UPDATE_ACCEPT_CONTEXT,
|
||||
(const char*)&listenSocket,
|
||||
sizeof(SOCKET));
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
const int errorCode = ::WSAGetLastError();
|
||||
throw std::system_error{
|
||||
errorCode,
|
||||
std::system_category(),
|
||||
"Socket accept operation failed: setsockopt(SO_UPDATE_ACCEPT_CONTEXT)"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,178 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_connect_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#include <cppcoro/operation_cancelled.hpp>
|
||||
|
||||
#include "socket_helpers.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <system_error>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_connect_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
// Lookup the address of the ConnectEx function pointer for this socket.
|
||||
LPFN_CONNECTEX connectExPtr;
|
||||
{
|
||||
GUID connectExGuid = WSAID_CONNECTEX;
|
||||
DWORD byteCount = 0;
|
||||
int result = ::WSAIoctl(
|
||||
m_socket.native_handle(),
|
||||
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||
static_cast<void*>(&connectExGuid),
|
||||
sizeof(connectExGuid),
|
||||
static_cast<void*>(&connectExPtr),
|
||||
sizeof(connectExPtr),
|
||||
&byteCount,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
operation.m_errorCode = ::WSAGetLastError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
SOCKADDR_STORAGE remoteSockaddrStorage;
|
||||
const int sockaddrNameLength = cppcoro::net::detail::ip_endpoint_to_sockaddr(
|
||||
m_remoteEndPoint,
|
||||
std::ref(remoteSockaddrStorage));
|
||||
|
||||
DWORD bytesSent = 0;
|
||||
const BOOL ok = connectExPtr(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<const SOCKADDR*>(&remoteSockaddrStorage),
|
||||
sockaddrNameLength,
|
||||
nullptr, // send buffer
|
||||
0, // size of send buffer
|
||||
&bytesSent,
|
||||
operation.get_overlapped());
|
||||
if (!ok)
|
||||
{
|
||||
const int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != ERROR_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Successfully completed synchronously and no completion event
|
||||
// will be posted to an I/O thread so we can return without suspending.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_connect_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_connect_operation_impl::get_result(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation)
|
||||
{
|
||||
if (operation.m_errorCode != ERROR_SUCCESS)
|
||||
{
|
||||
if (operation.m_errorCode == ERROR_OPERATION_ABORTED)
|
||||
{
|
||||
throw operation_cancelled{};
|
||||
}
|
||||
|
||||
throw std::system_error{
|
||||
static_cast<int>(operation.m_errorCode),
|
||||
std::system_category(),
|
||||
"Connect operation failed: ConnectEx"
|
||||
};
|
||||
}
|
||||
|
||||
// We need to call setsockopt() to update the socket state with information
|
||||
// about the connection now that it has been successfully connected.
|
||||
{
|
||||
const int result = ::setsockopt(
|
||||
m_socket.native_handle(),
|
||||
SOL_SOCKET,
|
||||
SO_UPDATE_CONNECT_CONTEXT,
|
||||
nullptr,
|
||||
0);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
// This shouldn't fail, but just in case it does we fall back to
|
||||
// setting the remote address as specified in the call to Connect().
|
||||
//
|
||||
// Don't really want to throw an exception here since the connection
|
||||
// has actually been established.
|
||||
m_socket.m_remoteEndPoint = m_remoteEndPoint;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
SOCKADDR_STORAGE localSockaddr;
|
||||
int nameLength = sizeof(localSockaddr);
|
||||
const int result = ::getsockname(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<SOCKADDR*>(&localSockaddr),
|
||||
&nameLength);
|
||||
if (result == 0)
|
||||
{
|
||||
m_socket.m_localEndPoint = cppcoro::net::detail::sockaddr_to_ip_endpoint(
|
||||
*reinterpret_cast<const SOCKADDR*>(&localSockaddr));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to get the updated local-end-point
|
||||
// Just leave m_localEndPoint set to whatever bind() left it as.
|
||||
//
|
||||
// TODO: Should we be throwing an exception here instead?
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
SOCKADDR_STORAGE remoteSockaddr;
|
||||
int nameLength = sizeof(remoteSockaddr);
|
||||
const int result = ::getpeername(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<SOCKADDR*>(&remoteSockaddr),
|
||||
&nameLength);
|
||||
if (result == 0)
|
||||
{
|
||||
m_socket.m_remoteEndPoint = cppcoro::net::detail::sockaddr_to_ip_endpoint(
|
||||
*reinterpret_cast<const SOCKADDR*>(&remoteSockaddr));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to get the actual remote end-point so just fall back to
|
||||
// remembering the actual end-point that was passed to connect().
|
||||
//
|
||||
// TODO: Should we be throwing an exception here instead?
|
||||
m_socket.m_remoteEndPoint = m_remoteEndPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_disconnect_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#include "socket_helpers.hpp"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_disconnect_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
// Lookup the address of the DisconnectEx function pointer for this socket.
|
||||
LPFN_DISCONNECTEX disconnectExPtr;
|
||||
{
|
||||
GUID disconnectExGuid = WSAID_DISCONNECTEX;
|
||||
DWORD byteCount = 0;
|
||||
const int result = ::WSAIoctl(
|
||||
m_socket.native_handle(),
|
||||
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||
static_cast<void*>(&disconnectExGuid),
|
||||
sizeof(disconnectExGuid),
|
||||
static_cast<void*>(&disconnectExPtr),
|
||||
sizeof(disconnectExPtr),
|
||||
&byteCount,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
operation.m_errorCode = static_cast<DWORD>(::WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
// Need to add TF_REUSE_SOCKET to these flags if we want to allow reusing
|
||||
// a socket for subsequent connections once the disconnect operation
|
||||
// completes.
|
||||
const DWORD flags = 0;
|
||||
|
||||
const BOOL ok = disconnectExPtr(
|
||||
m_socket.native_handle(),
|
||||
operation.get_overlapped(),
|
||||
flags,
|
||||
0);
|
||||
if (!ok)
|
||||
{
|
||||
const int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != ERROR_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Successfully completed synchronously and no completion event
|
||||
// will be posted to an I/O thread so we can return without suspending.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_disconnect_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_disconnect_operation_impl::get_result(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation)
|
||||
{
|
||||
if (operation.m_errorCode != ERROR_SUCCESS)
|
||||
{
|
||||
if (operation.m_errorCode == ERROR_OPERATION_ABORTED)
|
||||
{
|
||||
throw operation_cancelled{};
|
||||
}
|
||||
|
||||
throw std::system_error{
|
||||
static_cast<int>(operation.m_errorCode),
|
||||
std::system_category(),
|
||||
"Disconnect operation failed: DisconnectEx"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "socket_helpers.hpp"
|
||||
|
||||
#include <cppcoro/net/ip_endpoint.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#include <MSWSock.h>
|
||||
#include <Windows.h>
|
||||
|
||||
|
||||
cppcoro::net::ip_endpoint
|
||||
cppcoro::net::detail::sockaddr_to_ip_endpoint(const sockaddr& address) noexcept
|
||||
{
|
||||
if (address.sa_family == AF_INET)
|
||||
{
|
||||
SOCKADDR_IN ipv4Address;
|
||||
std::memcpy(&ipv4Address, &address, sizeof(ipv4Address));
|
||||
|
||||
std::uint8_t addressBytes[4];
|
||||
std::memcpy(addressBytes, &ipv4Address.sin_addr, 4);
|
||||
|
||||
return ipv4_endpoint{
|
||||
ipv4_address{ addressBytes },
|
||||
ntohs(ipv4Address.sin_port)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(address.sa_family == AF_INET6);
|
||||
|
||||
SOCKADDR_IN6 ipv6Address;
|
||||
std::memcpy(&ipv6Address, &address, sizeof(ipv6Address));
|
||||
|
||||
return ipv6_endpoint{
|
||||
ipv6_address{ ipv6Address.sin6_addr.u.Byte },
|
||||
ntohs(ipv6Address.sin6_port)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int cppcoro::net::detail::ip_endpoint_to_sockaddr(
|
||||
const ip_endpoint& endPoint,
|
||||
std::reference_wrapper<sockaddr_storage> address) noexcept
|
||||
{
|
||||
if (endPoint.is_ipv4())
|
||||
{
|
||||
const auto& ipv4EndPoint = endPoint.to_ipv4();
|
||||
|
||||
SOCKADDR_IN ipv4Address;
|
||||
ipv4Address.sin_family = AF_INET;
|
||||
std::memcpy(&ipv4Address.sin_addr, ipv4EndPoint.address().bytes(), 4);
|
||||
ipv4Address.sin_port = htons(ipv4EndPoint.port());
|
||||
std::memset(&ipv4Address.sin_zero, 0, sizeof(ipv4Address.sin_zero));
|
||||
|
||||
std::memcpy(&address.get(), &ipv4Address, sizeof(ipv4Address));
|
||||
|
||||
return sizeof(SOCKADDR_IN);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& ipv6EndPoint = endPoint.to_ipv6();
|
||||
|
||||
SOCKADDR_IN6 ipv6Address;
|
||||
ipv6Address.sin6_family = AF_INET6;
|
||||
std::memcpy(&ipv6Address.sin6_addr, ipv6EndPoint.address().bytes(), 16);
|
||||
ipv6Address.sin6_port = htons(ipv6EndPoint.port());
|
||||
ipv6Address.sin6_flowinfo = 0;
|
||||
ipv6Address.sin6_scope_struct = SCOPEID_UNSPECIFIED_INIT;
|
||||
|
||||
std::memcpy(&address.get(), &ipv6Address, sizeof(ipv6Address));
|
||||
|
||||
return sizeof(SOCKADDR_IN6);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CPPCORO_OS_WINNT
|
||||
@@ -0,0 +1,47 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef CPPCORO_PRIVATE_SOCKET_HELPERS_HPP_INCLUDED
|
||||
#define CPPCORO_PRIVATE_SOCKET_HELPERS_HPP_INCLUDED
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <cppcoro/detail/win32.hpp>
|
||||
struct sockaddr;
|
||||
struct sockaddr_storage;
|
||||
#endif
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
namespace net
|
||||
{
|
||||
class ip_endpoint;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
#if CPPCORO_OS_WINNT
|
||||
/// Convert a sockaddr to an IP endpoint.
|
||||
ip_endpoint sockaddr_to_ip_endpoint(const sockaddr& address) noexcept;
|
||||
|
||||
/// Converts an ip_endpoint to a sockaddr structure.
|
||||
///
|
||||
/// \param endPoint
|
||||
/// The IP endpoint to convert to a sockaddr structure.
|
||||
///
|
||||
/// \param address
|
||||
/// The sockaddr structure to populate.
|
||||
///
|
||||
/// \return
|
||||
/// The length of the sockaddr structure that was populated.
|
||||
int ip_endpoint_to_sockaddr(
|
||||
const ip_endpoint& endPoint,
|
||||
std::reference_wrapper<sockaddr_storage> address) noexcept;
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_recv_from_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include "socket_helpers.hpp"
|
||||
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_recv_from_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
static_assert(
|
||||
sizeof(m_sourceSockaddrStorage) >= sizeof(SOCKADDR_IN) &&
|
||||
sizeof(m_sourceSockaddrStorage) >= sizeof(SOCKADDR_IN6));
|
||||
static_assert(
|
||||
sockaddrStorageAlignment >= alignof(SOCKADDR_IN) &&
|
||||
sockaddrStorageAlignment >= alignof(SOCKADDR_IN6));
|
||||
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread, resume the coroutine and then destroy the
|
||||
// socket before we get a chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
m_sourceSockaddrLength = sizeof(m_sourceSockaddrStorage);
|
||||
|
||||
DWORD numberOfBytesReceived = 0;
|
||||
DWORD flags = 0;
|
||||
int result = ::WSARecvFrom(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<WSABUF*>(&m_buffer),
|
||||
1, // buffer count
|
||||
&numberOfBytesReceived,
|
||||
&flags,
|
||||
reinterpret_cast<sockaddr*>(&m_sourceSockaddrStorage),
|
||||
&m_sourceSockaddrLength,
|
||||
operation.get_overlapped(),
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != WSA_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesReceived;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Completed synchronously, no completion event will be posted to the IOCP.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesReceived;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operation will complete asynchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_recv_from_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
std::tuple<std::size_t, cppcoro::net::ip_endpoint>
|
||||
cppcoro::net::socket_recv_from_operation_impl::get_result(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation)
|
||||
{
|
||||
if (operation.m_errorCode != ERROR_SUCCESS)
|
||||
{
|
||||
throw std::system_error(
|
||||
static_cast<int>(operation.m_errorCode),
|
||||
std::system_category(),
|
||||
"Error receiving message on socket: WSARecvFrom");
|
||||
}
|
||||
|
||||
return std::make_tuple(
|
||||
static_cast<std::size_t>(operation.m_numberOfBytesTransferred),
|
||||
detail::sockaddr_to_ip_endpoint(
|
||||
*reinterpret_cast<SOCKADDR*>(&m_sourceSockaddrStorage)));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_recv_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_recv_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
DWORD numberOfBytesReceived = 0;
|
||||
DWORD flags = 0;
|
||||
int result = ::WSARecv(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<WSABUF*>(&m_buffer),
|
||||
1, // buffer count
|
||||
&numberOfBytesReceived,
|
||||
&flags,
|
||||
operation.get_overlapped(),
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != WSA_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesReceived;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Completed synchronously, no completion event will be posted to the IOCP.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesReceived;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operation will complete asynchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void cppcoro::net::socket_recv_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_send_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_send_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
DWORD numberOfBytesSent = 0;
|
||||
int result = ::WSASend(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<WSABUF*>(&m_buffer),
|
||||
1, // buffer count
|
||||
&numberOfBytesSent,
|
||||
0, // flags
|
||||
operation.get_overlapped(),
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != WSA_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesSent;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Completed synchronously, no completion event will be posted to the IOCP.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesSent;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operation will complete asynchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_send_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/net/socket_send_to_operation.hpp>
|
||||
#include <cppcoro/net/socket.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# include "socket_helpers.hpp"
|
||||
|
||||
# include <WinSock2.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <MSWSock.h>
|
||||
# include <Windows.h>
|
||||
|
||||
bool cppcoro::net::socket_send_to_operation_impl::try_start(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
// Need to read this flag before starting the operation, otherwise
|
||||
// it may be possible that the operation will complete immediately
|
||||
// on another thread and then destroy the socket before we get a
|
||||
// chance to read it.
|
||||
const bool skipCompletionOnSuccess = m_socket.skip_completion_on_success();
|
||||
|
||||
SOCKADDR_STORAGE destinationAddress;
|
||||
const int destinationLength = detail::ip_endpoint_to_sockaddr(
|
||||
m_destination, std::ref(destinationAddress));
|
||||
|
||||
DWORD numberOfBytesSent = 0;
|
||||
int result = ::WSASendTo(
|
||||
m_socket.native_handle(),
|
||||
reinterpret_cast<WSABUF*>(&m_buffer),
|
||||
1, // buffer count
|
||||
&numberOfBytesSent,
|
||||
0, // flags
|
||||
reinterpret_cast<const SOCKADDR*>(&destinationAddress),
|
||||
destinationLength,
|
||||
operation.get_overlapped(),
|
||||
nullptr);
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = ::WSAGetLastError();
|
||||
if (errorCode != WSA_IO_PENDING)
|
||||
{
|
||||
// Failed synchronously.
|
||||
operation.m_errorCode = static_cast<DWORD>(errorCode);
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesSent;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (skipCompletionOnSuccess)
|
||||
{
|
||||
// Completed synchronously, no completion event will be posted to the IOCP.
|
||||
operation.m_errorCode = ERROR_SUCCESS;
|
||||
operation.m_numberOfBytesTransferred = numberOfBytesSent;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operation will complete asynchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
void cppcoro::net::socket_send_to_operation_impl::cancel(
|
||||
cppcoro::detail::win32_overlapped_operation_base& operation) noexcept
|
||||
{
|
||||
(void)::CancelIoEx(
|
||||
reinterpret_cast<HANDLE>(m_socket.native_handle()),
|
||||
operation.get_overlapped());
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "spin_mutex.hpp"
|
||||
#include "spin_wait.hpp"
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
spin_mutex::spin_mutex() noexcept
|
||||
: m_isLocked(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool spin_mutex::try_lock() noexcept
|
||||
{
|
||||
return !m_isLocked.exchange(true, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void spin_mutex::lock() noexcept
|
||||
{
|
||||
spin_wait wait;
|
||||
while (!try_lock())
|
||||
{
|
||||
while (m_isLocked.load(std::memory_order_relaxed))
|
||||
{
|
||||
wait.spin_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spin_mutex::unlock() noexcept
|
||||
{
|
||||
m_isLocked.store(false, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef CPPCORO_SPIN_MUTEX_HPP_INCLUDED
|
||||
#define CPPCORO_SPIN_MUTEX_HPP_INCLUDED
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
class spin_mutex
|
||||
{
|
||||
public:
|
||||
|
||||
/// Initialise the mutex to the unlocked state.
|
||||
spin_mutex() noexcept;
|
||||
|
||||
/// Attempt to lock the mutex without blocking
|
||||
///
|
||||
/// \return
|
||||
/// true if the lock was acquired, false if the lock was already held
|
||||
/// and could not be immediately acquired.
|
||||
bool try_lock() noexcept;
|
||||
|
||||
/// Block the current thread until the lock is acquired.
|
||||
///
|
||||
/// This will busy-wait until it acquires the lock.
|
||||
///
|
||||
/// This has 'acquire' memory semantics and synchronises
|
||||
/// with prior calls to unlock().
|
||||
void lock() noexcept;
|
||||
|
||||
/// Release the lock.
|
||||
///
|
||||
/// This has 'release' memory semantics and synchronises with
|
||||
/// lock() and try_lock().
|
||||
void unlock() noexcept;
|
||||
|
||||
private:
|
||||
|
||||
std::atomic<bool> m_isLocked;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "spin_wait.hpp"
|
||||
|
||||
#include <cppcoro/config.hpp>
|
||||
#include <thread>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
constexpr std::uint32_t yield_threshold = 10;
|
||||
}
|
||||
}
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
spin_wait::spin_wait() noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
bool spin_wait::next_spin_will_yield() const noexcept
|
||||
{
|
||||
return m_count >= local::yield_threshold;
|
||||
}
|
||||
|
||||
void spin_wait::reset() noexcept
|
||||
{
|
||||
static const std::uint32_t initialCount =
|
||||
std::thread::hardware_concurrency() > 1 ? 0 : local::yield_threshold;
|
||||
m_count = initialCount;
|
||||
}
|
||||
|
||||
void spin_wait::spin_one() noexcept
|
||||
{
|
||||
#if CPPCORO_OS_WINNT
|
||||
// Spin strategy taken from .NET System.SpinWait class.
|
||||
// I assume the Microsoft developers knew what they're doing.
|
||||
if (!next_spin_will_yield())
|
||||
{
|
||||
// CPU-level pause
|
||||
// Allow other hyper-threads to run while we busy-wait.
|
||||
|
||||
// Make each busy-spin exponentially longer
|
||||
const std::uint32_t loopCount = 2u << m_count;
|
||||
for (std::uint32_t i = 0; i < loopCount; ++i)
|
||||
{
|
||||
::YieldProcessor();
|
||||
::YieldProcessor();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've already spun a number of iterations.
|
||||
//
|
||||
const auto yieldCount = m_count - local::yield_threshold;
|
||||
if (yieldCount % 20 == 19)
|
||||
{
|
||||
// Yield remainder of time slice to another thread and
|
||||
// don't schedule this thread for a little while.
|
||||
::SleepEx(1, FALSE);
|
||||
}
|
||||
else if (yieldCount % 5 == 4)
|
||||
{
|
||||
// Yield remainder of time slice to another thread
|
||||
// that is ready to run (possibly from another processor?).
|
||||
::SleepEx(0, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Yield to another thread that is ready to run on the
|
||||
// current processor.
|
||||
::SwitchToThread();
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (next_spin_will_yield())
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
#endif
|
||||
|
||||
++m_count;
|
||||
if (m_count == 0)
|
||||
{
|
||||
// Don't wrap around to zero as this would go back to
|
||||
// busy-waiting.
|
||||
m_count = local::yield_threshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef CPPCORO_SPIN_WAIT_HPP_INCLUDED
|
||||
#define CPPCORO_SPIN_WAIT_HPP_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
class spin_wait
|
||||
{
|
||||
public:
|
||||
|
||||
spin_wait() noexcept;
|
||||
|
||||
bool next_spin_will_yield() const noexcept;
|
||||
|
||||
void spin_one() noexcept;
|
||||
|
||||
void reset() noexcept;
|
||||
|
||||
private:
|
||||
|
||||
std::uint32_t m_count;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,754 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/static_thread_pool.hpp>
|
||||
|
||||
#include "auto_reset_event.hpp"
|
||||
#include "spin_mutex.hpp"
|
||||
#include "spin_wait.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace local
|
||||
{
|
||||
// Keep each thread's local queue under 1MB
|
||||
constexpr std::size_t max_local_queue_size = 1024 * 1024 / sizeof(void*);
|
||||
constexpr std::size_t initial_local_queue_size = 256;
|
||||
}
|
||||
}
|
||||
|
||||
namespace cppcoro
|
||||
{
|
||||
thread_local static_thread_pool::thread_state* static_thread_pool::s_currentState = nullptr;
|
||||
thread_local static_thread_pool* static_thread_pool::s_currentThreadPool = nullptr;
|
||||
|
||||
class static_thread_pool::thread_state
|
||||
{
|
||||
public:
|
||||
|
||||
explicit thread_state()
|
||||
: m_localQueue(
|
||||
std::make_unique<std::atomic<schedule_operation*>[]>(
|
||||
local::initial_local_queue_size))
|
||||
, m_mask(local::initial_local_queue_size - 1)
|
||||
, m_head(0)
|
||||
, m_tail(0)
|
||||
, m_isSleeping(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool try_wake_up()
|
||||
{
|
||||
if (m_isSleeping.load(std::memory_order_seq_cst))
|
||||
{
|
||||
if (m_isSleeping.exchange(false, std::memory_order_seq_cst))
|
||||
{
|
||||
try
|
||||
{
|
||||
m_wakeUpEvent.set();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// TODO: What do we do here?
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void notify_intent_to_sleep() noexcept
|
||||
{
|
||||
m_isSleeping.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void sleep_until_woken() noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
m_wakeUpEvent.wait();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
bool approx_has_any_queued_work() const noexcept
|
||||
{
|
||||
return difference(
|
||||
m_head.load(std::memory_order_relaxed),
|
||||
m_tail.load(std::memory_order_relaxed)) > 0;
|
||||
}
|
||||
|
||||
bool has_any_queued_work() noexcept
|
||||
{
|
||||
std::scoped_lock lock{ m_remoteMutex };
|
||||
auto tail = m_tail.load(std::memory_order_relaxed);
|
||||
auto head = m_head.load(std::memory_order_seq_cst);
|
||||
return difference(head, tail) > 0;
|
||||
}
|
||||
|
||||
bool try_local_enqueue(schedule_operation*& operation) noexcept
|
||||
{
|
||||
// Head is only ever written-to by the current thread so we
|
||||
// are safe to use relaxed memory order when reading it.
|
||||
auto head = m_head.load(std::memory_order_relaxed);
|
||||
|
||||
// It is possible this method may be running concurrently with
|
||||
// try_remote_steal() which may have just speculatively incremented m_tail
|
||||
// trying to steal the last item in the queue but has not yet read the
|
||||
// queue item. So we need to make sure we don't write to the last available
|
||||
// space (at slot m_tail - 1) as this may still contain a pointer to an
|
||||
// operation that has not yet been executed.
|
||||
//
|
||||
// Note that it's ok to read stale values from m_tail since new values
|
||||
// won't ever decrease the number of available slots by more than 1.
|
||||
// Reading a stale value can just mean that sometimes the queue appears
|
||||
// empty when it may actually have slots free.
|
||||
//
|
||||
// Here m_mask is equal to buffersize - 1 so we can only write to a slot
|
||||
// if the number of items consumed in the queue (head - tail) is less than
|
||||
// the mask.
|
||||
auto tail = m_tail.load(std::memory_order_relaxed);
|
||||
if (difference(head, tail) < static_cast<offset_t>(m_mask))
|
||||
{
|
||||
// There is space left in the local buffer.
|
||||
m_localQueue[head & m_mask].store(operation, std::memory_order_relaxed);
|
||||
m_head.store(head + 1, std::memory_order_seq_cst);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_mask == local::max_local_queue_size)
|
||||
{
|
||||
// No space in the buffer and we don't want to grow
|
||||
// it any further.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate the new buffer before taking out the lock so that
|
||||
// we ensure we hold the lock for as short a time as possible.
|
||||
const size_t newSize = (m_mask + 1) * 2;
|
||||
|
||||
std::unique_ptr<std::atomic<schedule_operation*>[]> newLocalQueue{
|
||||
new (std::nothrow) std::atomic<schedule_operation*>[newSize]
|
||||
};
|
||||
if (!newLocalQueue)
|
||||
{
|
||||
// Unable to allocate more memory.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_remoteMutex.try_lock())
|
||||
{
|
||||
// Don't wait to acquire the lock if we can't get it immediately.
|
||||
// Fail and let it be enqueued to the global queue.
|
||||
// TODO: Should we have a per-thread overflow queue instead?
|
||||
return false;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{ std::adopt_lock, m_remoteMutex };
|
||||
|
||||
// We can now re-read tail, guaranteed that we are not seeing a stale version.
|
||||
tail = m_tail.load(std::memory_order_relaxed);
|
||||
|
||||
// Copy the existing operations.
|
||||
const size_t newMask = newSize - 1;
|
||||
for (size_t i = tail; i != head; ++i)
|
||||
{
|
||||
newLocalQueue[i & newMask].store(
|
||||
m_localQueue[i & m_mask].load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Finally, write the new operation to the queue.
|
||||
newLocalQueue[head & newMask].store(operation, std::memory_order_relaxed);
|
||||
|
||||
m_head.store(head + 1, std::memory_order_relaxed);
|
||||
m_localQueue = std::move(newLocalQueue);
|
||||
m_mask = newMask;
|
||||
return true;
|
||||
}
|
||||
|
||||
schedule_operation* try_local_pop() noexcept
|
||||
{
|
||||
// Cheap, approximate, no memory-barrier check for emptiness
|
||||
auto head = m_head.load(std::memory_order_relaxed);
|
||||
auto tail = m_tail.load(std::memory_order_relaxed);
|
||||
if (difference(head, tail) <= 0)
|
||||
{
|
||||
// Empty
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 3 classes of interleaving of try_local_pop() and try_remote_steal()
|
||||
// - local pop completes before remote steal (easy)
|
||||
// - remote steal completes before local pop (easy)
|
||||
// - both are executed concurrently, both see each other's writes (harder)
|
||||
|
||||
// Speculatively try to acquire the head item of the work queue by
|
||||
// decrementing the head cursor. This may race with a concurrent call
|
||||
// to try_remote_steal() that is also trying to speculatively increment
|
||||
// the tail cursor to steal from the other end of the queue. In the case
|
||||
// that they both try to dequeue the last/only item in the queue then we
|
||||
// need to fall back to locking to decide who wins
|
||||
|
||||
auto newHead = head - 1;
|
||||
m_head.store(newHead, std::memory_order_seq_cst);
|
||||
|
||||
tail = m_tail.load(std::memory_order_seq_cst);
|
||||
|
||||
if (difference(newHead, tail) < 0)
|
||||
{
|
||||
// There was a race to get the last item.
|
||||
// We don't know whether the remote steal saw our write
|
||||
// and decided to back off or not, so we acquire the mutex
|
||||
// so that we wait until the remote steal has completed so
|
||||
// we can see what decision it made.
|
||||
std::lock_guard lock{ m_remoteMutex };
|
||||
|
||||
// Use relaxed since the lock guarantees visibility of the writes
|
||||
// that the remote steal thread performed.
|
||||
tail = m_tail.load(std::memory_order_relaxed);
|
||||
|
||||
if (difference(newHead, tail) < 0)
|
||||
{
|
||||
// The other thread didn't see our write and stole the last item.
|
||||
// We need to restore the head back to it's old value.
|
||||
// We hold the mutex so can just use relaxed memory order for this.
|
||||
m_head.store(head, std::memory_order_relaxed);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// We successfully acquired an item from the queue.
|
||||
return m_localQueue[newHead & m_mask].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
schedule_operation* try_steal(bool* lockUnavailable = nullptr) noexcept
|
||||
{
|
||||
if (lockUnavailable == nullptr)
|
||||
{
|
||||
m_remoteMutex.lock();
|
||||
}
|
||||
else if (!m_remoteMutex.try_lock())
|
||||
{
|
||||
*lockUnavailable = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{ std::adopt_lock, m_remoteMutex };
|
||||
|
||||
auto tail = m_tail.load(std::memory_order_relaxed);
|
||||
auto head = m_head.load(std::memory_order_seq_cst);
|
||||
if (difference(head, tail) <= 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// It looks like there are items in the queue.
|
||||
// We'll speculatively try to steal one by incrementing
|
||||
// the tail cursor. As this may be running concurrently
|
||||
// with try_local_pop() which is also speculatively trying
|
||||
// to remove an item from the other end of the queue we
|
||||
// need to re-read the 'head' cursor afterwards to see
|
||||
// if there was a potential race to dequeue the last item.
|
||||
// Use seq_cst memory order both here and in try_local_pop()
|
||||
// to ensure that either we will see their write to head or
|
||||
// they will see our write to tail or we will both see each
|
||||
// other's writes.
|
||||
m_tail.store(tail + 1, std::memory_order_seq_cst);
|
||||
head = m_head.load(std::memory_order_seq_cst);
|
||||
|
||||
if (difference(head, tail) > 0)
|
||||
{
|
||||
// There was still an item in the queue after incrementing tail.
|
||||
// We managed to steal an item from the bottom of the stack.
|
||||
return m_localQueue[tail & m_mask].load(std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we failed to steal the last item.
|
||||
// Restore the old tail position.
|
||||
m_tail.store(tail, std::memory_order_seq_cst);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
using offset_t = std::make_signed_t<std::size_t>;
|
||||
|
||||
static constexpr offset_t difference(size_t a, size_t b)
|
||||
{
|
||||
return static_cast<offset_t>(a - b);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::atomic<schedule_operation*>[]> m_localQueue;
|
||||
std::size_t m_mask;
|
||||
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4324)
|
||||
#endif
|
||||
|
||||
//alignas(std::hardware_destructive_interference_size)
|
||||
std::atomic<std::size_t> m_head;
|
||||
|
||||
//alignas(std::hardware_destructive_interference_size)
|
||||
std::atomic<std::size_t> m_tail;
|
||||
|
||||
//alignas(std::hardware_destructive_interference_size)
|
||||
std::atomic<bool> m_isSleeping;
|
||||
spin_mutex m_remoteMutex;
|
||||
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
auto_reset_event m_wakeUpEvent;
|
||||
|
||||
};
|
||||
|
||||
void static_thread_pool::schedule_operation::await_suspend(
|
||||
cppcoro::coroutine_handle<> awaitingCoroutine) noexcept
|
||||
{
|
||||
m_awaitingCoroutine = awaitingCoroutine;
|
||||
m_threadPool->schedule_impl(this);
|
||||
}
|
||||
|
||||
static_thread_pool::static_thread_pool()
|
||||
: static_thread_pool(std::thread::hardware_concurrency())
|
||||
{
|
||||
}
|
||||
|
||||
static_thread_pool::static_thread_pool(std::uint32_t threadCount)
|
||||
: m_threadCount(threadCount > 0 ? threadCount : 1)
|
||||
, m_threadStates(std::make_unique<thread_state[]>(m_threadCount))
|
||||
, m_stopRequested(false)
|
||||
, m_globalQueueHead(nullptr)
|
||||
, m_globalQueueTail(nullptr)
|
||||
, m_sleepingThreadCount(0)
|
||||
{
|
||||
m_threads.reserve(threadCount);
|
||||
try
|
||||
{
|
||||
for (std::uint32_t i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
m_threads.emplace_back([this, i] { this->run_worker_thread(i); });
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static_thread_pool::~static_thread_pool()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void static_thread_pool::run_worker_thread(std::uint32_t threadIndex) noexcept
|
||||
{
|
||||
auto& localState = m_threadStates[threadIndex];
|
||||
s_currentState = &localState;
|
||||
s_currentThreadPool = this;
|
||||
|
||||
auto tryGetRemote = [&]()
|
||||
{
|
||||
// Try to get some new work first from the global queue
|
||||
// then if that queue is empty then try to steal from
|
||||
// the local queues of other worker threads.
|
||||
// We try to get new work from the global queue first
|
||||
// before stealing as stealing from other threads has
|
||||
// the side-effect of those threads running out of work
|
||||
// sooner and then having to steal work which increases
|
||||
// contention.
|
||||
auto* op = try_global_dequeue();
|
||||
if (op == nullptr)
|
||||
{
|
||||
op = try_steal_from_other_thread(threadIndex);
|
||||
}
|
||||
return op;
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Process operations from the local queue.
|
||||
schedule_operation* op;
|
||||
|
||||
while (true)
|
||||
{
|
||||
op = localState.try_local_pop();
|
||||
if (op == nullptr)
|
||||
{
|
||||
op = tryGetRemote();
|
||||
if (op == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
op->m_awaitingCoroutine.resume();
|
||||
}
|
||||
|
||||
// No more operations in the local queue or remote queue.
|
||||
//
|
||||
// We spin for a little while waiting for new items
|
||||
// to be enqueued. This avoids the expensive operation
|
||||
// of putting the thread to sleep and waking it up again
|
||||
// in the case that an external thread is queueing new work
|
||||
|
||||
cppcoro::spin_wait spinWait;
|
||||
while (true)
|
||||
{
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
if (is_shutdown_requested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
spinWait.spin_one();
|
||||
|
||||
if (approx_has_any_queued_work_for(threadIndex))
|
||||
{
|
||||
op = tryGetRemote();
|
||||
if (op != nullptr)
|
||||
{
|
||||
// Now that we've executed some work we can
|
||||
// return to normal processing since this work
|
||||
// might have queued some more work to the local
|
||||
// queue which we should process first.
|
||||
goto normal_processing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any work after spinning for a while, let's
|
||||
// put ourselves to sleep and wait to be woken up.
|
||||
|
||||
// First, let other threads know we're going to sleep.
|
||||
notify_intent_to_sleep(threadIndex);
|
||||
|
||||
// As notifying the other threads that we're sleeping may have
|
||||
// raced with other threads enqueueing more work, we need to
|
||||
// re-check whether there is any more work to be done so that
|
||||
// we don't get into a situation where we go to sleep and another
|
||||
// thread has enqueued some work and doesn't know to wake us up.
|
||||
|
||||
if (has_any_queued_work_for(threadIndex))
|
||||
{
|
||||
op = tryGetRemote();
|
||||
if (op != nullptr)
|
||||
{
|
||||
// Try to clear the intent to sleep so that some other thread
|
||||
// that subsequently enqueues some work won't mistakenly try
|
||||
// to wake this threadup when we are already running as there
|
||||
// might have been some other thread that it could have woken
|
||||
// up instead which could have resulted in increased parallelism.
|
||||
//
|
||||
// However, it's possible that some other thread may have already
|
||||
// tried to wake us up, in which case the auto_reset_event used to
|
||||
// wake up this thread may already be in the 'set' state. Leaving
|
||||
// it in this state won't really hurt. It'll just mean we might get
|
||||
// a spurious wake-up next time we try to go to sleep.
|
||||
try_clear_intent_to_sleep(threadIndex);
|
||||
|
||||
goto normal_processing;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_shutdown_requested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
localState.sleep_until_woken();
|
||||
}
|
||||
|
||||
normal_processing:
|
||||
assert(op != nullptr);
|
||||
op->m_awaitingCoroutine.resume();
|
||||
}
|
||||
}
|
||||
|
||||
void static_thread_pool::shutdown()
|
||||
{
|
||||
m_stopRequested.store(true, std::memory_order_relaxed);
|
||||
|
||||
for (std::uint32_t i = 0; i < m_threads.size(); ++i)
|
||||
{
|
||||
auto& threadState = m_threadStates[i];
|
||||
|
||||
// We should not be shutting down the thread pool if there is any
|
||||
// outstanding work in the queue. It is up to the application to
|
||||
// ensure all enqueued work has completed first.
|
||||
assert(!threadState.has_any_queued_work());
|
||||
|
||||
threadState.try_wake_up();
|
||||
}
|
||||
|
||||
for (auto& t : m_threads)
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
void static_thread_pool::schedule_impl(schedule_operation* operation) noexcept
|
||||
{
|
||||
if (s_currentThreadPool != this ||
|
||||
!s_currentState->try_local_enqueue(operation))
|
||||
{
|
||||
remote_enqueue(operation);
|
||||
}
|
||||
|
||||
wake_one_thread();
|
||||
}
|
||||
|
||||
void static_thread_pool::remote_enqueue(schedule_operation* operation) noexcept
|
||||
{
|
||||
auto* tail = m_globalQueueTail.load(std::memory_order_relaxed);
|
||||
do
|
||||
{
|
||||
operation->m_next = tail;
|
||||
} while (!m_globalQueueTail.compare_exchange_weak(
|
||||
tail,
|
||||
operation,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
bool static_thread_pool::has_any_queued_work_for(std::uint32_t threadIndex) noexcept
|
||||
{
|
||||
if (m_globalQueueTail.load(std::memory_order_seq_cst) != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_globalQueueHead.load(std::memory_order_seq_cst) != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (std::uint32_t i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
if (i == threadIndex) continue;
|
||||
if (m_threadStates[i].has_any_queued_work())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool static_thread_pool::approx_has_any_queued_work_for(std::uint32_t threadIndex) const noexcept
|
||||
{
|
||||
// Cheap, approximate, read-only implementation that checks whether any work has
|
||||
// been queued in the system somewhere. We try to avoid writes here so that we
|
||||
// don't bounce cache-lines around between threads/cores unnecessarily when
|
||||
// multiple threads are all spinning waiting for work.
|
||||
|
||||
if (m_globalQueueTail.load(std::memory_order_relaxed) != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_globalQueueHead.load(std::memory_order_relaxed) != nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (std::uint32_t i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
if (i == threadIndex) continue;
|
||||
if (m_threadStates[i].approx_has_any_queued_work())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool static_thread_pool::is_shutdown_requested() const noexcept
|
||||
{
|
||||
return m_stopRequested.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void static_thread_pool::notify_intent_to_sleep(std::uint32_t threadIndex) noexcept
|
||||
{
|
||||
// First mark the thread as asleep
|
||||
m_threadStates[threadIndex].notify_intent_to_sleep();
|
||||
|
||||
// Then publish the fact that a thread is asleep by incrementing the count
|
||||
// of threads that are asleep.
|
||||
m_sleepingThreadCount.fetch_add(1, std::memory_order_seq_cst);
|
||||
}
|
||||
|
||||
void static_thread_pool::try_clear_intent_to_sleep(std::uint32_t threadIndex) noexcept
|
||||
{
|
||||
// First try to claim that we are waking up one of the threads.
|
||||
std::uint32_t oldSleepingCount = m_sleepingThreadCount.load(std::memory_order_relaxed);
|
||||
do
|
||||
{
|
||||
if (oldSleepingCount == 0)
|
||||
{
|
||||
// No more sleeping threads.
|
||||
// Someone must have woken us up.
|
||||
return;
|
||||
}
|
||||
} while (!m_sleepingThreadCount.compare_exchange_weak(
|
||||
oldSleepingCount,
|
||||
oldSleepingCount - 1,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed));
|
||||
|
||||
// Then preferentially try to wake up our thread.
|
||||
// If some other thread has already requested that this thread wake up
|
||||
// then we will wake up another thread - the one that should have been woken
|
||||
// up by the thread that woke this thread up.
|
||||
if (!m_threadStates[threadIndex].try_wake_up())
|
||||
{
|
||||
for (std::uint32_t i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
if (i == threadIndex) continue;
|
||||
if (m_threadStates[i].try_wake_up())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static_thread_pool::schedule_operation*
|
||||
static_thread_pool::try_global_dequeue() noexcept
|
||||
{
|
||||
std::scoped_lock lock{ m_globalQueueMutex };
|
||||
|
||||
auto* head = m_globalQueueHead.load(std::memory_order_relaxed);
|
||||
if (head == nullptr)
|
||||
{
|
||||
// Use seq-cst memory order so that when we check for an item in the
|
||||
// global queue after signalling an intent to sleep that either we
|
||||
// will see their enqueue or they will see our signal to sleep and
|
||||
// wake us up.
|
||||
if (m_globalQueueTail.load(std::memory_order_seq_cst) == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Acquire the entire set of queued operations in a single operation.
|
||||
auto* tail = m_globalQueueTail.exchange(nullptr, std::memory_order_acquire);
|
||||
if (tail == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Reverse the list
|
||||
do
|
||||
{
|
||||
auto* next = std::exchange(tail->m_next, head);
|
||||
head = std::exchange(tail, next);
|
||||
} while (tail != nullptr);
|
||||
}
|
||||
|
||||
m_globalQueueHead = head->m_next;
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static_thread_pool::schedule_operation*
|
||||
static_thread_pool::try_steal_from_other_thread(std::uint32_t thisThreadIndex) noexcept
|
||||
{
|
||||
// Try first with non-blocking steal attempts.
|
||||
|
||||
bool anyLocksUnavailable = false;
|
||||
for (std::uint32_t otherThreadIndex = 0; otherThreadIndex < m_threadCount; ++otherThreadIndex)
|
||||
{
|
||||
if (otherThreadIndex == thisThreadIndex) continue;
|
||||
auto& otherThreadState = m_threadStates[otherThreadIndex];
|
||||
auto* op = otherThreadState.try_steal(&anyLocksUnavailable);
|
||||
if (op != nullptr)
|
||||
{
|
||||
return op;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyLocksUnavailable)
|
||||
{
|
||||
// We didn't check all of the other threads for work to steal yet.
|
||||
// Try again, this time waiting to acquire the locks.
|
||||
for (std::uint32_t otherThreadIndex = 0; otherThreadIndex < m_threadCount; ++otherThreadIndex)
|
||||
{
|
||||
if (otherThreadIndex == thisThreadIndex) continue;
|
||||
auto& otherThreadState = m_threadStates[otherThreadIndex];
|
||||
auto* op = otherThreadState.try_steal();
|
||||
if (op != nullptr)
|
||||
{
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void static_thread_pool::wake_one_thread() noexcept
|
||||
{
|
||||
// First try to claim responsibility for waking up one thread.
|
||||
// This first read must be seq_cst to ensure that either we have
|
||||
// visibility of another thread going to sleep or they have
|
||||
// visibility of our prior enqueue of an item.
|
||||
std::uint32_t oldSleepingCount = m_sleepingThreadCount.load(std::memory_order_seq_cst);
|
||||
do
|
||||
{
|
||||
if (oldSleepingCount == 0)
|
||||
{
|
||||
// No sleeping threads.
|
||||
// Someone must have woken us up.
|
||||
return;
|
||||
}
|
||||
} while (!m_sleepingThreadCount.compare_exchange_weak(
|
||||
oldSleepingCount,
|
||||
oldSleepingCount - 1,
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed));
|
||||
|
||||
// Now that we have claimed responsibility for waking a thread up
|
||||
// we need to find a sleeping thread and wake it up. We should be
|
||||
// guaranteed of finding a thread to wake-up here, but not necessarily
|
||||
// in a single pass due to threads potentially waking themselves up
|
||||
// in try_clear_intent_to_sleep().
|
||||
while (true)
|
||||
{
|
||||
for (std::uint32_t i = 0; i < m_threadCount; ++i)
|
||||
{
|
||||
if (m_threadStates[i].try_wake_up())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
###############################################################################
|
||||
# Copyright (c) Lewis Baker
|
||||
# Licenced under MIT license. See LICENSE.txt for details.
|
||||
###############################################################################
|
||||
|
||||
import cake.path
|
||||
|
||||
from cake.tools import script, env, compiler, variant
|
||||
|
||||
compiler.addIncludePath(env.expand('${CPPCORO}/include'))
|
||||
|
||||
buildScript = script.get(script.cwd('build.cake'))
|
||||
compiler.addLibrary(buildScript.getResult('library'))
|
||||
|
||||
if variant.platform == "windows":
|
||||
compiler.addLibrary("Synchronization")
|
||||
compiler.addLibrary("kernel32")
|
||||
compiler.addLibrary("WS2_32")
|
||||
compiler.addLibrary("Mswsock")
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/detail/win32.hpp>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
|
||||
void cppcoro::detail::win32::safe_handle::close() noexcept
|
||||
{
|
||||
if (m_handle != nullptr && m_handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
::CloseHandle(m_handle);
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro/writable_file.hpp>
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
void cppcoro::writable_file::set_size(
|
||||
std::uint64_t fileSize)
|
||||
{
|
||||
LARGE_INTEGER position;
|
||||
position.QuadPart = fileSize;
|
||||
|
||||
BOOL ok = ::SetFilePointerEx(m_fileHandle.handle(), position, nullptr, FILE_BEGIN);
|
||||
if (!ok)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error setting file size: SetFilePointerEx"
|
||||
};
|
||||
}
|
||||
|
||||
ok = ::SetEndOfFile(m_fileHandle.handle());
|
||||
if (!ok)
|
||||
{
|
||||
DWORD errorCode = ::GetLastError();
|
||||
throw std::system_error
|
||||
{
|
||||
static_cast<int>(errorCode),
|
||||
std::system_category(),
|
||||
"error setting file size: SetEndOfFile"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
cppcoro::file_write_operation cppcoro::writable_file::write(
|
||||
std::uint64_t offset,
|
||||
const void* buffer,
|
||||
std::size_t byteCount) noexcept
|
||||
{
|
||||
return file_write_operation{
|
||||
m_fileHandle.handle(),
|
||||
offset,
|
||||
buffer,
|
||||
byteCount
|
||||
};
|
||||
}
|
||||
|
||||
cppcoro::file_write_operation_cancellable cppcoro::writable_file::write(
|
||||
std::uint64_t offset,
|
||||
const void* buffer,
|
||||
std::size_t byteCount,
|
||||
cancellation_token ct) noexcept
|
||||
{
|
||||
return file_write_operation_cancellable{
|
||||
m_fileHandle.handle(),
|
||||
offset,
|
||||
buffer,
|
||||
byteCount,
|
||||
std::move(ct)
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) Lewis Baker
|
||||
// Licenced under MIT license. See LICENSE.txt for details.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cppcoro\write_only_file.hpp>
|
||||
|
||||
#if CPPCORO_OS_WINNT
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <Windows.h>
|
||||
|
||||
cppcoro::write_only_file cppcoro::write_only_file::open(
|
||||
io_service& ioService,
|
||||
const cppcoro::filesystem::path& path,
|
||||
file_open_mode openMode,
|
||||
file_share_mode shareMode,
|
||||
file_buffering_mode bufferingMode)
|
||||
{
|
||||
return write_only_file(file::open(
|
||||
GENERIC_WRITE,
|
||||
ioService,
|
||||
path,
|
||||
openMode,
|
||||
shareMode,
|
||||
bufferingMode));
|
||||
}
|
||||
|
||||
cppcoro::write_only_file::write_only_file(
|
||||
detail::win32::safe_handle&& fileHandle) noexcept
|
||||
: file(std::move(fileHandle))
|
||||
, writable_file(detail::win32::safe_handle{})
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user