Skip to content

Commit 52bf1dc

Browse files
committed
Support Windows using IOCP (#1)
1 parent 3156dc2 commit 52bf1dc

22 files changed

+1289
-581
lines changed

.github/workflows/compiler-support.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ jobs:
2121
- { tag: "ubuntu-2004_clang-11", name: "Ubuntu 20.04 Clang 11", cxx: "/usr/bin/clang++-11", cc: "/usr/bin/clang-11", runs-on: "ubuntu-20.04" }
2222
- { tag: "ubuntu-2004_clang-10", name: "Ubuntu 20.04 Clang 10", cxx: "/usr/bin/clang++-10", cc: "/usr/bin/clang-10", runs-on: "ubuntu-20.04" }
2323
- { tag: "ubuntu-2004_gcc-10", name: "Ubuntu 20.04 G++ 10", cxx: "/usr/bin/g++-10", cc: "/usr/bin/gcc-10", runs-on: "ubuntu-20.04" }
24-
#- { tag: "windows-2022_msvc17", name: "Windows Server 2022 MSVC 17", cxx: "", cc: "", runs-on: "windows-2022" }
25-
#- { tag: "windows-2019_msvc16", name: "Windows Server 2019 MSVC 16", cxx: "", cc: "", runs-on: "windows-2019" }
24+
- { tag: "windows-2022_msvc17", name: "Windows Server 2022 MSVC 17", cxx: "", cc: "", runs-on: "windows-2022" }
25+
- { tag: "windows-2019_msvc16", name: "Windows Server 2019 MSVC 16", cxx: "", cc: "", runs-on: "windows-2019" }
2626
- { tag: "macos-12_gcc-12", name: "MacOS 12 G++ 12", cxx: "g++-12", cc: "gcc-12", runs-on: "macos-12" }
2727
#- { tag: "macos-12_gcc-13", name: "MacOS 12 G++ 13", cxx: "g++-13", cc: "gcc-13", runs-on: "macos-12" }
2828
- { tag: "macos-12_gcc-14", name: "MacOS 12 G++ 14", cxx: "g++-14", cc: "gcc-14", runs-on: "macos-12" }

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,18 @@ add_library(
3434
${CMAKE_CURRENT_SOURCE_DIR}/src/address.cpp
3535
${CMAKE_CURRENT_SOURCE_DIR}/src/dns.cpp
3636
${CMAKE_CURRENT_SOURCE_DIR}/src/file.cpp
37+
${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_generic_unix.cpp
3738
${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_select.cpp
3839
${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_uring.cpp
40+
${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_iocp.cpp
3941
${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine.cpp
4042
${CMAKE_CURRENT_SOURCE_DIR}/src/io_service.cpp
4143
${CMAKE_CURRENT_SOURCE_DIR}/src/socket.cpp
4244
${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp)
4345
target_link_libraries(asyncpp_io PUBLIC asyncpp OpenSSL::SSL Threads::Threads)
46+
if(WIN32)
47+
target_link_libraries(asyncpp_io PUBLIC wsock32 ws2_32 ntdll)
48+
endif()
4449
target_include_directories(asyncpp_io
4550
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
4651
target_compile_features(asyncpp_io PUBLIC cxx_std_20)

include/asyncpp/io/address.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ namespace asyncpp::io {
9494
constexpr auto parse_part = [](std::string_view::const_iterator& it, std::string_view::const_iterator end) {
9595
if (it == end || (*it < '0' && *it > '9')) return -1;
9696
int32_t result = 0;
97-
while (*it >= '0' && *it <= '9') {
97+
while (it != end && *it >= '0' && *it <= '9') {
9898
result = (result * 10) + (*it - '0');
9999
it++;
100100
}
@@ -157,7 +157,7 @@ namespace asyncpp::io {
157157

158158
constexpr std::span<const uint8_t, 16> data() const noexcept { return m_data; }
159159
constexpr std::span<const uint8_t, 4> ipv4_data() const noexcept {
160-
return std::span<const uint8_t, 4>{&m_data[12], &m_data[16]};
160+
return std::span<const uint8_t, 4>{&m_data[12], &m_data[12] + 4};
161161
}
162162

163163
constexpr uint64_t subnet_prefix() const noexcept {
@@ -189,7 +189,7 @@ namespace asyncpp::io {
189189
}
190190
constexpr ipv4_address mapped_ipv4() const noexcept {
191191
if (!is_ipv4_mapped()) return ipv4_address();
192-
return ipv4_address(std::span<const uint8_t, 4>(&m_data[12], &m_data[16]));
192+
return ipv4_address(std::span<const uint8_t, 4>(&m_data[12], &m_data[12] + 4));
193193
}
194194

195195
std::string to_string(bool full = false) const {
@@ -249,7 +249,7 @@ namespace asyncpp::io {
249249
auto it = str.begin();
250250
auto part_start = it;
251251
bool is_v4_interop = false;
252-
if (*it == ':') {
252+
if (it != str.end() && *it == ':') {
253253
dcidx = idx++;
254254
it++;
255255
if (it == str.end() || *it != ':') return std::nullopt;
@@ -508,7 +508,7 @@ namespace std {
508508
template<>
509509
struct hash<asyncpp::io::uds_address> {
510510
size_t operator()(const asyncpp::io::uds_address& x) const noexcept {
511-
size_t res = 0;
511+
size_t res{};
512512
for (auto e : x.data())
513513
res = res ^ (e + 0x9e3779b99e3779b9ull + (res << 6) + (res >> 2));
514514
return res;
@@ -518,7 +518,7 @@ namespace std {
518518
template<>
519519
struct hash<asyncpp::io::address> {
520520
size_t operator()(const asyncpp::io::address& x) const noexcept {
521-
size_t res;
521+
size_t res{};
522522
switch (x.type()) {
523523
case asyncpp::io::address_type::ipv4: res = std::hash<asyncpp::io::ipv4_address>{}(x.ipv4()); break;
524524
case asyncpp::io::address_type::ipv6: res = std::hash<asyncpp::io::ipv6_address>{}(x.ipv6()); break;

include/asyncpp/io/detail/cancel_awaitable.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace asyncpp::io::detail {
2727
bool await_ready() const noexcept { return m_child.await_ready(); }
2828
bool await_suspend(coroutine_handle<> hdl) {
2929
if (m_stop_token.stop_requested()) {
30-
m_child.m_completion.result = -ECANCELED;
30+
m_child.m_completion.result = std::make_error_code(std::errc::operation_canceled);
3131
return false;
3232
}
3333
auto res = m_child.await_suspend(hdl);

include/asyncpp/io/detail/io_engine.h

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,62 @@
11
#pragma once
22
#include <asyncpp/io/endpoint.h>
33

4+
#include <cstddef>
5+
#include <ios>
46
#include <memory>
7+
#include <system_error>
58

69
namespace asyncpp::io::detail {
710
class io_engine {
811
public:
12+
#ifndef _WIN32
913
using file_handle_t = int;
1014
constexpr static file_handle_t invalid_file_handle = -1;
1115
using socket_handle_t = int;
1216
constexpr static socket_handle_t invalid_socket_handle = -1;
17+
#else
18+
using file_handle_t = void*;
19+
constexpr static file_handle_t invalid_file_handle = reinterpret_cast<void*>(static_cast<long long>(-1));
20+
using socket_handle_t = unsigned long long;
21+
constexpr static socket_handle_t invalid_socket_handle = ~static_cast<socket_handle_t>(0);
22+
23+
#endif
1324
enum class fsync_flags { none, datasync };
25+
enum class socket_type { stream, dgram, seqpacket };
1426

1527
struct completion_data {
28+
completion_data(void (*cb)(void*) = nullptr, void* udata = nullptr) noexcept
29+
: callback(cb), userdata(udata) {}
30+
31+
// Private data the engine can use to associate state
32+
alignas(std::max_align_t) std::array<std::byte, 256> engine_state{};
33+
1634
// Info provided by caller
17-
void (*callback)(void*);
18-
void* userdata;
35+
void (*callback)(void*){};
36+
void* userdata{};
1937

2038
// Filled by io_engine
21-
int result;
39+
std::error_code result{};
40+
union {
41+
socket_handle_t result_handle{};
42+
size_t result_size;
43+
};
2244

23-
// Private data the engine can use to associate state
24-
void* engine_state{};
45+
template<typename T>
46+
T* es_init() noexcept {
47+
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T> &&
48+
std::is_trivially_destructible_v<T>);
49+
static_assert(sizeof(T) <= std::tuple_size_v<decltype(engine_state)>);
50+
engine_state.fill(std::byte{});
51+
return new (engine_state.data()) T();
52+
}
53+
template<typename T>
54+
T* es_get() noexcept {
55+
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T> &&
56+
std::is_trivially_destructible_v<T>);
57+
static_assert(sizeof(T) <= std::tuple_size_v<decltype(engine_state)>);
58+
return reinterpret_cast<T*>(engine_state.data());
59+
}
2560
};
2661

2762
public:
@@ -33,6 +68,18 @@ namespace asyncpp::io::detail {
3368
virtual void wake() = 0;
3469

3570
// Networking api
71+
virtual socket_handle_t socket_create(address_type domain, socket_type type) = 0;
72+
virtual std::pair<socket_handle_t, socket_handle_t> socket_create_connected_pair(address_type domain,
73+
socket_type type) = 0;
74+
virtual void socket_register(socket_handle_t socket) = 0;
75+
virtual void socket_release(socket_handle_t socket) = 0;
76+
virtual void socket_close(socket_handle_t socket) = 0;
77+
virtual void socket_bind(socket_handle_t socket, endpoint ep) = 0;
78+
virtual void socket_listen(socket_handle_t socket, size_t backlog) = 0;
79+
virtual endpoint socket_local_endpoint(socket_handle_t socket) = 0;
80+
virtual endpoint socket_remote_endpoint(socket_handle_t socket) = 0;
81+
virtual void socket_enable_broadcast(socket_handle_t socket, bool enable) = 0;
82+
virtual void socket_shutdown(socket_handle_t socket, bool receive, bool send) = 0;
3683
virtual bool enqueue_connect(socket_handle_t socket, endpoint ep, completion_data* cd) = 0;
3784
virtual bool enqueue_accept(socket_handle_t socket, completion_data* cd) = 0;
3885
virtual bool enqueue_recv(socket_handle_t socket, void* buf, size_t len, completion_data* cd) = 0;
@@ -43,8 +90,13 @@ namespace asyncpp::io::detail {
4390
completion_data* cd) = 0;
4491

4592
// Filesystem IO
46-
virtual bool enqueue_readv(file_handle_t fd, void* buf, size_t len, off_t offset, completion_data* cd) = 0;
47-
virtual bool enqueue_writev(file_handle_t fd, const void* buf, size_t len, off_t offset,
93+
virtual file_handle_t file_open(const char* filename, std::ios_base::openmode mode) = 0;
94+
virtual void file_register(file_handle_t fd) = 0;
95+
virtual void file_release(file_handle_t fd) = 0;
96+
virtual void file_close(file_handle_t fd) = 0;
97+
virtual uint64_t file_size(file_handle_t fd) = 0;
98+
virtual bool enqueue_readv(file_handle_t fd, void* buf, size_t len, uint64_t offset, completion_data* cd) = 0;
99+
virtual bool enqueue_writev(file_handle_t fd, const void* buf, size_t len, uint64_t offset,
48100
completion_data* cd) = 0;
49101
virtual bool enqueue_fsync(file_handle_t fd, fsync_flags flags, completion_data* cd) = 0;
50102

include/asyncpp/io/endpoint.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ namespace asyncpp::io {
108108
m_ipv6 = {addr.ipv6(), port};
109109
m_type = address_type::ipv6;
110110
break;
111+
#ifndef _WIN32
111112
case address_type::uds:
112113
m_uds = addr.uds();
113114
m_type = address_type::uds;
114115
break;
116+
#endif
115117
}
116118
}
117119
explicit constexpr endpoint(ipv4_endpoint ep) noexcept : m_ipv4(ep), m_type(address_type::ipv4) {}
@@ -128,15 +130,19 @@ namespace asyncpp::io {
128130
constexpr ipv4_endpoint ipv4() const noexcept {
129131
switch (m_type) {
130132
case address_type::ipv4: return m_ipv4;
131-
case address_type::ipv6:
133+
case address_type::ipv6: return {};
134+
#ifndef _WIN32
132135
case address_type::uds: return {};
136+
#endif
133137
}
134138
}
135139
constexpr ipv6_endpoint ipv6() const noexcept {
136140
switch (m_type) {
137141
case address_type::ipv4: return {};
138142
case address_type::ipv6: return m_ipv6;
143+
#ifndef _WIN32
139144
case address_type::uds: return {};
145+
#endif
140146
}
141147
}
142148
#ifndef _WIN32
@@ -213,7 +219,7 @@ namespace std {
213219
template<>
214220
struct hash<asyncpp::io::endpoint> {
215221
size_t operator()(const asyncpp::io::endpoint& x) const noexcept {
216-
size_t res;
222+
size_t res{};
217223
switch (x.type()) {
218224
case asyncpp::io::address_type::ipv4: res = std::hash<asyncpp::io::ipv4_endpoint>{}(x.ipv4()); break;
219225
case asyncpp::io::address_type::ipv6: res = std::hash<asyncpp::io::ipv6_endpoint>{}(x.ipv6()); break;

include/asyncpp/io/file.h

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ namespace asyncpp::io {
102102
detail::io_engine::completion_data m_completion;
103103

104104
public:
105-
constexpr file_read_awaitable(io_engine* engine, io_engine::file_handle_t fd, void* buf, size_t len,
106-
uint64_t offset, std::error_code* ec) noexcept
105+
file_read_awaitable(io_engine* engine, io_engine::file_handle_t fd, void* buf, size_t len, uint64_t offset,
106+
std::error_code* ec) noexcept
107107
: m_engine(engine), m_fd(fd), m_buf(buf), m_len(len), m_offset(offset), m_ec(ec), m_completion{} {}
108108
bool await_ready() const noexcept { return false; }
109109
bool await_suspend(coroutine_handle<> hdl) {
@@ -112,10 +112,9 @@ namespace asyncpp::io {
112112
return !m_engine->enqueue_readv(m_fd, m_buf, m_len, m_offset, &m_completion);
113113
}
114114
size_t await_resume() {
115-
if (m_completion.result >= 0) return static_cast<size_t>(m_completion.result);
116-
if (m_ec == nullptr)
117-
throw std::system_error(std::error_code(-m_completion.result, std::system_category()));
118-
*m_ec = std::error_code(-m_completion.result, std::system_category());
115+
if (!m_completion.result) return m_completion.result_size;
116+
if (m_ec == nullptr) throw std::system_error(m_completion.result);
117+
*m_ec = m_completion.result;
119118
return 0;
120119
}
121120
};
@@ -140,8 +139,8 @@ namespace asyncpp::io {
140139
detail::io_engine::completion_data m_completion;
141140

142141
public:
143-
constexpr file_write_awaitable(io_engine* engine, io_engine::file_handle_t fd, const void* buf, size_t len,
144-
uint64_t offset, std::error_code* ec) noexcept
142+
file_write_awaitable(io_engine* engine, io_engine::file_handle_t fd, const void* buf, size_t len,
143+
uint64_t offset, std::error_code* ec) noexcept
145144
: m_engine(engine), m_fd(fd), m_buf(buf), m_len(len), m_offset(offset), m_ec(ec), m_completion{} {}
146145
bool await_ready() const noexcept { return false; }
147146
bool await_suspend(coroutine_handle<> hdl) {
@@ -150,10 +149,9 @@ namespace asyncpp::io {
150149
return !m_engine->enqueue_writev(m_fd, m_buf, m_len, m_offset, &m_completion);
151150
}
152151
size_t await_resume() {
153-
if (m_completion.result >= 0) return static_cast<size_t>(m_completion.result);
154-
if (m_ec == nullptr)
155-
throw std::system_error(std::error_code(-m_completion.result, std::system_category()));
156-
*m_ec = std::error_code(-m_completion.result, std::system_category());
152+
if (!m_completion.result) return m_completion.result_size;
153+
if (m_ec == nullptr) throw std::system_error(m_completion.result);
154+
*m_ec = m_completion.result;
157155
return 0;
158156
}
159157
};
@@ -175,7 +173,7 @@ namespace asyncpp::io {
175173
detail::io_engine::completion_data m_completion;
176174

177175
public:
178-
constexpr file_fsync_awaitable(io_engine* engine, io_engine::file_handle_t fd, std::error_code* ec) noexcept
176+
file_fsync_awaitable(io_engine* engine, io_engine::file_handle_t fd, std::error_code* ec) noexcept
179177
: m_engine(engine), m_fd(fd), m_ec(ec), m_completion{} {}
180178
bool await_ready() const noexcept { return false; }
181179
bool await_suspend(coroutine_handle<> hdl) {
@@ -184,10 +182,9 @@ namespace asyncpp::io {
184182
return !m_engine->enqueue_fsync(m_fd, io_engine::fsync_flags::none, &m_completion);
185183
}
186184
void await_resume() {
187-
if (m_completion.result >= 0) return;
188-
if (m_ec == nullptr)
189-
throw std::system_error(std::error_code(-m_completion.result, std::system_category()));
190-
*m_ec = std::error_code(-m_completion.result, std::system_category());
185+
if (!m_completion.result) return;
186+
if (m_ec == nullptr) throw std::system_error(m_completion.result);
187+
*m_ec = m_completion.result;
191188
}
192189
};
193190
} // namespace detail
@@ -269,7 +266,7 @@ namespace asyncpp::io {
269266
file(const file&) = delete;
270267
file(file&&) noexcept;
271268
file& operator=(const file&) = delete;
272-
file& operator=(file&&);
269+
file& operator=(file&&) noexcept;
273270
~file();
274271

275272
[[nodiscard]] io_service& service() const noexcept { return *m_io; }

0 commit comments

Comments
 (0)