Skip to content

Invalid state: Controller is already closed #1564

@FZambia

Description

@FZambia

Bug Description

When running my tests with undici v5.8.0 I am getting the traceback:

node:internal/errors:466
    ErrorCaptureStackTrace(err);
    ^

TypeError: Invalid state: Controller is already closed
    at new NodeError (node:internal/errors:377:5)
    at ReadableStreamDefaultController.enqueue (node:internal/webstreams/readablestream:992:13)
    at Fetch.fetchParams.controller.resume (/Users/fz/projects/centrifugal/centrifuge-js/node_modules/undici/lib/fetch/index.js:1864:41)
    at processTicksAndRejections (node:internal/process/task_queues:95:5) {
  code: 'ERR_INVALID_STATE'

I did some research if this can be helpful:

In my case the code in undici is getting exception in calling await fetchParams.controller.next() in lib/fetch/index.js, so catch branch works:

      try {
        const { done, value } = await fetchParams.controller.next()

        if (isAborted(fetchParams)) {
          break
        }

        bytes = done ? undefined : value
      } catch (err) {
        if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
          // zlib doesn't like empty streams.
          bytes = undefined
        } else {
          bytes = err
        }
      }

I added some console logging:

      } catch (err) {
        console.log(err); // DOMException [AbortError]: The operation was aborted.
        console.log(fetchParams.controller.ended); // undefined
        console.log(timingInfo.encodedBodySize); // 136
        console.log(fetchParams.controller.ended && !timingInfo.encodedBodySize); // undefined
        if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
          // zlib doesn't like empty streams.
          bytes = undefined
        } else {
          console.log("bytes is err"); // bytes is err
          bytes = err
          console.log(bytes instanceof Error); // false
          console.log(typeof bytes); // object
        }
      }

The detailed exception is:

DOMException [AbortError]: The operation was aborted.
        at new DOMException (node:internal/per_context/domexception:51:5)
        at Fetch.abort (/Users/fz/projects/centrifugal/centrifuge-js/node_modules/undici/lib/fetch/index.js:89:20)
        at AbortSignal.requestObject.signal.addEventListener.once (/Users/fz/projects/centrifugal/centrifuge-js/node_modules/undici/lib/fetch/index.js:165:20)
        at AbortSignal.[nodejs.internal.kHybridDispatch] (node:internal/event_target:639:20)
        at AbortSignal.dispatchEvent (node:internal/event_target:581:26)
        at abortSignal (node:internal/abort_controller:291:10)
        at AbortController.abort (node:internal/abort_controller:321:5)
        at AbortSignal.abort (/Users/fz/projects/centrifugal/centrifuge-js/node_modules/undici/lib/fetch/request.js:372:32)
        at AbortSignal.[nodejs.internal.kHybridDispatch] (node:internal/event_target:639:20)
        at AbortSignal.dispatchEvent (node:internal/event_target:581:26)

So I suppose fetchParams.controller.controller.enqueue(new Uint8Array(bytes)) is called even if the stream was aborted.

Reproducible By

Create a new Node project with npm init

Add jest:

npm install --save-dev jest

Add test file index.test.js:

test('test exception', async () => {
    const d = new DOMException('test')
    expect(d).toBeInstanceOf(Error)
    expect(d instanceof Error).toBeTruthy()
})

Update package.json to have:

{
  "scripts": {
    "test": "jest"
  }
}

Run npm test

Expected Behavior

DOMException in Jest env does not extend Error, so stream aborting logic in

if (bytes instanceof Error) {
does not work.

Environment

Node v18.2.0

Running tests with jest

Additional context

This reproduces starting from undici v5.6.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions