Skip to content

feat: prototype recvmmsg/sendmmsg batched datagram I/O#41

Open
Segfaultd wants to merge 5 commits into
masterfrom
feat/mmsg-batching
Open

feat: prototype recvmmsg/sendmmsg batched datagram I/O#41
Segfaultd wants to merge 5 commits into
masterfrom
feat/mmsg-batching

Conversation

@Segfaultd

@Segfaultd Segfaultd commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

Prototype of batched UDP datagram I/O (recvmmsg/sendmmsg) for MafiaNet, replacing the current one-syscall-per-datagram model on the hot paths. All new behaviour is behind default-off Linux build flags — default builds are completely unchanged.

Motivation: MafiaNet's workload is many small UDP datagrams at high packet rates, where the per-packet syscall tax dominates. recvmmsg/sendmmsg move a whole array of datagrams per syscall, cutting that overhead — the low-effort first step toward io_uring/RIO, slotting into the existing RNS2 seams with no threading-model change.

What's here

Portable, unit-tested core (MmsgBatch.h / MmsgBatch.cpp) — compiled and tested on every platform, including ones without the mmsg syscalls:

  • DriveBatchedSend — the sendmmsg partial-send resume state machine (short-count retry; error only propagated when nothing was sent, mirroring sendmmsg(2)).
  • SockaddrToSystemAddress — byte-order-correct sockaddrSystemAddress (network-order port + ntohs).
  • DispatchRecvBatch — fans a received batch out to the event handler, freeing zero-length reads and the unused tail so no buffer leaks.

Linux syscall wiring, behind MAFIANET_USE_RECVMMSG / MAFIANET_USE_SENDMMSG (both OFF by default):

  • RNS2_Berkley::RecvFromBatchedLoop — one recvmmsg(..., MSG_WAITFORONE) per burst instead of one recvfrom per packet. MSG_WAITFORONE is deliberate: it returns as soon as one datagram is ready, so low packet rates aren't delayed waiting to fill the array.
  • RNS2_Linux::SendBatch — coalesces datagrams via sendmmsg + DriveBatchedSend. A loop-local RNS2SendBatch in ReliabilityLayer's resend loop defers only the transmit — encryption, packet-loss simulation and metrics still run per datagram — and flushes at the loop's single exit, so no datagram is stranded (the receive-path SendACKs route stays scalar on purpose).

Testing

  • 11 hermetic unit tests for the portable core (Tests/Unit/MmsgBatchTests.cpp), written test-first. These run in CI.
  • Full unit suite: 59/59 pass.
  • Default build (flags off), static + shared: clean — Linux wiring confirmed inert on macOS.
  • Linux path with both flags on: -fsyntax-only clean on gcc 13 — the recvmmsg/sendmmsg code actually compiles.

Known limitations / follow-ups

  • The Linux syscall path is compile-verified only so far (the dev machine is macOS, which lacks these syscalls). It still needs a real Linux run of the integration suite with the flags on before it's more than a prototype.
  • Suggested follow-ups: a CI job building+testing the flags-on Linux config, and a loopback integration test for the batched paths.
  • Send batching only coalesces one peer's datagrams per tick; the larger win is recv batching (many peers → one socket).

Summary by CodeRabbit

  • New Features
    • Added batched UDP receive support on Linux using recvmmsg, with configurable build options.
    • Added batched UDP send support on Linux using sendmmsg, integrated into the reliability/datagram sending path.
    • Introduced a new batching helper to drive multi-datagram send/receive more efficiently.
  • Tests
    • Added unit tests for batched send/receive logic, address decoding, dispatching, and receive/send buffer cleanup.
  • Chores
    • Added a Linux CI job that builds and runs tests with batched I/O enabled.

Add batched UDP send/receive behind default-off Linux build flags
(MAFIANET_USE_RECVMMSG / MAFIANET_USE_SENDMMSG), plus a portable,
unit-tested core.

Portable core (MmsgBatch.h/.cpp), compiled and tested on every platform:
- DriveBatchedSend: sendmmsg partial-send resume state machine.
- SockaddrToSystemAddress: byte-order-correct sockaddr -> SystemAddress.
- DispatchRecvBatch: fan a received batch to the event handler, freeing
  zero-length reads and the unused tail.

Linux syscall wiring (guarded, inert by default):
- RNS2_Berkley::RecvFromBatchedLoop drains the socket with one
  recvmmsg(MSG_WAITFORONE) per burst instead of one recvfrom per packet.
- RNS2_Linux::SendBatch coalesces datagrams via sendmmsg; a loop-local
  RNS2SendBatch in ReliabilityLayer's resend loop defers only the
  transmit (encryption/simulation/metrics still run per datagram) and
  flushes at the loop's single exit.

Tests: 11 hermetic unit tests for the core (Tests/Unit/MmsgBatchTests.cpp).
The Linux path is compile-verified (gcc 13, both flags on); runtime
validation on Linux via the integration suite is still pending.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Segfaultd, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98aa3214-2fea-45c0-b338-3363ad0199e1

📥 Commits

Reviewing files that changed from the base of the PR and between 89fed16 and 392ea0a.

📒 Files selected for processing (8)
  • .github/workflows/build.yml
  • .gitignore
  • Source/include/mafianet/MmsgBatch.h
  • Source/include/mafianet/socket2.h
  • Source/src/MmsgBatch.cpp
  • Source/src/RakNetSocket2.cpp
  • Source/src/RakNetSocket2_Berkley.cpp
  • Tests/Unit/MmsgBatchTests.cpp

Walkthrough

Adds optional Linux recvmmsg receive and sendmmsg send batching, shared address and dispatch helpers, socket and reliability-layer integration, CMake options, CI configuration, and hermetic unit tests.

Changes

UDP batching

Layer / File(s) Summary
Batching contracts and build wiring
Source/CMakeLists.txt, Source/include/mafianet/MmsgBatch.h, Source/include/mafianet/ReliabilityLayer.h, Source/include/mafianet/socket2.h, .github/workflows/build.yml
Adds batching options, shared helpers, socket and reliability-layer APIs, and a Linux batching test job.
Batched UDP receive path
Source/src/MmsgBatch.cpp, Source/src/RakNetSocket2.cpp, Source/src/RakNetSocket2_Berkley.cpp
Adds address conversion, receive-slot dispatch and cleanup, and Linux recvmmsg integration.
Batched UDP send path
Source/src/ReliabilityLayer.cpp, Source/src/RakNetSocket2.cpp, Source/include/mafianet/MmsgBatch.h
Accumulates reliability datagrams, flushes them through SendBatch, and implements Linux sendmmsg support.
Batch behavior validation
Tests/Unit/MmsgBatchTests.cpp
Tests partial sends, address decoding, receive cleanup, datagram counts, buffering, flush boundaries, and oversized datagrams.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

Batched receive flow

sequenceDiagram
  participant RNS2_Berkley
  participant recvmmsg
  participant DispatchRecvBatch
  participant RNS2EventHandler
  RNS2_Berkley->>recvmmsg: receive datagram batch
  recvmmsg-->>RNS2_Berkley: lengths and source addresses
  RNS2_Berkley->>DispatchRecvBatch: dispatch received slots
  DispatchRecvBatch->>RNS2EventHandler: dispatch valid slots and deallocate others
Loading

Batched send flow

sequenceDiagram
  participant ReliabilityLayer
  participant RNS2SendBatch
  participant RNS2_Linux
  participant sendmmsg
  ReliabilityLayer->>RNS2SendBatch: add prepared datagram
  ReliabilityLayer->>RNS2SendBatch: flush update batch
  RNS2SendBatch->>RNS2_Linux: send datagram batch
  RNS2_Linux->>sendmmsg: transmit batch
  sendmmsg-->>RNS2_Linux: return sent count or error
Loading

Poem

A rabbit packs UDP with care,
Receive and send now share the air.
Slots are filled, then neatly freed,
Batches carry every need.
The Linux burrow hops along! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: Linux recvmmsg/sendmmsg batched datagram I/O.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mmsg-batching

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Source/CMakeLists.txt (1)

344-350: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the batched-UDP macros off the installed library consumers.

MAFIANET_USE_RECVMMSG and MAFIANET_USE_SENDMMSG are installed public options, but MAFIANET_USE_SENDMMSG controls the RNS2_Linux::SendBatch override and the RNS2SendBatch class body. Consumers linking against the installed target only get the macro definitions the library was initialized with when building with MAFIANET_USE_* OFF, while installed consumers can include and use RNS2_Linux::SendBatch or RNS2SendBatch only when the option was ON. Keep these flags internal, or expose them as target options and ensure exported consumers receive the matching config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/CMakeLists.txt` around lines 344 - 350, Keep MAFIANET_USE_RECVMMSG and
MAFIANET_USE_SENDMMSG private to the library build in
target_compile_definitions(${target_name}), and ensure the installed/exported
target does not propagate them to consumers. Update the public
RNS2_Linux::SendBatch and RNS2SendBatch configuration to remain consistent
without requiring installed consumers to define these macros, or explicitly
export matching option values if that API must remain conditional.
🧹 Nitpick comments (1)
Source/src/MmsgBatch.cpp (1)

13-32: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Unhandled address family leaves out with stale/uninitialized data.

When RAKNET_SUPPORT_IPV6 != 1 and from.ss_family != AF_INET, neither branch executes, so *out (the RNS2RecvStruct::systemAddress of a possibly-reused struct) is left with whatever it held before. DispatchRecvBatch doesn't gate dispatch on family validity, only on lens[i] > 0, so such a slot would still be handed to OnRNS2Recv with a stale source address. This mirrors the existing RecvFromBlockingIPV4And6 pattern by design, and a bound IPv4-only socket should never hand back a non-AF_INET address, so the practical exposure is low — but worth a defensive zero-init or assert given it silently misattributes a datagram's origin if it ever does trigger.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/src/MmsgBatch.cpp` around lines 13 - 32, Update
SockaddrToSystemAddress to defensively initialize or otherwise invalidate *out
before handling the address family, and explicitly handle unsupported families
so no stale source address or port remains when neither AF_INET nor IPv6
applies. Preserve the existing IPv4 and IPv6 byte-order handling for supported
families.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Source/include/mafianet/MmsgBatch.h`:
- Around line 116-125: Update RNS2SendBatch::Add to reject oversized datagrams
rather than clamping length to MAXIMUM_MTU_SIZE. Preserve the full payload
behavior of the non-batched send path by signaling failure before memcpy when
length exceeds the supported limit, and adjust the caller as needed to handle
that failure without incrementing count.

In `@Source/include/mafianet/socket2.h`:
- Around line 267-269: Update the conditional declaration of
RNS2Socket2::SendBatch in the socket2 header so it is available to consumers
whenever the implementation supports MAFIANET_USE_SENDMMSG, rather than relying
on a library-private compile definition. Align this visibility with the
corresponding CMakeLists.txt and MmsgBatch.h changes.

In `@Source/src/ReliabilityLayer.cpp`:
- Around line 2206-2210: Remove the per-call allocation in the UpdateInternal
send path by making the RNS2SendBatch storage reusable across ticks, such as
persistent member- or thread-owned storage initialized once and reused by
Add/Flush. Update the RNS2SendBatch construction near the send loop to use that
storage, while preserving the existing batch capacity and behavior and only
growing the buffer when necessary.

In `@Tests/Unit/MmsgBatchTests.cpp`:
- Line 21: Replace the direct arpa/inet.h include in MmsgBatchTests.cpp with
platform-conditional socket headers matching the library’s Windows handling: use
winsock2.h and ws2tcpip.h on MSVC/mingw, while retaining the appropriate POSIX
header elsewhere so htons and inet_pton remain available.

---

Outside diff comments:
In `@Source/CMakeLists.txt`:
- Around line 344-350: Keep MAFIANET_USE_RECVMMSG and MAFIANET_USE_SENDMMSG
private to the library build in target_compile_definitions(${target_name}), and
ensure the installed/exported target does not propagate them to consumers.
Update the public RNS2_Linux::SendBatch and RNS2SendBatch configuration to
remain consistent without requiring installed consumers to define these macros,
or explicitly export matching option values if that API must remain conditional.

---

Nitpick comments:
In `@Source/src/MmsgBatch.cpp`:
- Around line 13-32: Update SockaddrToSystemAddress to defensively initialize or
otherwise invalidate *out before handling the address family, and explicitly
handle unsupported families so no stale source address or port remains when
neither AF_INET nor IPv6 applies. Preserve the existing IPv4 and IPv6 byte-order
handling for supported families.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a84bf948-9853-4f37-8fef-812670c91381

📥 Commits

Reviewing files that changed from the base of the PR and between 13d344c and 0ae732f.

📒 Files selected for processing (9)
  • Source/CMakeLists.txt
  • Source/include/mafianet/MmsgBatch.h
  • Source/include/mafianet/ReliabilityLayer.h
  • Source/include/mafianet/socket2.h
  • Source/src/MmsgBatch.cpp
  • Source/src/RakNetSocket2.cpp
  • Source/src/RakNetSocket2_Berkley.cpp
  • Source/src/ReliabilityLayer.cpp
  • Tests/Unit/MmsgBatchTests.cpp

Comment thread Source/include/mafianet/MmsgBatch.h
Comment thread Source/include/mafianet/socket2.h
Comment thread Source/src/ReliabilityLayer.cpp
Comment thread Tests/Unit/MmsgBatchTests.cpp Outdated
Address CI + review feedback on the batched I/O prototype:

- Windows build: MmsgBatchTests.cpp used <arpa/inet.h>; guard the include
  (<ws2tcpip.h> on Windows) so the hermetic tests build cross-platform.
- RNS2SendBatch::Add rejected oversized datagrams instead of silently
  clamping to the MTU, which diverged from the scalar send path and could
  ship truncated payloads in release builds.
- RNS2SendBatch no longer heap-allocates ~95 KB per UpdateInternal call;
  it reuses a thread_local buffer (single update thread per RakPeer,
  flushed synchronously) so the send path stays allocation-free.
- MAFIANET_USE_RECVMMSG/SENDMMSG are now PUBLIC compile definitions so
  consumers see the same conditional declarations the library was built
  with (no header/ABI mismatch).
… in CI

Pre-landing review follow-ups on the batched datagram I/O prototype:

- SendBatch returned a byte total from the portable base implementation but a
  datagram count from the sendmmsg override. Both now return a datagram count
  (and the error only when nothing was sent) by routing the scalar loop through
  the same DriveBatchedSend state machine.
- SockaddrToSystemAddress silently left *out untouched for an address family
  the build cannot decode, so a recycled recv struct would attribute the
  datagram to its previous sender. It now returns bool, sets
  UNASSIGNED_SYSTEM_ADDRESS, and DispatchRecvBatch frees instead of dispatching.
- RNS2SendBatch is non-copyable and asserts against a second live batch on the
  same thread; its shared buffer block moved from TLS to a lazily allocated
  per-thread block, so threads that never batch pay nothing.
- The batched recv loop kept reallocating the whole slot batch on every
  recvmmsg failure. Slots are now held across failures with progressive
  back-off, so a persistent async error (ECONNREFUSED on a closed peer port)
  cannot spin the polling thread.
- RNS2SendBatch is compiled unconditionally (it only needs the portable
  SendBatch), with unit tests for payload copying, batch-boundary flush,
  destructor backstop, and the oversized-datagram drop.
- New linux-mmsg CI job builds and tests with both flags ON; the syscall paths
  and their reliability-layer wiring were previously never compiled anywhere.

Verified: 98/98 (unit + integration) on Linux with MAFIANET_USE_RECVMMSG and
MAFIANET_USE_SENDMMSG ON; 69/69 unit on macOS with both OFF.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Tests/Unit/MmsgBatchTests.cpp (1)

57-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use MafiaNet containers in test scaffolding.

These std::vector usages conflict with the repository container convention. Replace them with an appropriate DataStructures container.

As per coding guidelines, use “custom MafiaNet data structure containers (DS_List, DS_Queue, DS_Map, DS_OrderedList, DS_MemoryPool) for consistency.”

Also applies to: 97-98, 309-312, 451-451

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Unit/MmsgBatchTests.cpp` around lines 57 - 58, Replace the std::vector
declarations in the MmsgBatchTests test scaffolding, including the referenced
locations, with the appropriate DataStructures container types from the
repository convention. Preserve each container’s required ordering and access
behavior, and update any dependent operations or iteration as needed to compile
with the selected MafiaNet containers.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Tests/Unit/MmsgBatchTests.cpp`:
- Around line 57-58: Replace the std::vector declarations in the MmsgBatchTests
test scaffolding, including the referenced locations, with the appropriate
DataStructures container types from the repository convention. Preserve each
container’s required ordering and access behavior, and update any dependent
operations or iteration as needed to compile with the selected MafiaNet
containers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c6c3ed44-6c07-4210-b8b1-ca5eae4d3eab

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae732f and 89fed16.

📒 Files selected for processing (9)
  • .github/workflows/build.yml
  • Source/CMakeLists.txt
  • Source/include/mafianet/MmsgBatch.h
  • Source/include/mafianet/ReliabilityLayer.h
  • Source/include/mafianet/socket2.h
  • Source/src/MmsgBatch.cpp
  • Source/src/RakNetSocket2.cpp
  • Source/src/RakNetSocket2_Berkley.cpp
  • Tests/Unit/MmsgBatchTests.cpp
🚧 Files skipped from review as they are similar to previous changes (4)
  • Source/include/mafianet/socket2.h
  • Source/CMakeLists.txt
  • Source/include/mafianet/ReliabilityLayer.h
  • Source/src/RakNetSocket2.cpp

Pre-landing review follow-ups on the batched datagram I/O work.

- RecvFromBatchedLoop refilled all MMSG_BATCH_MAX slots every pass and
  handed the whole array to DispatchRecvBatch, so a pass that received a
  single datagram still cost 64 allocs plus 63 frees -- each taking the
  event handler's pool mutex. Only the slots that actually received a
  datagram now change hands; the untouched tail carries over, putting the
  steady state at one alloc/free round trip per datagram, the same as the
  scalar path.

- RNS2_Linux::SendBatch silently ignored RNS2_SendParameters::ttl, which
  the scalar Send() honours by bracketing the sendto with
  setsockopt(IP_TTL). sendmmsg has no per-message TTL, so a batch carrying
  one is now deferred to the portable base loop. Nothing sets ttl today
  (RNS2SendBatch always flushes with zero), so this closes a trap rather
  than a live bug.

- RNS2SendBatch::Flush discarded SendBatch's result: a failed or partial
  sendmmsg dropped datagrams with no trace, where the scalar path at least
  reports its sendto failures. Both that shortfall and a dropped oversized
  datagram now emit RAKNET_DEBUG_PRINTF.

Also drops the dead lens[] writes past the received count, corrects the
comment claiming shutdown arrives on the recvmmsg error branch (the poke is
a real datagram, so it surfaces as a normal pass), and switches to
_FILE_AND_LINE_ to match the rest of the codebase.

Tests: cover the DispatchRecvBatch clamp, the IPv6 decode branch of
SockaddrToSystemAddress (and its IPv4-only-build rejection), TTL
pass-through in the base SendBatch, and that RNS2SendBatch flushes with a
zero TTL so the sendmmsg path is never deferred.

Verified with the full suite on Linux with both flags on (102/102).
DriveBatchedSend treated any negative transmit() return as "stop", so a
single undeliverable datagram silently discarded every datagram queued
behind it -- the scalar Send() loop it replaced would have delivered
those. Split the contract: 0 now means a transient socket-wide condition
(stop, don't spin), negative means the message at that offset is itself
undeliverable (drop it and continue). RNS2_Linux::SendBatch classifies
errno accordingly, since sendmmsg funnels both through the same -1.

Also:
- Assert in RNS2SendBatch::Flush that SendBatch returned a datagram count
  rather than a byte total. That is the one contract the sendmmsg
  override cannot be unit-tested against, so check it at runtime.
- Build the linux-mmsg CI job in Debug. It is the only job that compiles
  these paths, and RakAssert -- which guards the return contract, the
  RNS2SendBatch reentrancy invariant and the IPv4-only address branch --
  is compiled out unless _DEBUG.
- Make the IPv4-only branch of the sendmmsg address setup explicit
  instead of silently leaving msg_name null.
- Cover the previously untested failure paths: a partially failing flush,
  a fully failing flush not resending its batch, per-message skip, and
  first-error propagation.
- Ignore build-*/ and docs/superpowers/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant