Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream.timeoutFail doesn't work for hanging Node Readable #4490

Open
stebl opened this issue Feb 21, 2025 · 0 comments
Open

Stream.timeoutFail doesn't work for hanging Node Readable #4490

stebl opened this issue Feb 21, 2025 · 0 comments
Labels
bug Something isn't working

Comments

@stebl
Copy link

stebl commented Feb 21, 2025

What version of Effect is running?

3.13.2

What steps can reproduce the bug?

I can reproduce this in jest,

it('given a hanging stream, should timeout', async () => {
  jest.useFakeTimers();

  const readable = new Readable({
    read() {
      /**
       * Override _read.  Keep the stream open by never pushing null to the buffer
       */
    },
  });
  readable.push(Buffer.from(`1234567890`));

  const stream = () =>
    pipe(
      Stream.fromAsyncIterable(readable, (e) => e),
      Stream.timeoutFail(() => new Error('Timeout'), '2 seconds'),
      Stream.runForEachChunk((chunk) =>
        Effect.sync(() => {
          console.log(chunk);
        }),
      ),
    );

  const program = stream();

  const promise = Effect.runPromise(program)
    .then(console.log)
    .catch((e) => expect(e.message).toBe('Timeout'));

  await jest.advanceTimersByTimeAsync(10 * 60 * 1000); // 10 minutes
  expect.assertions(1);
  await promise;  // test times out, never resolves
});

What is the expected behavior?

Expect the effect to result in a timeout error

What do you see instead?

Instead the test times out after 45 seconds (our test config)

Additional information

Any workarounds here?

Stream.fromReadableStream isn't applicable here b/c the Web API stream is not the same as the node stream.

The timeout works when a generator is provided, example below. Unfortunately the code is receiving a Readable from an HTTP request, so the difference in behavior between Readable and Iterator is likely to result in the stream hanging in production.

it.only('given a generator that stalls, will timeout', async () => {
  jest.useFakeTimers();

  async function* generate() {
    yield 1;
    yield 2;
    await new Promise((resolve) => setTimeout(resolve, 5000));
    yield 3;
    yield 4;
  }

  const stream = () =>
    pipe(
      Stream.fromAsyncIterable(generate(), (e) => e),
      Stream.timeoutFail(() => new Error('Timeout'), '2 seconds'),
      Stream.runForEachChunk((chunk) =>
        Effect.sync(() => {
          console.log(chunk);
        }),
      ),
    );

  const program = stream();

  const promise = Effect.runPromise(program)
    .then(console.log)
    .catch((e) => expect(e.message).toBe('Timeout'));

  await jest.advanceTimersByTimeAsync(10 * 60 * 1000 + 1500);
  expect.assertions(1);
  await promise;  // resolves with caught error
});
@stebl stebl added the bug Something isn't working label Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant