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

Allow setting custom highWaterMark via node-fetch options (#386) #671

Merged
merged 10 commits into from Sep 24, 2019

Conversation

xxczaki
Copy link
Member

@xxczaki xxczaki commented Sep 9, 2019

Fixes #386

This PR uses some bits from #563, but instead of passing the custom highWaterMark to clone method, it is passed via node-fetch options:

- fetch(url).then(res => res.clone(10).buffer());
+ fetch(url, {highWaterMark: 10}).then(res => res.clone().buffer());

I used the same tests from #563 to confirm that everything works as expected. However, it would be great if someone could review my changes to check whether I made any mistakes 😄

@bitinn
Copy link
Collaborator

bitinn commented Sep 12, 2019

Interesting, so you end up adding an extension to the clone() API.

I would say that's understandable, but wonder:

  • Whether we should default to a larger highWaterMark on clone, current limit (16KB) is fairly low even for a JSON response, it seems fair to say people wouldn't want to use this API extension unless absolutely needed.

@xxczaki
Copy link
Member Author

xxczaki commented Sep 12, 2019

@bitinn Well, we can set a default highWaterMark value to match browser limits (about 1MB, not consistent in all browsers). Are there any downsides from using higher limit than default 16KB?

@xxczaki xxczaki mentioned this pull request Sep 12, 2019
35 tasks
@bitinn
Copy link
Collaborator

bitinn commented Sep 13, 2019

@xxczaki some thoughts:

  • In theory I don't want to set a larger highWaterMark at all, it basically means we are hiding an issue from users: that they didn't fully understand how stream works, and let 1 of the cloned stream buffer the entire content in memory instead. (because content flow into both cloned streams at the same time, if one is filling up, it will cause back-pressure on upstream, which pauses both streams.)

  • But the reality is: browsers used to implement this freely, as Stream spec doesn't suggest a default value; Chrome used to buffer as much as possible until OOM, so users will never run into a back-pressure problem.

  • Now that there is a CountQueuingStrategy, which means the number of chucks a stream can hold, I have been looking for a default value from major browsers, but haven't found one. And of course both highWaterMark and chunk size can be set based on platforms and devices...

So here is the thing:

  • We introduce an API extension that will for sure make isomorphic users not happy.

  • We can't share similar highWaterMark as browsers, and people will surely run into back-pressure issue not experienced in browsers, mostly because of their own misunderstanding.

  • I don't know what we should do, let people experience their own fault or shield them from it in common cases.

  • I would like to hear opinions.

@@ -1755,6 +1759,69 @@ describe('node-fetch', () => {
);
});

it('should timeout on cloning response without consuming one of the streams when the second packet size is equal default highWaterMark', function () {
this.timeout(300);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a cleaner way to test these instead of timeout?

  • let one of the clone stream flow,
  • count bytes,
  • let it back-pressure, make sure it's within expectation
  • then observe the drain event on it
  • letting the other stream flow.
  • and know that it works as expected?

@bitinn
Copy link
Collaborator

bitinn commented Sep 16, 2019

ping @jimmywarting @gr2m for comments:

  • Should we add this API extension?

  • Should we set a larger highWaterMark on clone()?

  • Otherwise, should we just provide a segment on how clone streams work in our readme?

See my comment above for why I don't like this solution: #671 (comment)

@gr2m
Copy link
Collaborator

gr2m commented Sep 16, 2019

I don't have strong opinions either way. If we introduce the new option then we should add documentation for it

@jimmywarting
Copy link
Collaborator

  • In theory I don't want to set a larger highWaterMark at all, it basically means we are hiding an issue from users: that they didn't fully understand how stream works, and let 1 of the cloned stream buffer the entire content in memory instead. (because content flow into both cloned streams at the same time, if one is filling up, it will cause back-pressure on upstream, which pauses both streams.)

Fair point.

So here is the thing:

  • We introduce an API extension that will for sure make isomorphic users not happy.
  • We can't share similar highWaterMark as browsers, and people will surely run into back-pressure issue not experienced in browsers, mostly because of their own misunderstanding.
  • I don't know what we should do, let people experience their own fault or shield them from it in common cases.

Sticking to our motto "stay as close to the spec as possible without much differences".

what's the status of bringing whatwg streams into node?
If we switch to whatwg streams would it break stuff?

could we maybe increase the/set highWaterMark by some % of available memory but maybe still limit it to something with Math.max?

I have not had any problem with how it currently works, never had to clone anything using node-fetch so i don't have a strong opinion either

@bitinn
Copy link
Collaborator

bitinn commented Sep 17, 2019

could we maybe increase the/set highWaterMark by some % of available memory but maybe still limit it to something with Math.max?

probably not safe, people do parallel fetching all the time, handling that means a lot more complexity.

what's the status of bringing whatwg streams into node?

Stream spec is getting more complex by the days, I just don't know if it's ever going to be in nodejs core. I do want to support the polyfill at some point though.

@xxczaki
Copy link
Member Author

xxczaki commented Sep 21, 2019

When creating this PR, I was following the suggested solution from this comment. I think what we should do, no matter what solution we choose, is to throw a specific error message, when the default highWaterMark limit is surpassed, so that users will know about the fact, that the default Node.js limit is different, than the one in the browser.

@bitinn
Copy link
Collaborator

bitinn commented Sep 22, 2019

When creating this PR, I was following the suggested solution from this comment.

It's indeed my suggestion to pass highWaterMark via options. Though I always have some reservations on fixing the problem this way. Let me explain below.

no matter what solution we choose, is to throw a specific error message, when the default highWaterMark limit is surpassed

I don't think this is possible or desirable: back-pressure is simply a core mechanism of streams, it keeps the memory usage of nodejs process to a reasonable level.

  • We don't know the size of the stream beforehand, hence can't provide a message on clone() to inform users it may trigger back-pressure.
  • And when back-pressure kicks it, it simply pause the upstream and wait for the blocking downstream to drain, there is no inherent issues here.
  • What nodejs is saying: Users are solely responsible to let their downstream drain and not fill up.
  • But to the user's eyes: Their isomorphic application appears to "hang forever", and "it doesn't happen in browsers".

So my reservation on the fix has been:

  • The user will need to know their issue is caused by back-pressure to rise the highWaterMark cap.
  • But if they already know that, they might as well realize the correct solution is to pipe both of your clone stream to somewhere.
  • That is: say you have got a large JSON response stream, you want to run 2 different operations on it, so you clone it, then you are responsible to do them in parallel, not one after another.
  • Because doing it in sequence will require the idle stream to hold all data in memory, causing back-pressure.

So the solution we offer here is for users to be lazy, "just hold all of them in memory instead", well, my question is: if you don't care about memory, why use stream or clone? why not get a buffer() or text() or blob()?

/rant over

@bitinn
Copy link
Collaborator

bitinn commented Sep 22, 2019

But all in all, I think users aren't going to think much on this:

  • That clone is essentially tee-ing the response stream (as defined in Fetch spec);
  • But instead copy-ing the content of response object (this is much more nature);
  • Fetch is after all, an easy to use, low-level API.

So I say, fine, merge this PR, give users an easy way out ^_^

@bitinn
Copy link
Collaborator

bitinn commented Sep 22, 2019

(by the way, our tests actually illustrate 2 ways a cloned response can be used, and the reason people keep running into this problem is because they chose the sequential version, it's more nature to think that way...)

@xxczaki
Copy link
Member Author

xxczaki commented Sep 24, 2019

Before merging this PR, I will document this option ;)

@xxczaki xxczaki merged commit 48d45ec into 3.x Sep 24, 2019
@xxczaki xxczaki deleted the highWaterMark-fix branch September 24, 2019 19:00
@bitinn
Copy link
Collaborator

bitinn commented Sep 25, 2019 via email

@xxczaki xxczaki deleted the highWaterMark-fix branch September 25, 2019 12:03
@xxczaki
Copy link
Member Author

xxczaki commented Sep 26, 2019

@bitinn I agree, we should also edit the LIMITS.md file and update the section related to highWaterMark.

/**
* Clone this response
*
* @return Response
*/
clone() {
return new Response(clone(this), {
return new Response(clone(this, this.highWaterMark), {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I could briefly comment on this, I've noticed that in the scenario that a cloned response gets cloned, highWaterMark also needs to be passed in the response options to prevent it from being undefined.

bitinn added a commit that referenced this pull request Mar 13, 2020
* feat: Migrate TypeScript types (#669)

* style: Introduce linting via XO

* fix: Fix tests

* chore!: Drop support for nodejs 4 and 6

* chore: Fix Travis CI yml

* Use old Babel (needs migration)

* chore: lint everything

* chore: Migrate to microbundle

* Default response.statusText should be blank (#578)

* fix: Use correct AbortionError message

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Use modern @babel/register

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Remove redundant packages

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Readd form-data

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* fix: Fix tests and force utf8-encoded urls

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* lint index.js

* Update devDependencies & ignore `test` directory in linter options

* Remove unnecessary eslint-ignore comment

* Update the `lint` script to run linter on every file

* Remove unused const & unnecessary import

* TypeScript: Fix Body.blob() wrong type (DefinitelyTyped/DefinitelyTyped#33721)

* chore: Lint as part of the build process

* fix: Convert Content-Encoding to lowercase (#672)

* fix: Better object checks (#673)

* Fix stream piping (#670)

* chore: Remove useless check

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* style: Fix lint

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* style: Fix lint

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* refactor: Modernise code

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Ensure all files are properly included

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Update deps and utf8 should be in dependencies

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* test: Drop Node v4 from tests

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* test: Modernise code

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Move errors to seperate directory

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* refactor: Add fetch-blob (#678)

* feat: Migrate data uri integration

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Allow setting custom highWaterMark via node-fetch options (#386) (#671)

* Expose highWaterMark option to body clone function

* Add highWaterMark to responseOptions

* Add highWaterMark as node-fetch-only option

* a way to silently pass highWaterMark to clone

* Chai helper

* Server helper

* Tests

* Remove debug comments

* Document highWaterMark option

* Add TypeScript types for the new highWaterMark option

* feat: Include system error in FetchError if one occurs (#654)

* style: Add editorconfig

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore!: Drop NodeJS v8

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Remove legacy code for node < 8

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Use proper checks for ArrayBuffer and AbortError

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Use explicitly set error name in checks

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Propagate size and timeout to cloned response (#664)

* Remove --save option as it isn't required anymore (#581)

* Propagate size and timeout to cloned response


Co-authored-by: Steve Moser <contact@stevemoser.org>
Co-authored-by: Antoni Kepinski <xxczaki@pm.me>

* Update Response types

* Update devDependencies

* feat: Fallback to blob type (Closes: #607)

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* style: Update formatting

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* style: Fix linting issues

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: Add info on patching the global object

* docs: Added non-globalThis polyfill

* Replace deprecated `url.resolve` with the new WHATWG URL

* Update devDependencies

* Format code in examples to use `xo` style

* Verify examples with RunKit and edit them if necessary

* Add information about TypeScript support

* Document the new `highWaterMark` option

* Add Discord badge & information about Open Collective

* Style change

* Edit acknowledgement & add "Team" section

* fix table

* Format example code to use xo style

* chore: v3 release changelog

* Add the recommended way to fix `highWaterMark` issues

* docs: Add simple Runkit example

* fix: Properly set the name of the errors.

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: Add AbortError to documented types

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: AbortError proper typing parameters

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: Add example code for Runkit

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Replace microbundle with @pika/pack (#689)

* gitignore the pkg/ directory

* Move TypeScript types to the root of the project

* Replace microbundle with @pika/pack

* chore: Remove @pika/plugin-build-web and revert ./dist output directory

Signed-off-by: Richie Bendall <richiebendall@gmail.com>


Co-authored-by: Richie Bendall <richiebendall@gmail.com>

* fix incorrect statement in changelog

* chore: v3.x upgrade guide

* Change the Open Collective button

* docs: Encode support button as Markdown instead of HTML

* chore: Ignore proper directory in xo

* Add an "Upgrading" section to readme

* Split the upgrade guide into 2 files & add the missing changes about v3.x

* style: Lint test and example files

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Move *.md files to the `docs` folder (except README.md)

* Update references to files

* Split LIMITS.md into 2 files (as of v2.x and v3.x)

* chore: Remove logging statement

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* style: Fix lint

* docs: Correct typings for systemError in FetchError (Fixes #697)

* refactor: Replace `encoding` with `fetch-charset-detection`. (#694)

* refactor: Replace `encoding` with `fetch-charset-detection`.

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* refactor: Move writing to stream back to body.js

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* refactor: Only put convertBody in fetch-charset-detection and refactor others.

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* test: Readd tests for getTotalBytes and extractContentType

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Revert package.json indention

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Remove optional dependency

* docs: Replace code for fetch-charset-detection with documentation.

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Remove iconv-lite

* fix: Use default export instead of named export for convertBody

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Remove unneeded installation of fetch-charset-detection in the build

* docs: Fix typo

* fix: Throw SyntaxError instead of FetchError in case of invalid… (#700)

* fix: Throw SyntaxError instead of FetchError in case of invalid JSON

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: Add to upgrade guide

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Remove deprecated url.parse from test

* Remove deprecated url.parse from server

* fix: Proper data uri to buffer conversion (#703)

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Add funding info

* fix: Flawed property existence test (#706)

Fix a problem where not all prototype methods are copied from the Body via the mixin method due to a failure to properly detect properties in the target. The current code uses the `in` operator, which may return properties lower down the inheritance chain, thus causing them to fail the copy. The new code properly calls the `.hasOwnProperty()` method to make the determination.

* fix: Properly handle stream pipeline double-fire

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: Fix spelling

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Add `funding` field to package.json (#708)

* Fix: Do not set ContentLength to NaN (#709)

* do not set ContentLength to NaN

* lint

* docs: Add logo

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Update repository name from bitinn/node-fetch to node-fetch/node-fetch.

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Fix unit tests

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore(deps): Bump @pika/plugin-copy-assets from 0.7.1 to 0.8.1 (#713)

Bumps [@pika/plugin-copy-assets](https://github.com/pikapkg/builders) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.7.1...v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* chore(deps): Bump @pika/plugin-build-types from 0.7.1 to 0.8.1 (#710)

Bumps [@pika/plugin-build-types](https://github.com/pikapkg/builders) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.7.1...v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump nyc from 14.1.1 to 15.0.0 (#714)

Bumps [nyc](https://github.com/istanbuljs/nyc) from 14.1.1 to 15.0.0.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](istanbuljs/nyc@v14.1.1...v15.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* chore: Update travis ci url

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore(deps): Bump mocha from 6.2.2 to 7.0.0 (#711)

Bumps [mocha](https://github.com/mochajs/mocha) from 6.2.2 to 7.0.0.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](mochajs/mocha@v6.2.2...v7.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* feat: Allow excluding a user agent in a fetch request by setting… (#715)

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* Bump @pika/plugin-build-node from 0.7.1 to 0.8.1 (#717)

Bumps [@pika/plugin-build-node](https://github.com/pikapkg/builders) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.7.1...v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump @pika/plugin-standard-pkg from 0.7.1 to 0.8.1 (#716)

Bumps [@pika/plugin-standard-pkg](https://github.com/pikapkg/builders) from 0.7.1 to 0.8.1.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.7.1...v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump form-data from 2.5.1 to 3.0.0 (#712)

Bumps [form-data](https://github.com/form-data/form-data) from 2.5.1 to 3.0.0.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Commits](form-data/form-data@v2.5.1...v3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* fix: typo

* update suggestion

* feat: Added missing redirect function (#718)

* added missing redirect function
* chore: Add types

Co-authored-by: Richie Bendall <richiebendall@gmail.com>

* fix: Use req.setTimeout for timeout (#719)

* chore: Update typings comment

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* chore: Update deps

Signed-off-by: Richie Bendall <richiebendall@gmail.com>

* docs: center badges & Open Collective button

* docs: add missing comma

* Remove current stable & LTS node version numbers from the comments

I don't think we really want to update them

* Bump xo from 0.25.4 to 0.26.1 (#730)

Bumps [xo](https://github.com/xojs/xo) from 0.25.4 to 0.26.1.
- [Release notes](https://github.com/xojs/xo/releases)
- [Commits](xojs/xo@v0.25.4...v0.26.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump @pika/plugin-build-types from 0.8.3 to 0.9.2 (#729)

Bumps [@pika/plugin-build-types](https://github.com/pikapkg/builders) from 0.8.3 to 0.9.2.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.8.3...v0.9.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Bump @pika/plugin-standard-pkg from 0.8.3 to 0.9.2 (#726)

Bumps [@pika/plugin-standard-pkg](https://github.com/pikapkg/builders) from 0.8.3 to 0.9.2.
- [Release notes](https://github.com/pikapkg/builders/releases)
- [Commits](FredKSchott/pika-pack-builders@v0.8.3...v0.9.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* docs: Update information about `req.body` type in v3.x release

* Add information about removed body type to the v3 upgrade guide

* add awesome badge

* Show 2 ways of importing node-fetch (CommonJS & ES module)

* update dependencies

* lint

* refactor: Replace `url.parse` with `new URL()` (#701)

* chore: replace `url.parse` with `new URL()`

* lint

* handle relative URLs

* Change error message

* detect whether the url is absolute or not

* update tests

* drop relative url support

* lint

* fix tests

* typo

* Add information about dropped arbitrary URL support in v3.x upgrade guide

* set xo linting rule (node/no-deprecated-api) to on

* remove the `utf8` dependency

* fix

* refactor: split tests into several files, create the `utils` directory

* Update package.json scripts & remove unnecessary xo linting rules

* refactor: turn on some xo linting rules to improve code quality

* fix tests

* Remove invalid urls

* fix merge conflict

* update the upgrade guide

* test if URLs are encoded as UTF-8

* update xo to 0.28.0

* chore: Build before publishing

* v3.0.0-beta.1

* fix lint on test/main.js

Co-authored-by: Richie Bendall <richiebendall@gmail.com>
Co-authored-by: Antoni Kepinski <xxczaki@pm.me>
Co-authored-by: aeb-sia <50743092+aeb-sia@users.noreply.github.com>
Co-authored-by: Nazar Mokrynskyi <nazar@mokrynskyi.com>
Co-authored-by: Steve Moser <contact@stevemoser.org>
Co-authored-by: Erick Calder <e@arix.com>
Co-authored-by: Yaacov Rydzinski <yaacovCR@gmail.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Jimmy Wärting <jimmy@warting.se>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants