denoland / denoland/std

pooledMap can throw uncaught `undefined` when cancelled

Open
#2,197 1 comment 1 reaction 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
3.6k
Forks
681
PR merge metrics
No merged PRs in 30d

Description

**Describe the bug**

If `pooledMap` gets canceled, it seems to throw an uncaught `undefined` value. Cancelling can happen due to returning from within the loop body. The `undefined` does not seem to be catchable with a `try {} catch {}` construction.

The lack of a stack trace can make debugging tricky, as the developer may not immediately suspect `pooledMap` to be the source of the issue.

**Steps to Reproduce**

```ts
import { pooledMap } from "https://deno.land/std@0.138.0/async/pool.ts";

Deno.test('cancel pooledMap', async () => {
async function firstNumber() {
for await (const num of pooledMap(4, [100], num => Promise.resolve(num))) {
return num;
}
}
await firstNumber();
});
```

```
> deno test
running 1 test from pooledMap_test.ts
test cancel pooledMap ...
test result: FAILED. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (34ms)

error: Uncaught (in promise) undefined
```

**Expected behavior**

In this specific case, there were no remaining items to map over, so I expected pooledMap to cleanly accept the cancelation. I had a routine where some outputs result in immediately returning a specific value, so it's a bit cleaner to return from the loop body.

Alternatively, if cancellation isn't considered sound, I'd expect `pooledMap` to throw an actual `Error` so that a stack trace is printed.

I'd also expect any error to bubbled through my calling code instead of being uncaught.

**Extra information**

If `poolLimit` is set to `1` (disabling concurrency), then an actual error _is_ thrown. It is still uncaught:

```ts
import { pooledMap } from "https://deno.land/std@0.138.0/async/pool.ts";

Deno.test('cancel pooledMap', async () => {
async function firstNumber() {
for await (const num of pooledMap(1, [100], num => Promise.resolve(num))) {
return num;
}
}
try {
await firstNumber();
} catch (err) {
// attempt to not crash the program
console.log('err:', err);
}
});
```

```
> deno test
running 1 test from pooledMap_test.ts
test cancel pooledMap ...
test result: FAILED. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (26ms)

error: Uncaught (in promise) TypeError: Writable stream is closed or errored.
writer.close();
^
at writableStreamClose (deno:ext/web/06_streams.js:3541:9)
at writableStreamDefaultWriterClose (deno:ext/web/06_streams.js:3778:12)
at WritableStreamDefaultWriter.close (deno:ext/web/06_streams.js:5534:14)
at https://deno.land/std@0.138.0/async/pool.ts:54:14
```

**Environment**

- OS: `Debian GNU/Linux 11 (bullseye)`
- deno version: `1.20.5`
- std version: Found on `0.130.0`, verified on `0.138.0`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.