Skip to content

yamux Stream.Read/write spins at 100% CPU on session shutdown (vendored fix in third_party; upstream PR TODO) #3923

Description

@0pcom

Summary

github.com/hashicorp/yamux v0.1.2 Stream.Read (and Stream.write) can spin a single goroutine at 100% CPU forever when the underlying session is shut down while a read/write is blocked and the stream is still in streamEstablished.

In each WAIT block the select has:

select {
case <-s.session.shutdownCh:   // closed on session teardown — no body, no return
case <-s.recvNotifyCh:
case <-timeout:
    return 0, ErrTimeout
}
...
goto START

shutdownCh is a closed channel after teardown, so once the session is gone that case is always ready. It has no body, so it falls through to goto START. START only inspects the per-stream s.state, which stays streamEstablished when a session is torn down out from under an in-flight read — so START finds no data and jumps back to WAIT, which re-fires shutdownCh immediately, re-allocating a time.NewTimer each pass. Result: one goroutine pegs a core at ~370k time.NewTimer/s and never returns.

Where it bites us

The wasm (js/wasm) hypervisor build is single-threaded, so a single spinning Stream.Read starves the entire runtime — the tab pegs a CPU core and stops responding. It reproduces when a dmsg WS / WebRTC carrier drops during an in-flight dial (session teardown mid-readResponse). Native builds are far less exposed (multi-core, TCP carriers rarely drop mid-read) but the same latent spin exists.

Fix

Return from the shutdownCh case in both Read and write instead of falling through to goto START:

case <-s.session.shutdownCh:
    return 0, ErrSessionShutdown

A shut-down session can neither deliver more data nor drain the send window, so returning is strictly correct.

Status in this repo

Applied locally by copying yamux in-module under third_party/hashicorp/yamux/ (import path github.com/skycoin/skywire/third_party/hashicorp/yamux, package unchanged) with the fix + PATCHES.md provenance. An upstream PR to hashicorp/yamux should follow (needs a fork); this issue tracks that follow-up so the local copy can eventually be dropped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions