Reading from a socket gets stuck on Node.js 26

オープン 初心者向け
#3,793 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 3 日以内に返信

@sirocchj がすでに取り組んでいます。

2026年10月30日 から。

  • #3794 @sirocchj による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
node.js, scala
領域
networking

調査の方向性

IoPlatformSuiteについて説明されている再現テストから始め、次にNodeの'readable'イベントでread()を呼び出すScala.jsのreadable-streamコードを追ってください。バッファリングされたチャンクの回帰テストを追加し、Node.js 26ですべてのデータを読み取ることを確認するとともに、古いNode.jsバージョンとの互換性も維持してください。

索引モデルが issue の本文から書いたものです。

説明

bug

On Node.js 26, socket.reads can stop returning data even though the data has arrived. It works on Node.js 25 and earlier.

  • fs2-io 3.14.0, also on main (419d575)
  • cats-effect 3.7.1
  • Scala 2.13 and 3.3.7
  • Scala.js 1.22.0, Node.js 26.13.0, macOS
  • JVM and Native are fine
How to reproduce

This test, added to IoPlatformSuite, puts three pieces of data in a stream and reads them. On Node.js 26 it times out after getting only the first piece:

test("reads every chunk already buffered in a Readable") {
  def readable = {
    val duplex = new facade.stream.Duplex(
      new facade.stream.DuplexOptions {
        var autoDestroy = false
        var read = _ => ()
        var write = (_, _, _, _) => ()
        var `final` = (_, _) => ()
        var destroy = (_, _, _) => ()
      }
    )
    List[Byte](0, 1, 2).foreach(b => duplex.push(Chunk(b, b).toUint8Array))
    duplex
  }

  Stream
    .resource(suspendReadableAndRead[IO, Duplex]()(readable))
    .flatMap(_._2)
    .take(6)
    .compile
    .toList
    .timeout(1.second)
    .assertEquals(List[Byte](0, 0, 1, 1, 2, 2))
}

You can also see it with a real socket:

//> using scala 3.3.7
//> using platform scala-js
//> using jsVersion 1.22.0
//> using jsModuleKind common
//> using dep co.fs2::fs2-io::3.14.0

import cats.effect.{IO, IOApp, Resource}
import com.comcast.ip4s.*
import fs2.io.net.Network

import scala.concurrent.duration.*
import scala.scalajs.js

object Main extends IOApp.Simple {
  private val net = js.Dynamic.global.require("net")

  // sends 8 bytes three times, 50ms apart, then stays quiet
  private val server: Resource[IO, Int] =
    Resource.make(IO.async_[js.Dynamic] { cb =>
      val s = net.createServer { (socket: js.Dynamic) =>
        socket.write("aaaaaaaa")
        js.timers.setTimeout(50)(socket.write("bbbbbbbb"))
        js.timers.setTimeout(100)(socket.write("cccccccc"))
        ()
      }
      s.listen(0, "127.0.0.1", () => cb(Right(s)))
    })(s => IO(s.close()).void).map(_.address().port.asInstanceOf[Int])

  val run: IO[Unit] =
    server.use { port =>
      Network[IO].connect(SocketAddress(ipv4"127.0.0.1", Port.fromInt(port).get)).use { socket =>
        // wait until all 24 bytes have arrived, then read them
        IO.sleep(500.millis) >>
          socket.reads.chunks
            .evalTap(chunk => IO.println(s"  got ${chunk.size} bytes"))
            .flatMap(fs2.Stream.chunk)
            .take(24)
            .compile
            .count
            .timeout(3.seconds)
            .attempt
            .flatMap {
              case Right(n) => IO.println(s"Node ${js.Dynamic.global.process.version}: read all $n bytes")
              case Left(_)  => IO.println(s"Node ${js.Dynamic.global.process.version}: got stuck, the rest never came")
            }
      }
    }
}

On Node.js 26:

  got 8 bytes
  got 8 bytes
Node v26.13.0: got stuck, the rest never came

On Node.js 25 (same on 24 and 22):

  got 8 bytes
  got 16 bytes
Node v25.9.0: read all 24 bytes
Why

Node.js 26 changed readable.read(): it now returns one piece of the buffered data at a time, where it used to return all of it (nodejs/node#60441).

fs2 calls read() once each time Node fires 'readable'. Node only fires 'readable' again once everything has been read or more data comes in. So on Node.js 26, if nothing else comes in, the rest of the data is never read.

Fix

Keep calling read() until it returns null. On older Node.js versions the first call already returns everything, so nothing changes. PR to follow.

主要言語
Scala
スター
2.5k
フォーク
633
平均マージ
4日 16時間
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

typelevel/fs2 のほかの issue

typelevel/fs2 の issue をすべて見る

似ている issue

Scala の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。