This commit is contained in:
jeanlemotan
2024-07-02 18:13:47 +02:00
commit bbeaa887cd
173 changed files with 34365 additions and 0 deletions
+178
View File
@@ -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)
+285
View File
@@ -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;
}
+99
View File
@@ -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;
}
+122
View File
@@ -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;
}
}
}
}
+97
View File
@@ -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
}
+44
View File
@@ -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
View File
@@ -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,
)
+41
View File
@@ -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;
}
}
+97
View File
@@ -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();
}
+624
View File
@@ -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)
{
}
+108
View File
@@ -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
+108
View File
@@ -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
View File
@@ -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);
}
+53
View File
@@ -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
+53
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -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;
}
+27
View File
@@ -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;
}
+174
View File
@@ -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 };
}
+71
View File
@@ -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 };
}
+362
View File
@@ -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 };
}
+84
View File
@@ -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 };
}
+254
View File
@@ -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
+36
View File
@@ -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
+38
View File
@@ -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
+36
View File
@@ -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
View File
@@ -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
+129
View File
@@ -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
+178
View File
@@ -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
+107
View File
@@ -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
+85
View File
@@ -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
+47
View File
@@ -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
+96
View File
@@ -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
+66
View File
@@ -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
+64
View File
@@ -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
+72
View File
@@ -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
+37
View File
@@ -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);
}
}
+47
View File
@@ -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
+101
View File
@@ -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;
}
}
}
+31
View File
@@ -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
+754
View File
@@ -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;
}
}
}
}
}
+20
View File
@@ -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")
+20
View File
@@ -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;
}
}
+75
View File
@@ -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
+37
View File
@@ -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