hono icon indicating copy to clipboard operation
hono copied to clipboard

Error when using streamSSE: IncompleteRead(0 bytes read, 2 more expected))

Open roziscoding opened this issue 1 year ago • 2 comments

What version of Hono are you using?

4.6.5

What runtime/platform is your app running on?

Bun

What steps can reproduce the bug?

  1. Run this code with bun run --watch <path-to-file>
import { streamSSE } from "hono/streaming";
import { EventEmitter } from 'node:events'
import { Hono } from "hono";
import { cors } from 'hono/cors'
import crypto from 'node:crypto'
import { zValidator } from "@hono/zod-validator";
import { TapObject } from '@internal-lib/tap/schemas'

const emitter = new EventEmitter<{
  tap: [id: string, object: TapObject],
}>()

export const app = new Hono();

app.use('*', cors())

app.post('/tap', zValidator('json', TapObject), async (c) => {
  const object = c.req.valid('json')
  const id = crypto.randomUUID()
  emitter.emit('tap', id, object)

  return new Response(JSON.stringify({ id }), { status: 201 })
})

app.get('/taps', cors(), (c) => {
  return streamSSE(c, async (stream) => {
    emitter.on('tap', async (id, object) => {
      await stream.writeSSE({
        data: JSON.stringify(object),
        event: object.type,
        id,
      })
    })

    return new Promise((resolve) => {
      stream.onAbort(() => {
        emitter.removeAllListeners('tap')
        resolve()
      })
    })
  }, async (e) => {
    console.error(e)
  })
})

const server = Bun.serve({
  fetch: app.fetch,
  port: 3000,
})

console.log(`Server is running on port ${server.port}`)
  1. Connect with an EventSource client or a terminal http client

What is the expected behavior?

An SSE connection is open, and the events arrive correctly, with no errors

What do you see instead?

The events do arrive correctly for a while, but after some (apparently arbitrary) amount of time, I get the following error on HTTPie:

ChunkedEncodingError: ('Connection broken: IncompleteRead(0 bytes read, 2 more expected)', IncompleteRead(0 bytes read, 2 more expected))

And the following error on chrome: GET http://localhost:3000/taps net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)

Additional information

I've tried running the same code (given some imports adjustment and replacing of Bun.serve with Deno.serve) on Deno, and I get no errors at all. It all just works normally.

I've also tried using a plain TransformStream on Bun, and the error persisted. I'd like some help on narrowing this down before I think of opening an issue on Bun itself.

roziscoding avatar Oct 21 '24 16:10 roziscoding

Hi @roziscoding

I can't reproduce it. I've tried it, but it works well. Can you share a minimal project to reproduce it?

yusukebe avatar Oct 26 '24 06:10 yusukebe

Hi @roziscoding

I can't reproduce it. I've tried it, but it works well. Can you share a minimal project to reproduce it?

sorry for the late reply. I'll be able to share a repro on about 8 hours max. thanks for trying it tho.

roziscoding avatar Oct 28 '24 23:10 roziscoding

I was able to reproduce it using the following code:

import { streamSSE } from "hono/streaming";
import { Hono } from "hono";

export const app = new Hono();

app.get('/taps', (c) => {
  return streamSSE(c, async (stream) => {
    await stream.writeSSE({
      data: 'ping',
    })

    return new Promise((resolve) => {
      stream.onAbort(() => {
        resolve()
      })
    })
  }, async (e) => {
    console.error(e)
  })
})

const server = Bun.serve({
  fetch: app.fetch,
  port: 3000,
})

console.log(`Server is running on port ${server.port}`)

Although, after updating Bun to version 1.1.33, I got an error on the server's console:

[Bun.serve]: request timed out after 10 seconds. Pass `idleTimeout` to configure.

Which lead me to try this code:

import { streamSSE } from "hono/streaming";
import { Hono } from "hono";

export const app = new Hono();

app.get('/taps', (c) => {
  return streamSSE(c, async (stream) => {
    const interval = setInterval(() => {
      stream.writeSSE({
        data: 'ping',
      })
    }, 1000)

    return new Promise((resolve) => {
      stream.onAbort(() => {
        clearInterval(interval)
        resolve()
      })
    })
  }, async (e) => {
    console.error(e)
  })
})

const server = Bun.serve({
  fetch: app.fetch,
  port: 3000,
})

console.log(`Server is running on port ${server.port}`)

An this worked. So it was probably some internal Bun problem with detecting and killing idle connections that got solved by the update. It might be worth it to add a note on the docs about idleTimeout on Bun. I can contribute that if you agree.

roziscoding avatar Oct 31 '24 12:10 roziscoding

Also, it might be worth it updating the issue template to ask for the runtime version as well, to make it easy to verify if people are on the latest version. That I can't contribute, tho 😅

roziscoding avatar Oct 31 '24 12:10 roziscoding

@roziscoding I have created a PR for the version description.
Please check it out if you like.

EdamAme-x avatar Oct 31 '24 13:10 EdamAme-x

@roziscoding

I have created a PR for the version description.

Please check it out if you like.

looks good. thanks!

roziscoding avatar Oct 31 '24 18:10 roziscoding

Hi @roziscoding

An this worked. So it was probably some internal Bun problem with detecting and killing idle connections that got solved by the update. It might be worth it to add a note on the docs about idleTimeout on Bun. I can contribute that if you agree.

Adding the note is a good idea. Please create a PR in the honojs/website repo!

yusukebe avatar Nov 01 '24 07:11 yusukebe