Reading from a socket gets stuck on Node.js 26

Open Beginner friendly
#3,793 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 3 days

@sirocchj is already working on this.

Since Oct 30, 2026.

  • #3794 by @sirocchj — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, scala
Domain
networking

Research direction

Start with the reproduction test described for IoPlatformSuite, then trace the Scala.js readable-stream code that calls read() on Node's 'readable' event. Add a regression test for buffered chunks and verify it reads all data on Node.js 26 while remaining compatible with older Node.js versions.

Written by the indexing model from the issue text.

Description

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.

Dominant language
Scala
Stars
2.5k
Forks
633
Avg merge
4d 16h
Merged PRs (30d)
5

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from typelevel/fs2

All issues in typelevel/fs2

Similar issues

More Scala issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.