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
53 changes: 44 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ on:
branches: [main]
pull_request:

# A new push to the same ref supersedes the running build. main keeps every
# run (github.run_id) so merged pushes always populate the bazel caches.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && github.run_id || github.ref }}
cancel-in-progress: true

permissions:
contents: read

Expand All @@ -29,7 +35,12 @@ jobs:
os: windows-2022
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
disk-cache: ${{ matrix.name }}
- name: bazel test
env:
CC: ${{ matrix.cc }}
Expand All @@ -40,7 +51,12 @@ jobs:
name: bazel (asan + ubsan)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
disk-cache: asan-ubsan
- name: bazel test with sanitizers
env:
CC: clang
Expand All @@ -51,7 +67,12 @@ jobs:
name: fuzz (libFuzzer smoke)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
disk-cache: fuzz
# Each harness runs a short bounded libFuzzer session (a "smoke" run, not
# a soak) so regressions in the parsers surface on every PR. The
# deterministic-driver variants also run as ordinary tests in the bazel
Expand Down Expand Up @@ -80,7 +101,12 @@ jobs:
os: [ubuntu-24.04, macos-14, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
disk-cache: consumer
# The quick-start acceptance test (docs/quickstart.md): a standalone
# out-of-tree Bazel module consumes smithy_cpp via the rules in
# bazel/defs.bzl — generation runs inside the consumer's build graph,
Expand All @@ -93,7 +119,7 @@ jobs:
name: codegen (gradle)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: actions/setup-java@v4
with:
distribution: temurin
Expand All @@ -118,7 +144,12 @@ jobs:
runs-on: ubuntu-24.04
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-cache: true
repository-cache: true
disk-cache: benchmarks-opt
- name: serde benchmarks
run: bazel run -c opt //benchmarks:serde_benchmark -- --benchmark_min_time=0.2s
- name: request benchmarks
Expand All @@ -130,10 +161,14 @@ jobs:
name: lint
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# Boost headers so clang-tidy can process the Beast transport.
- uses: actions/checkout@v7
# Boost headers so clang-tidy can process the Beast transport. libboost-dev
# comes from the Ubuntu archive; tolerate update failures from the runner
# image's unrelated third-party repos (packages.microsoft.com flakes).
- name: install boost headers
run: sudo apt-get update -q && sudo apt-get install -y -q libboost-dev
run: |
sudo apt-get update -q || true
sudo apt-get install -y -q libboost-dev
# Generated code (examples/*/generated) is exempt from format checks;
# its shape is locked by the golden diff check in the codegen job.
- name: clang-format
Expand Down
15 changes: 14 additions & 1 deletion runtime/src/http/socket_transport.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,25 @@ void SetTimeouts(SocketFd fd, int timeout_ms) {
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &value, sizeof(value));
#endif
#ifdef SO_NOSIGPIPE
// macOS/BSD have no MSG_NOSIGNAL; a peer that closes mid-send must surface
// as an EPIPE write error, never a process-killing SIGPIPE.
const int no_sigpipe = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe));
#endif
}

bool SendAll(SocketFd fd, std::string_view data) {
#ifdef MSG_NOSIGNAL
// Linux: writes to a peer-closed socket fail with EPIPE instead of raising
// SIGPIPE (whose default action would kill the process).
constexpr int kSendFlags = MSG_NOSIGNAL;
#else
constexpr int kSendFlags = 0; // Windows has no SIGPIPE; macOS uses SO_NOSIGPIPE.
#endif
while (!data.empty()) {
const int chunk = static_cast<int>(data.size() > 65536 ? 65536 : data.size());
const auto sent = send(fd, data.data(), chunk, 0);
const auto sent = send(fd, data.data(), chunk, kSendFlags);
if (sent <= 0) return false;
data.remove_prefix(static_cast<std::size_t>(sent));
}
Expand Down
4 changes: 3 additions & 1 deletion runtime/tests/core/timestamp_differential_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ TEST(TimestampDifferentialTest, DateTimeMatchesGmtimeOverRandomEpochs) {
if (!GmTime(seconds, &reference)) {
continue; // libc range limits are not what we're testing
}
char expected[32];
// Sized for gcc's -Wformat-truncation worst case (full int widths), not
// the real bounded output (the epoch range keeps years at four digits).
char expected[80];
std::snprintf(expected, sizeof(expected), "%04d-%02d-%02dT%02d:%02d:%02dZ",
reference.tm_year + 1900, reference.tm_mon + 1, reference.tm_mday,
reference.tm_hour, reference.tm_min, reference.tm_sec);
Expand Down
22 changes: 22 additions & 0 deletions runtime/tests/http/socket_transport_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ TEST(SocketTransportTest, HandlesSequentialRequestsAndLargeBodies) {
server.Stop();
}

TEST(SocketTransportTest, PeerCloseMidSendIsAnErrorNotSigpipe) {
SocketHttpServer server;
ASSERT_TRUE(server.Start([](const HttpRequest&) { return HttpResponse{200, {}, "ok"}; }).ok());
SocketHttpClient client("127.0.0.1", server.port());

// Over the server's 64 MiB body cap: it rejects on content-length and
// closes while the client is still writing. That must surface as a
// transport error (or an HTTP error status), never a SIGPIPE that kills
// the process.
HttpRequest request;
request.method = "POST";
request.target = "/";
request.body = std::string((std::size_t{64} << 20) + 1024, 'x');
const auto response = client.Send(request);
if (response.ok()) {
EXPECT_GE(response->status, 400);
} else {
EXPECT_EQ(response.error().kind(), ErrorKind::kTransport);
}
server.Stop();
}

TEST(SocketTransportTest, ReportsConnectionFailure) {
SocketHttpServer throwaway;
ASSERT_TRUE(throwaway.Start([](const HttpRequest&) { return HttpResponse{}; }).ok());
Expand Down
Loading