-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: limit concurrent HTTP requests in browser #2304
fix: limit concurrent HTTP requests in browser #2304
Conversation
src/core/runtime/dns-browser.js
Outdated
Object.keys(opts).forEach(prop => { | ||
url += `&${encodeURIComponent(prop)}=${encodeURIComponent(opts[prop])}` | ||
}) | ||
|
||
self.fetch(url, { mode: 'cors' }) | ||
_httpQueue.add(() => fetch(url, { mode: 'cors' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would use https://github.com/sindresorhus/ky (check this discussion ipfs-inactive/js-ipfs-http-client#1059 (comment))
it will simplify the above lines too and you can specific retries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a stab at ky
in 410ac10. Code is much simpler now. Thoughts?
We were using fetch
for preload and dns in browser, however addFromURL
was handled via custom code on top of node-fetch
. I removed custom code around fetch
and used ky-universal
everywhere for consistency.
All of them benefit from retries provided by ky
out of the box.
Question: should we hardcode timeout
and retry
, or go with defaults?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we will probably create an ky instance to be used everywhere but for now just leave it hardcoded
19dcf6c
to
410ac10
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would have preferred a promise first approach like this https://github.com/ipfs/js-ipfs/blob/156125535213836cda58249576f6dbfa2eeac039/src/core/components/resolve.js but i will not block this PR because of it !!
Another suggestion is to use ky.get
and ky.post
to be more explicit and easier to identify the HTTP operation.
7d82d34
to
070e205
Compare
src/core/runtime/dns-browser.js
Outdated
hooks: { | ||
afterResponse: [ | ||
async response => { | ||
const query = new URLSearchParams(new URL(response.url).search).toString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the response doesn't have the searchParams already ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik no, when I checked it only had .url
(string
)
damn ky doesnt support returning a Response from beforeRequest to skip the http request entirely and fetch from cache. |
Preload tests fail in browser because preload is enabled in browser context by default and while js-ipfs tests disable preload in tests, interface-ipfs-core does not. This means interface-ipfs-core tests trigger dozens of slow/hanging requests to I believe we should detect js-ipfs is running in test environment ( |
This basically is telling us to "really" solve this problem lol, with http2, http cache and making refs endpoint faster Still if you want to do this workaround make sure NODE_ENV is properly set in browsers and electron if not file an issue in aegir and i'll fix it. |
We really don't need preload calls in tests other than tests of preload itself. 76ae81c disables preload by default when
Remaining thing to fix is
|
The problem was that
As a temporary workaround, 8f41347 fixes the problem in userland by assigning We should solve this upstream somewhow. So far options I see are:
@hugomrdias thoughts? |
I would like to test with the new electron 6 before deciding |
@hugomrdias same errors in 6.0.0: |
8f41347
to
6fa8f88
Compare
Updates
On
|
name: 'addFromURL', | |
reason: 'Not designed to run in the browser' |
This is because karma running interface-js-ipfs-core
tests in browser context is unable to start HTTP(S) server. We may be able to make it work with something like karma-server-side, but it sounds like something we need to add to interface-js-ipfs-core
or maybe aegir
directly.
@hugomrdias any thoughts on this?
When we have addFromURL
tests working in web browser, then we can revisit electron errors (we probably need to rewrite node-centric tests, and the discrepancies will not matter anymore).
This switches to 0.2.x versions of delegate modules which work correctly with js-libp2p + wip [1] fix for js-ipfs that caches DNS records for 1 minute, greatly reducing the HTTP request overhead to remote APIs. [1]: ipfs/js-ipfs#2304
* feat(brave): delegated peers and content routing This enables delegated routers in embedded js-ipfs running in Brave. Coupled with preload, this gives us basic file sharing functionality back, until we have real p2p transport, native DHT etc. * fix: callback-based delgates + DNS caching This switches to 0.2.x versions of delegate modules which work correctly with js-libp2p + wip [1] fix for js-ipfs that caches DNS records for 1 minute, greatly reducing the HTTP request overhead to remote APIs. [1]: ipfs/js-ipfs#2304
for reference hugomrdias: ok so we need to find a way to test https js-ipfs/test/core/interface.spec.js Line 67 in fc76875
and skip inside the tests with ipfs-utils/src/env.js do you agree ? lidel: yup |
@lidel what's the current status of this PR - still draft? |
@alanshaw still a draft. Remaining work here is to figure out I took a break from this PR after my vacation, but will rebase and try to get it ready for review this week. |
6fa8f88
to
25d3144
Compare
Just incase this is helpful, you could spin up a server to serve some content via aegir hooks: Line 27 in c67b8a5
Maybe the server accepts a querystring that is the fixture you want it to return? Then in tests you can call it and assert the correct content was added. Won't work with HTTPS though, but only HTTP is better than nothing. |
we need this version for isTest check License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
hmm, tests are failing in windows and both browsers and electron. Something is not right here! The |
oh boy, this PR is cursed 👀 @alanshaw electron was a random one, but remaining Windows issue was triggered by parallel |
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
493a00b
to
11ab304
Compare
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
fade339
to
ddd49ce
Compare
Resolved conflicts + re-enabled tests for #2379 |
This should be ready to merge now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of small nits but this is great.
const addFromURL = async (url, opts = {}) => { | ||
const res = await ky.get(url) | ||
const path = decodeURIComponent(new URL(res.url).pathname.split('/').pop()) | ||
const content = Buffer.from(await res.arrayBuffer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That day is today. Well, yesterday. Since we merged #2379 you should be able to pass iterables to ipfs.add
.
throw new Error(response.Message) | ||
} | ||
|
||
module.exports = (fqdn, opts = {}, cb) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be promise-first please? E.g. make this an async method but export a nodeified version. This will make future refactors to remove callbacks easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its already done? the async method is inside:
const resolveDnslink = async (fqdn, opts = {}) => {
...
}
return nodeify(resolveDnslink(fqdn, opts), cb)
when we refactor to remove callbacks we just remove the wrapper.
Let me know if this should be handled some other way.
License: MIT Signed-off-by: Marcin Rataj <lidel@lidel.org>
This switch to js-ipfs before PR-2379 ipfs/js-ipfs#2379 switched ipfs.add to ipfs._addAsyncIterator and it broke /api/v0/add exposed by embedded js-ipfs in Brave. It seems old polyfills are no longer enough. Real fix requires more time to investigate, so for now we switch to version before js-ipfs/PR-2379. Used js-ipfs commit is from ipfs/js-ipfs#2304 before it was rebased on top of master after PR-2379, making it the last safe version. Real fix will be tracked in #757
I'm encountering this problem in latest |
Can you explain further ? this was a problem in js-ipfs not http-client |
@hugomrdias I was playing around with orbitdb + ipfs-http-client in web browser (connected to my local go-ipfs). I found that when I subscribe to a lot of datastores, orbitdb's periodic fetches / pubsub on each datastore effectively use up the concurrent HTTP request limit and stall the entire pipeline of requests, just like what's shown in the screenshot in the PR. This behavior seems to be a result from https://github.com/ipfs-shipyard/ipfs-pubsub-1on1. When I saw this PR, I thought this might be something that addresses my issue, but you are right - I missed that this PR was for js-ipfs. It wouldn't make much sense for ipfs-http-client in fact. |
can you make another issue with further info maybe a repo we can clone please ? |
I've decided to drop OrbitDB for now, so maybe won't see the issue anymore. But if I manage to reproduce it in a consistent manner will log a separate issue like you suggested. Thanks! |
This PR solves request floods like this:
It adds a limit of concurrent HTTP requests sent to remote API
by dns and preload calls when running in web browser contexts.
Browsers limit connections per host (~6). This change mitigates the problem of expensive and long running calls of one type exhausting connection pool for other uses.
This is similar to problems described in libp2p/js-libp2p-delegated-content-routing#12
This PR additionally limits the number of DNS lookup calls by introducing time-bound cache with eviction rules following what browser already do.
TODO
/api/v0/dns
in browserprocess.env.NODE_ENV === 'test'
addFromURL
testscc ipfs/ipfs-companion#716