Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions .github/compilers.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,36 +92,37 @@
],
"msvc": [
{
"version": "14.34",
"version": "14.44",
"cxxstd": "20",
"latest_cxxstd": "20",
"runs_on": "windows-2022",
"b2_toolset": "msvc-14.3",
"b2_toolset": "msvc-14.4",
"generator": "Visual Studio 17 2022",
"is_earliest": true
},
{
"version": "14.44",
"version": "14.51",
"cxxstd": "20",
"latest_cxxstd": "20",
"runs_on": "windows-2022",
"b2_toolset": "msvc-14.4",
"generator": "Visual Studio 17 2022",
"runs_on": "windows-2025-vs2026",
"b2_toolset": "msvc-14.5",
"generator": "Ninja",
"is_latest": true
},
{
"version": "14.34",
"version": "14.44",
"cxxstd": "20",
"latest_cxxstd": "20",
"runs_on": "windows-11-arm",
"b2_toolset": "msvc-14.3"
"runs_on": "windows-11-vs2026-arm",
"b2_toolset": "msvc-14.4",
"msvc_toolset": "14.44"
},
{
"version": "14.44",
"version": "14.51",
"cxxstd": "20",
"latest_cxxstd": "20",
"runs_on": "windows-11-arm",
"b2_toolset": "msvc-14.4"
"runs_on": "windows-11-vs2026-arm",
"b2_toolset": "msvc-14.5"
}
],
"clang-cl": [
Expand Down
2 changes: 2 additions & 0 deletions .github/generate-matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def make_entry(compiler_family, spec, **overrides):
entry["shared"] = spec["shared"]
if spec.get("vcpkg_triplet"):
entry["vcpkg-triplet"] = spec["vcpkg_triplet"]
if spec.get("msvc_toolset"):
entry["msvc-toolset"] = spec["msvc_toolset"]

# CMake builds only on earliest/latest compilers, unless explicitly disabled
if spec.get("build_cmake") is False:
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,51 @@ jobs:
check-latest: true
trace-commands: true

# setup-cpp ignores the MSVC version and loads the image's default
# toolset, so without this check the matrix version is only a label.
# A side-by-side toolset (msvc-toolset) is pinned for B2 through a
# setup script, because B2 re-runs vcvarsall before building.
- name: Configure MSVC
id: msvc
if: matrix.compiler == 'msvc'
env:
MSVC_VERSION: ${{ matrix.version }}
MSVC_TOOLSET: ${{ matrix.msvc-toolset }}
B2_TOOLSET: ${{ matrix.b2-toolset }}
run: |
set -euo pipefail
# Not PROCESSOR_ARCHITECTURE: on ARM64 runners this bash step
# does not see ARM64 there, and picked the x64 toolset.
case "$RUNNER_ARCH" in
ARM64) arch=arm64; bin=HostARM64/ARM64; target=ARM64 ;;
X64) arch=amd64; bin=Hostx64/x64; target=x64 ;;
*) echo "::error::Unsupported runner architecture: $RUNNER_ARCH"; exit 1 ;;
esac
check="$RUNNER_TEMP/msvc-banner.bat"
if [ -n "$MSVC_TOOLSET" ]; then
setup="$RUNNER_TEMP/vcvars-$MSVC_TOOLSET.bat"
printf '@call "%sAuxiliary\\Build\\vcvarsall.bat" %%* -vcvars_ver=%s\r\n' \
"$VCINSTALLDIR" "$MSVC_TOOLSET" > "$setup"
tools=( "$(cygpath -m "$VCINSTALLDIR")Tools/MSVC/$MSVC_TOOLSET."* )
cl="${tools[-1]}/bin/$bin/cl.exe"
test -f "$cl"
user_config="$RUNNER_TEMP/user-config.jam"
echo "using msvc : ${B2_TOOLSET#msvc-} : \"$cl\" : <setup>\"$(cygpath -m "$setup")\" ;" \
> "$user_config"
cat "$user_config"
echo "user-config=$(cygpath -m "$user_config")" >> "$GITHUB_OUTPUT"
printf '@call "%s" %s >nul\r\n@cl\r\n' "$(cygpath -w "$setup")" "$arch" > "$check"
else
printf '@cl\r\n' > "$check"
fi
out=$(cmd //c "$(cygpath -w "$check")" 2>&1 || true)
banner=$(printf '%s\n' "$out" | tr -d '\r' | grep -m1 'Compiler Version' || true)
echo "$banner"
case "$banner" in
*"Version 19.${MSVC_VERSION#14.}."*" for $target") ;;
*) echo "::error::Expected MSVC $MSVC_VERSION for $target, got: $banner"; exit 1 ;;
esac

- name: Install packages
uses: alandefreitas/cpp-actions/package-install@v1.9.0
id: package-install
Expand Down Expand Up @@ -494,6 +539,10 @@ jobs:
source-dir: boost-root
modules: corosio
toolset: ${{ matrix.b2-toolset }}
# Set only for a pinned msvc-toolset. B2 then reads this file
# instead of any other user-config.jam, so a pinned x64 toolset
# must also carry the "using openssl" line.
user-config: ${{ steps.msvc.outputs.user-config }}
build-variant: ${{ (matrix.compiler == 'msvc' && 'debug,release') || matrix.build-type }}
cxx: ${{ matrix.cxx || steps.setup-cpp.outputs.cxx || '' }}
cxxstd: ${{ matrix.cxxstd }}
Expand Down
155 changes: 81 additions & 74 deletions test/unit/socket_stress.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//
// Copyright (c) 2026 Vinnie Falco
// Copyright (c) 2026 Steve Gerbino
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -375,6 +376,85 @@ struct cancel_close_stress_test
std::atomic<std::size_t> cancel_writes{0};
std::atomic<bool> stop_flag{false};

// One cancel cycle. It is a separate coroutine rather than
// inline in worker's try block because MSVC 19.51.36257 x64
// hits an internal compiler error (C1001, Utc\src\p2\main.cpp
// line 262) at /O2 when a co_await of corosio::delay sits in
// a loop inside a coroutine's try block.
auto iteration = [&](int i) -> capy::task<> {
// Start a blocking read - use atomic for thread-safe signaling
char buf[32];
std::atomic<bool> read_done{false};
std::error_code read_ec;

auto read_coro = [&read_done, &read_ec, &s2,
&buf]() -> capy::task<> {
auto [ec, n] = co_await s2.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
read_ec = ec;
read_done.store(true, std::memory_order_release);
};

capy::run_async(ex)(read_coro());

// Vary the cancellation method
switch (i % 3)
{
case 0:
{
// Yield to let the posted read_coro start
std::ignore =
co_await corosio::delay(std::chrono::microseconds(1));
// Cancel via tcp_socket.cancel()
s2.cancel();
++cancels;
break;
}
case 1:
// Write data to complete the read normally
{
[[maybe_unused]] auto [ec, n] =
co_await s1.write_some(capy::const_buffer("data", 4));
}
++writes;
break;
case 2:
// Cancel then immediately write (race)
s2.cancel();
{
[[maybe_unused]] auto [ec, n] =
co_await s1.write_some(capy::const_buffer("data", 4));
}
++cancel_writes;
break;
}

// Poll for read completion with timeout (max 1 second)
for (int wait = 0; wait < 100; ++wait)
{
if (read_done.load(std::memory_order_acquire))
break;
std::ignore =
co_await corosio::delay(std::chrono::milliseconds(10));
}

if (!read_done.load(std::memory_order_acquire))
{
std::fprintf(
stderr,
" cancel_close_stress: read hung on case %d, "
"iter %d\n",
i % 3, i);
BOOST_TEST(read_done.load(std::memory_order_acquire));
// Force cancel
s2.cancel();
std::ignore =
co_await corosio::delay(std::chrono::milliseconds(100));
}

++iterations;
};

// Worker: rapidly cancel operations on pre-created sockets
auto worker = [&]() -> capy::task<> {
while (!stop_flag.load(std::memory_order_relaxed))
Expand All @@ -385,80 +465,7 @@ struct cancel_close_stress_test
i < 50 && !stop_flag.load(std::memory_order_relaxed);
++i)
{
// Start a blocking read - use atomic for thread-safe signaling
char buf[32];
std::atomic<bool> read_done{false};
std::error_code read_ec;

auto read_coro = [&read_done, &read_ec, &s2,
&buf]() -> capy::task<> {
auto [ec, n] = co_await s2.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
read_ec = ec;
read_done.store(true, std::memory_order_release);
};

capy::run_async(ex)(read_coro());

// Vary the cancellation method
switch (i % 3)
{
case 0:
{
// Yield to let the posted read_coro start
std::ignore = co_await corosio::delay(
std::chrono::microseconds(1));
// Cancel via tcp_socket.cancel()
s2.cancel();
++cancels;
break;
}
case 1:
// Write data to complete the read normally
{
[[maybe_unused]] auto [ec, n] =
co_await s1.write_some(
capy::const_buffer("data", 4));
}
++writes;
break;
case 2:
// Cancel then immediately write (race)
s2.cancel();
{
[[maybe_unused]] auto [ec, n] =
co_await s1.write_some(
capy::const_buffer("data", 4));
}
++cancel_writes;
break;
}

// Poll for read completion with timeout (max 1 second)
for (int wait = 0; wait < 100; ++wait)
{
if (read_done.load(std::memory_order_acquire))
break;
std::ignore = co_await corosio::delay(
std::chrono::milliseconds(10));
}

if (!read_done.load(std::memory_order_acquire))
{
std::fprintf(
stderr,
" cancel_close_stress: read hung on case %d, "
"iter %d\n",
i % 3, i);
BOOST_TEST(
read_done.load(std::memory_order_acquire));
// Force cancel
s2.cancel();
std::ignore = co_await corosio::delay(
std::chrono::milliseconds(100));
}

++iterations;
co_await iteration(i);
}
}
catch (const std::exception& e)
Expand Down
Loading