Skip to content

yamux fixes#1269

Open
asabya wants to merge 22 commits intolibp2p:mainfrom
asabya:main
Open

yamux fixes#1269
asabya wants to merge 22 commits intolibp2p:mainfrom
asabya:main

Conversation

@asabya
Copy link
Copy Markdown
Contributor

@asabya asabya commented Mar 13, 2026

What was wrong?

yamux window update and auto stream size compatibility with go-yamux

  • SYN/ACK/FIN/RST were sent as TYPE_DATA
  • frames were interleaving on secured_conn
  • fixed window size of 256KB created a bottleneck

Issue #

How was it fixed?

  • Send SYN/ACK/FIN/RST as TYPE_WINDOW_UPDATE, not TYPE_DATA
  • Interpret length as window delta for TYPE_WINDOW_UPDATE frames
  • Serialize writes with _write_lock to prevent frame interleaving
  • The receive window starts at 256KB and doubles each RTT epoch up to 16MB, with 50% hysteresis to avoid flooding small updates.

Cute Animal Picture

image

asabya added 7 commits March 7, 2026 22:15
- Send SYN/ACK/FIN/RST as TYPE_WINDOW_UPDATE, not TYPE_DATA
- Interpret length as window delta for TYPE_WINDOW_UPDATE frames
- Serialize writes with _write_lock to prevent frame interleaving

Ref: github.com/libp2p/go-yamux/v5 (stream.go, session.go, const.go)
fix(yamux): match go-yamux frame types and add write serialization
Port go-yamux's window auto-tuning to py-libp2p. The receive window
starts at 256KB and doubles each RTT epoch up to 16MB, with 50%
hysteresis to avoid flooding small updates. Includes background RTT
measurement via ping/pong with exponential smoothing.

This eliminates ~4,000 round-trips per 1GB transfer (down to ~64),
significantly improving throughput for perf tests.
feat: add yamux receive window auto-tuning
@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Mar 22, 2026

Thanks @asabya . Please read the issues, and elaborate on them.
Before merging we should try this in test-plans interop transport, perf

AI PR Review: #1269 — yamux fixes

Review generated using downloads/py-libp2p-pr-review-prompt.md. Local branch: asabya/main (from gh pr checkout 1269).


1. Summary of Changes

This PR addresses yamux interoperability with go-yamux and throughput on multiplexed connections:

  • Control frames: SYN, ACK, FIN, and RST are sent as TYPE_WINDOW_UPDATE (not TYPE_DATA); SYN/ACK carry DEFAULT_WINDOW_SIZE in the length field where appropriate. Incoming TYPE_WINDOW_UPDATE SYN/ACK paths interpret length as initial send window or delta, matching go-yamux-style framing.
  • Write serialization: A trio.Lock (_write_lock) guards muxed writes via _write_frame(), so frames no longer interleave on secured_conn from concurrent stream operations.
  • Receive window auto-tuning: Ports go-yamux-style behavior: MAX_WINDOW_SIZE (16 MiB), target_recv_window, hysteresis, and RTT measurement via a background PING loop with exponential smoothing on PONG.
  • Structured concurrency: Yamux.start() runs handle_incoming in a nursery together with _measure_rtt_loop; when handle_incoming exits, the nursery is cancelled (same pattern as before for lifecycle).
  • Repo hygiene: Removes orphaned submodule entries under extra/ (multihash-spec, py-multihash, pymultihash).

Related issues (not “Fixes” in the PR body, but linked in the ecosystem):

Issue Role
#1271 — bug: yamux sends SYN/ACK/FIN/RST as TYPE_DATA… Describes incorrect frame types and interleaving; author comments that #1269 resolves it.
#1270 — feat: yamux receive window auto-tuning Tracks auto-tuning feature and points to this PR.

Affected modules: libp2p/stream_muxer/yamux/yamux.py (core), tests under tests/core/stream_muxer/.

Breaking changes: None intended; behavior change is wire-compatible with correct yamux peers and fixes interop with Go.


2. Branch Sync Status and Merge Conflicts

Branch sync status

From downloads/AI-PR-REVIEWS/1269/branch_sync_status.txt (git rev-list --left-right --count origin/main...HEAD):

  • Output: 0 10
  • Meaning: HEAD is 0 commits behind origin/main and 10 commits ahead (PR branch contains 10 commits not in origin/main).

Merge conflict analysis

  • Conflicts: None — test merge origin/main into HEAD completed successfully (merge_conflict_check.log reports “NO MERGE CONFLICTS DETECTED”).
  • Merge readiness: No conflict resolution required for mergeability; remaining blockers are process/items below (newsfragments, review findings), not git conflicts.

3. Strengths

  • Correct problem framing: Aligning frame types and window semantics with go-yamux is the right fix for real-world interop (TCP + Noise + yamux).
  • Centralized writes: _write_frame() is the right abstraction for serializing mux output; data path and control path mostly share it.
  • Tests and CI (local): make lint, make typecheck, make test, and make linux-docs all succeeded on the checked-out branch (see §8).
  • Tests touched: test_yamux.py and test_yamux_window_update_error_handling.py updated for behavior and error handling.

4. Issues Found

Critical

None confirmed with a failing test locally. Two areas deserve maintainer scrutiny before treating this as production-ready:

  • _auto_tune_and_send_window_update and bytes_consumed: The parameter bytes_consumed is never used. The method gates sending on delta = target_recv_window - self.recv_window and hysteresis (delta < target_recv_window // 2 → early return). On early return, no send_window_update runs and recv_window is not adjusted for the bytes just consumed from the application buffer. For small reads (or until recv_window drops far enough), this may delay or omit window credits versus the previous per-chunk recv_window += len(chunk) + send_window_update(len(chunk)) behavior. Recommend cross-check against go-yamux GrowTo / window accounting and add a focused test (e.g. many small reads after filling the window) if not already covered.

libp2p/stream_muxer/yamux/yamux.py, lines 244–274 — bytes_consumed is unused; early return skips any window update for that read:

    async def _auto_tune_and_send_window_update(
        self: "YamuxStream", bytes_consumed: int
    ) -> None:
        """
        Auto-tune receive window size based on RTT and send window update.

        Ports go-yamux's auto-tuning: starts at 256KB, doubles each RTT epoch
        up to 16MB. Only sends update when delta >= 50% of target (hysteresis).
        """
        async with self.window_lock:
            delta = self.target_recv_window - self.recv_window
            # Hysteresis: skip if delta < 50% of target (matches go-yamux GrowTo)
            if delta < self.target_recv_window // 2:
                return

            # Auto-tune: if within 4x RTT of last epoch, double the target
            now = trio.current_time()
            rtt = self.conn.rtt()
            if rtt > 0 and self.epoch_start > 0 and (now - self.epoch_start) < rtt * 4:
                self.target_recv_window = min(
                    self.target_recv_window * 2, MAX_WINDOW_SIZE
                )
                delta = self.target_recv_window - self.recv_window

            self.epoch_start = now
            self.recv_window += delta
            logger.debug(
                f"Stream {self.stream_id}: Auto-tune window update "
                f"delta={delta}, target={self.target_recv_window}"
            )
            await self.send_window_update(delta, skip_lock=True)

libp2p/stream_muxer/yamux/yamux.py, lines 324–331 — callers pass len(chunk) / len(data) into that unused parameter:

                if len(buffer) > 0:
                    chunk = bytes(buffer)
                    buffer.clear()
                    data += chunk

                    # Auto-tune and send window update for the chunk we just read
                    await self._auto_tune_and_send_window_update(len(chunk))

Major

  • File: libp2p/stream_muxer/yamux/yamux.py
    Line(s): 504–516 (_measure_rtt_loop)
    Issue: RTT PING frames call await self.secured_conn.write(header) outside _write_frame() / _write_lock. All other mux frames go through _write_frame(). That reintroduces possible interleaving of PING with DATA/WINDOW_UPDATE/GO_AWAY on the wire—the same class of bug the PR describes fixing for secured_conn.
    Suggestion: Send PING via await self._write_frame(header) (or acquire _write_lock around the same bytes) so every yamux frame is serialized.

libp2p/stream_muxer/yamux/yamux.py, lines 504–516 — PING bypasses _write_frame:

    async def _measure_rtt_loop(self) -> None:
        """Background task that periodically measures RTT via ping/pong."""
        # Initial delay to let the connection establish
        await trio.sleep(0.5)
        while not self.event_shutting_down.is_set():
            try:
                self._ping_id += 1
                self._ping_event = trio.Event()
                self._ping_sent_time = trio.current_time()
                header = struct.pack(
                    YAMUX_HEADER_FORMAT, 0, TYPE_PING, FLAG_SYN, 0, self._ping_id
                )
                await self.secured_conn.write(header)

libp2p/stream_muxer/yamux/yamux.py, lines 624–648 — contrast: all other mux output holds _write_lock before secured_conn.write:

    async def _write_frame(self, data: bytes) -> None:
        """Write a frame to the connection, serializing all writes."""
        if len(data) >= HEADER_SIZE:
            _, typ, flags, sid, length = struct.unpack(
                YAMUX_HEADER_FORMAT, data[:HEADER_SIZE]
            )
            flag_names = []
            if flags & FLAG_SYN:
                flag_names.append("SYN")
            if flags & FLAG_ACK:
                flag_names.append("ACK")
            if flags & FLAG_FIN:
                flag_names.append("FIN")
            if flags & FLAG_RST:
                flag_names.append("RST")
            type_names = {0: "DATA", 1: "WINDOW_UPDATE", 2: "PING", 3: "GO_AWAY"}
            logger.info(
                f"YAMUX TX: type={type_names.get(typ, typ)} "
                f"flags={'+'.join(flag_names) or '0'} "
                f"stream={sid} length={length} "
                f"is_initiator={self.is_initiator_value} "
                f"payload_bytes={len(data) - HEADER_SIZE}"
            )
        async with self._write_lock:
            await self.secured_conn.write(data)

Example expected layout (not present on the branch at review time):

newsfragments/1271.bugfix.rst
newsfragments/1270.feature.rst

Minor

  • File: libp2p/stream_muxer/yamux/yamux.py
    Line(s): _write_frame (640–646), handle_incoming RX (884–889)
    Issue: Every transmitted and received frame is logged at logger.info, which can be extremely noisy on busy connections and may impact performance.
    Suggestion: Use logger.debug for per-frame traces, or gate behind an env flag / lazy formatting policy consistent with the rest of the codebase.

libp2p/stream_muxer/yamux/yamux.py, lines 640–646 — TX path:

            logger.info(
                f"YAMUX TX: type={type_names.get(typ, typ)} "
                f"flags={'+'.join(flag_names) or '0'} "
                f"stream={sid} length={length} "
                f"is_initiator={self.is_initiator_value} "
                f"payload_bytes={len(data) - HEADER_SIZE}"
            )

libp2p/stream_muxer/yamux/yamux.py, lines 884–889 — RX path:

                logger.info(
                    f"YAMUX RX: type={type_names.get(typ, typ)} "
                    f"flags={'+'.join(flag_names) or '0'} "
                    f"stream={stream_id} length={length} "
                    f"is_initiator={self.is_initiator_value}"
                )
  • File: libp2p/stream_muxer/yamux/yamux.py
    Line(s): YamuxStream.close() / YamuxStream.reset() (FIN/RST paths, 379–425)
    Issue: except clauses were narrowed from (RawConnError, ConnectionClosedError) to RawConnError only for FIN/RST sends. If some transports raise ConnectionClosedError on half-closed writes, those may now propagate. Tests passed locally; worth confirming against WebSocket/TLS paths.
    Suggestion: Restore ConnectionClosedError in the tuple if that was intentional for graceful teardown.

libp2p/stream_muxer/yamux/yamux.py, lines 379–425:

    async def close(self) -> None:
        async with self.close_lock:
            if not self.send_closed:
                logger.debug(f"Half-closing stream {self.stream_id} (local end)")
                try:
                    header = struct.pack(
                        YAMUX_HEADER_FORMAT,
                        0,
                        TYPE_WINDOW_UPDATE,
                        FLAG_FIN,
                        self.stream_id,
                        0,
                    )
                    await self.conn._write_frame(header)
                except RawConnError as e:
                    logger.debug(f"Error sending FIN, connection likely closed: {e}")
                finally:
                    self.send_closed = True

            # Only set fully closed if both directions are closed
            if self.send_closed and self.recv_closed:
                self.closed = True
            else:
                # Stream is half-closed but not fully closed
                self.closed = False

    async def reset(self) -> None:
        if not self.closed:
            async with self.close_lock:
                logger.debug(f"Resetting stream {self.stream_id}")
                try:
                    header = struct.pack(
                        YAMUX_HEADER_FORMAT,
                        0,
                        TYPE_WINDOW_UPDATE,
                        FLAG_RST,
                        self.stream_id,
                        0,
                    )
                    await self.conn._write_frame(header)
                except RawConnError as e:
                    logger.debug(f"Error sending RST, connection likely closed: {e}")
                finally:
                    self.closed = True
                    self.send_closed = True
                    self.recv_closed = True
                    self.reset_received = True  # Mark as reset

5. Security Review

  • Input validation: Changes are internal framing and flow control; no new untrusted surface beyond existing yamux parsing. handle_incoming still drives reads from secured_conn.
  • PING loop: Uses monotonically increasing _ping_id; PONG handling should remain tied to matching IDs (verify if spoofed PONG could skew RTT—impact is performance tuning only, not auth).
  • Logging: Verbose INFO logs could leak traffic patterns in shared logs; prefer DEBUG for high-volume paths.

Overall security impact: Low (no new crypto or identity bypass identified).


6. Documentation and Examples

  • Docstrings: _auto_tune_and_send_window_update and _measure_rtt_loop have short explanations; acceptable.
  • User-facing docs: No README/tutorial updates required unless the project documents yamux tuning elsewhere.
  • PR / issues: Completing the “Issue #” line and newsfragments is the main documentation gap for release notes.

7. Newsfragment Requirement

Check Status
PR references issues Partial: #1270 and #1271 exist and describe the work; PR body does not use Fixes #….
newsfragments/<ISSUE>.<type>.rst Missing for #1270 and #1271 on this branch.

Severity: BLOCKER for merge under stated py-libp2p release/process rules: add at least:

  • 1271.bugfix.rst — control frames / write serialization / go-yamux interop (user-visible bugfix).
  • 1270.feature.rst — receive window auto-tuning (if that issue is closed by this PR).

Content should be user-impact focused (throughput, interop with Go peers), one line or short paragraph each, ending with newline.


8. Tests and Validation

Commands run: source venv/bin/activate && make lint|typecheck|test|linux-docs with logs under downloads/AI-PR-REVIEWS/1269/.

Lint (lint_output.log)

  • Result: Success (exit 0).
  • Hooks: yaml/toml, eof, whitespace, pyupgrade, ruff, ruff format, mdformat, mypy, pyrefly, .rst top-level check, path audit — all Passed.

Typecheck (typecheck_output.log)

  • Result: Success (exit 0).
  • mypy / pyrefly: Passed.

Tests (test_output.log)

  • Summary: 2562 passed, 15 skipped, 24 warnings in ~110s (parallel workers).
  • Exit: 0.
  • Warnings: PytestCollectionWarning in tests/core/records/test_ipns_validator.py:489 (TestVector / __init__) — pre-existing, not introduced by this PR.

Docs (docs_output.log)

  • Result: sphinx-build completed; exit 0.
  • Note: make linux-docs may open a browser (xdg-open); build itself succeeded.

9. Recommendations for Improvement

  1. Route PING through _write_frame() to honor single-writer semantics for all yamux frames.
  2. Add newsfragments for bug: yamux sends SYN/ACK/FIN/RST as TYPE_DATA instead of TYPE_WINDOW_UPDATE #1271 and feat: yamux receive window auto-tuning #1270 and update the PR description with proper Fixes lines.
  3. Reconcile _auto_tune_and_send_window_update with go-yamux accounting; either use bytes_consumed explicitly or remove the parameter and document invariants; add tests for small incremental reads under window pressure if missing.
  4. Demote per-frame INFO logs to DEBUG (or sample).
  5. Optionally restore ConnectionClosedError on FIN/RST paths if regressions appear on specific transports.

10. Questions for the Author

  1. Was ConnectionClosedError dropped from FIN/RST handlers deliberately after observing double-handling, or should it remain for transports that map closure to that type?
  2. Can you confirm go-yamux parity for _auto_tune_and_send_window_update (especially the early return path and unused bytes_consumed)?
  3. Should both feat: yamux receive window auto-tuning #1270 and bug: yamux sends SYN/ACK/FIN/RST as TYPE_DATA instead of TYPE_WINDOW_UPDATE #1271 be closed by this PR, or will auto-tuning land under a follow-up PR?

11. Overall Assessment

Dimension Rating
Quality Good — direction and tests are strong; a few design details need tightening.
Security impact None to Low
Merge readiness Needs fixes — newsfragments + issue linkage are process blockers; PING/write-lock and auto-tune accounting should be addressed or explicitly signed off by maintainers.
Confidence Medium — full suite green locally; remaining concerns are from code review (serialization gap, flow-control accounting).

12. Spec and cross-implementation comparison

Sources used for this section (on the reviewer’s machine):

Source Path (reviewer workspace)
libp2p yamux spec (copy of Hashicorp framing rules) /home/luca/PNL_Launchpad_Curriculum/Libp2p/specs/yamux/README.md
go-libp2p muxer adapter /home/luca/PNL_Launchpad_Curriculum/Libp2p/go-libp2p/p2p/muxer/yamux/ — thin wrapper over github.com/libp2p/go-yamux/v5 (go.mod lists v5.0.1). Framing implementation: Go module cache, e.g. ~/go/pkg/mod/github.com/libp2p/go-yamux/v5@v5.0.0/ (exact patch version may differ after go mod download).
js-libp2p @chainsafe/libp2p-yamux (e.g. js-libp2p/packages/integration-tests/package.json); sources: /home/luca/PNL_Launchpad_Curriculum/Libp2p/js-libp2p/node_modules/@chainsafe/libp2p-yamux/src/.
py-libp2p (this PR) libp2p/stream_muxer/yamux/yamux.py (within this repo)

Upstream canonical framing text is also linked from the spec: Hashicorp yamux spec.md.

12.1 What the libp2p yamux spec says

From specs/yamux/README.md (mirrored spec):

  • Types: 0x0 Data, 0x1 Window update, 0x2 Ping, 0x3 Go away.
  • Flags: SYN / ACK / FIN / RST may be sent with a data or window update message (not exclusively with Data).
  • Length field: For Window update, length is a delta on the window. For Data, length is the payload size after the header.
  • Opening streams: First frame may be data or window update with SYN; peer replies with data or window update with ACK or RST.
  • Closing: Half-close uses data or window update with FIN; RST may accompany data or window update.
  • Flow control: Default 256 KiB per stream; either side may advertise a larger window via window updates (including as part of SYN/ACK).
  • Accounting: “Both sides should track the number of bytes sent in Data frames only” for window consumption.

Implication for the old py behavior: Sending SYN/ACK/FIN/RST as TYPE_DATA was not aligned with the spec’s “data or window update” wording for those control transitions, and it disagrees with how go-yamux and @chainsafe/libp2p-yamux encode those transitions (see below). The PR’s move to TYPE_WINDOW_UPDATE for those frames is spec-aligned.

12.2 go-libp2p / go-yamux v5

go-libp2p does not reimplement yamux framing; it wraps go-yamux/v5 (stream.go / conn.go only adapt errors and interfaces).

In go-yamux v5 (stream.go in the module cache):

  • Window updates and stream opens use typeWindowUpdate with flags from sendFlags() (SYN/ACK), and sendMsgsendCh → dedicated send() loop serializes all writes (including Ping and GoAway) on one path—no concurrent interleaving on net.Conn.Write.
// sendWindowUpdate — FIN/RST/control window path
hdr := encode(typeWindowUpdate, flags, s.id, delta)
return s.session.sendMsg(hdr, nil, deadline)

// sendClose — FIN
hdr := encode(typeWindowUpdate, flags|flagFIN, s.id, 0)
return s.session.sendMsg(hdr, nil, nil)

// sendReset — RST
hdr := encode(typeWindowUpdate, flagRST, s.id, errCode)
return s.session.sendMsg(hdr, nil, nil)

Auto-tuning uses recvBuf.GrowTo, RTT from the session, and now.Sub(epochStart) < rtt*4 to double the receive window up to MaxStreamWindowSize—the same family of behavior the PR ports to Python.

Compare to py PR: Matching TYPE_WINDOW_UPDATE + SYN/ACK/FIN/RST and adding _write_lock brings py closer to go-yamux’s single send path. The remaining gap is PING still calling secured_conn.write directly (§4), which go-yamux does not do.

12.3 js-libp2p / @chainsafe/libp2p-yamux

FrameType and Flag match the spec (frame.ts). Control-plane sends use WindowUpdate:

  • Open stream: newStream() ends with stream.sendWindowUpdate() — first frame is WindowUpdate with SYN (via getSendFlags()), length = delta (initial grant), not a Data frame.
  • FIN / RST: sendCloseWrite and sendReset use FrameType.WindowUpdate with FIN / RST and length 0 (same pattern as py PR).

muxer.ts — all outbound frames (including Ping) go through sendFrame, which pushes encoded headers (and data) onto the muxer’s source queue—effectively one ordered outbound pipeline consumed by the connection sink:

  private sendFrame (header: FrameHeader, data?: Uint8ArrayList): void {
    this.log?.trace('sending frame %o', header)
    if (header.type === FrameType.Data) {
      if (data === undefined) {
        throw new InvalidFrameError('Invalid frame')
      }
      this.source.push(
        new Uint8ArrayList(encodeHeader(header), data)
      )
    } else {
      this.source.push(encodeHeader(header))
    }
  }

stream.tssendWindowUpdate implements RTT-based doubling (rtt * 4, epochStart) and computes delta = recvWindow - recvWindowCapacity before emitting a WindowUpdate (different shape than py’s target_recv_window - recv_window + //2 hysteresis, but same high-level goal: batch updates and grow windows).

Compare to py PR: Frame types for open/close/reset match Chainsafe’s approach. Serialization: JS funnels Ping through sendFrame; py should do the equivalent via _write_frame. Auto-tune: worth line-by-line parity with sendWindowUpdate + go-yamux GrowTo, not only with PR comments.

12.4 Summary table (PR #1269 vs spec vs Go vs JS)

Topic libp2p spec (specs/yamux) go-yamux v5 (go-libp2p) @chainsafe/libp2p-yamux (js-libp2p) py-libp2p after #1269
SYN / ACK / FIN / RST frame type May use Data or Window update Window update (+ flags) Window update (+ flags) Window update (+ flags)
Window length on update Delta Delta (GrowTo / encode) delta in sendWindowUpdate Delta in send_window_update; SYN/ACK carry DEFAULT_WINDOW_SIZE where intended
Ping type Ping, stream ID 0 typePing via sendMsg FrameType.Ping via sendFrame TYPE_PING — but write path bypasses _write_lock (issue in §4)
Serialize all mux writes (implied: correct framing order) Single send() loop Single source.push pipeline _write_lock + _write_frame for most paths
Initial / max window 256 KiB default, configurable initialStreamWindow / MaxStreamWindowSize INITIAL_STREAM_WINDOW / MAX_STREAM_WINDOW DEFAULT_WINDOW_SIZE / MAX_WINDOW_SIZE
Auto-tune / RTT (not specified in detail) GrowTo, rtt*4, epoch sendWindowUpdate, rtt*4, epoch _auto_tune_and_send_window_update, _measure_rtt_loop — verify against §4

asabya added 3 commits March 23, 2026 14:59
- Send SYN/ACK with length=0 (matches go-yamux GrowTo delta behavior),
  fixing inflated send_window when go peer ADDs ACK length to initial 256K
- Change SYN/ACK handlers from SET to ADD for send_window (matches
  go-yamux incrSendWindow)
- Route PING through _write_frame to honor single-writer serialization
- Restore ConnectionClosedError in FIN/RST exception handlers
- Demote per-frame TX/RX logging from INFO to DEBUG
- Rewrite _auto_tune_and_send_window_update with two-pass GrowTo logic
  matching go-yamux, remove unused bytes_consumed parameter
- Add recv_window overflow guards (clamp to 0 with warning)
- Add newsfragments for libp2p#1270 (feature) and libp2p#1271 (bugfix)
The two-pass GrowTo logic was overwriting pass 1 delta with pass 2
delta, causing the peer to receive only the incremental (pass 2)
window update. This left the peer's send_window consistently short,
degrading throughput and causing perf test timeouts during download.

Change delta = extra_delta to delta += extra_delta so the peer
receives the full window growth (pass 1 + pass 2).
@asabya
Copy link
Copy Markdown
Contributor Author

asabya commented Mar 23, 2026

I have added fixes for the review. here is the perf PR libp2p/unified-testing#62

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Mar 25, 2026

I have added fixes for the review. here is the perf PR libp2p/unified-testing#62

    - python-v0.x x python-v0.x (tcp, noise, yamux)
    - python-v0.x x python-v0.x (tcp, noise, mplex)
    - python-v0.x x python-v0.x (ws, noise, yamux)
    - python-v0.x x python-v0.x (ws, noise, mplex)
    - python-v0.x x python-v0.x (ws, tls, yamux)
    - python-v0.x x python-v0.x (ws, tls, mplex)
  → Total time: 00:20:196 test(s) failed

asabya added 2 commits March 25, 2026 22:57
The yamux window auto-tuning (256KB → 16MB) caused two failures:

1. Noise encrypt() rejects data > 65535 bytes. Fix: chunk writes into
   ≤65519-byte (65535 - 16 MAC) segments before encrypting, matching
   go-libp2p's noise/rw.go Write() approach.

2. Yamux frames sized by send_window (up to 16MB) overwhelm transports.
   Fix: cap per-frame payload at 64KB - 12 (header), matching go-yamux's
   MaxMessageSize default.
The chunking loop skipped empty messages (b"") which broke the Noise XX
handshake msg#1. Use a fast path for messages ≤ MAX_PLAINTEXT_LENGTH
(including empty) and only chunk larger messages.
@asabya
Copy link
Copy Markdown
Contributor Author

asabya commented Mar 26, 2026

This PR broke Noise and WS+TLS

TLDR;

libp2p/unified-testing#62 (comment)

@seetadev
Copy link
Copy Markdown
Contributor

@acul71 , @asabya : Wish if we could arrive at a good conclusion on this PR. Also, address libp2p/unified-testing#62 (comment)

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.

3 participants