fix(routing/http): cap records after filtering#1157
Conversation
The Delegated Routing server applied the records limit before filter-addrs/filter-protocols ran, so records the filters dropped shrank /routing/v1/providers and /routing/v1/peers responses below the configured limit. The server now calls the delegate FindProviders/FindPeers with a limit of 0 (unbounded), reads the result iterator lazily, and applies the cap itself after filtering. A delegate that used the limit to end its walk early will now end it on Close instead. - routing/http/types/iter: add Limit, an iterator that caps another iterator at a fixed number of values - routing/http/server: apply the records limit post-filter; pass 0 to the delegate; update tests and DelegatedRouter docs
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #1157 +/- ##
==========================================
+ Coverage 63.15% 63.21% +0.05%
==========================================
Files 267 268 +1
Lines 26867 26884 +17
==========================================
+ Hits 16969 16994 +25
+ Misses 8173 8163 -10
- Partials 1725 1727 +2
... and 12 files with indirect coverage changes 🚀 New features to boost your workflow:
|
guillaumemichel
left a comment
There was a problem hiding this comment.
This seems like a better approach than ipfs/someguy#149 👍🏻
Would it make sense to use FindProvidersAsync() instead of FindProviders()? It is the more standard way to find provider (go-libp2p interface), and gives caller more control.
FindProvidersAsync(ctx, cid, 0) would allow to cancel the request (cancel context) after we are happy with the results (e.g enough providers with addresses), so that we don't solicit the content router more than strictly needed.
|
Hm.. the goal here (cancel once we have enough, don't over-ask the router) is right, and it's already what this PR does. @guillaumemichel lmk if i misunderstood, but the
Is the idea to lift
So I'd keep |
guillaumemichel
left a comment
There was a problem hiding this comment.
Alright, I was missing a piece of the puzzle in someguy.
Everything looks good, just 2 nits inline
| defer provIter.Close() | ||
|
|
||
| filteredIter := filters.ApplyFiltersToIter(provIter, filterAddrs, filterProtocols) | ||
| providers, err := iter.ReadAllResults(filteredIter) | ||
| var limitedIter iter.ResultIter[types.Record] = iter.Limit(filteredIter, recordsLimit) | ||
| providers, err := iter.ReadAllResults(limitedIter) |
There was a problem hiding this comment.
provIter could be unconditionally closed after iter.ReadAllResults(), which would cancel request on router faster. No need to wait for response to be sent.
| defer provIter.Close() | |
| filteredIter := filters.ApplyFiltersToIter(provIter, filterAddrs, filterProtocols) | |
| providers, err := iter.ReadAllResults(filteredIter) | |
| var limitedIter iter.ResultIter[types.Record] = iter.Limit(filteredIter, recordsLimit) | |
| providers, err := iter.ReadAllResults(limitedIter) | |
| filteredIter := filters.ApplyFiltersToIter(provIter, filterAddrs, filterProtocols) | |
| var limitedIter iter.ResultIter[types.Record] = iter.Limit(filteredIter, recordsLimit) | |
| providers, err := iter.ReadAllResults(limitedIter) | |
| provIter.Close() |
There was a problem hiding this comment.
Or even better to close limitedIter?
| defer peersIter.Close() | ||
|
|
||
| peersIter = filters.ApplyFiltersToPeerRecordIter(peersIter, filterAddrs, filterProtocols) | ||
| filteredIter := filters.ApplyFiltersToPeerRecordIter(peersIter, filterAddrs, filterProtocols) | ||
| var limitedIter iter.ResultIter[*types.PeerRecord] = iter.Limit(filteredIter, recordsLimit) | ||
|
|
||
| peers, err := iter.ReadAllResults(peersIter) | ||
| peers, err := iter.ReadAllResults(limitedIter) |
There was a problem hiding this comment.
Same reasoning as above, no need to keep query longer than required.
| defer peersIter.Close() | |
| peersIter = filters.ApplyFiltersToPeerRecordIter(peersIter, filterAddrs, filterProtocols) | |
| filteredIter := filters.ApplyFiltersToPeerRecordIter(peersIter, filterAddrs, filterProtocols) | |
| var limitedIter iter.ResultIter[*types.PeerRecord] = iter.Limit(filteredIter, recordsLimit) | |
| peers, err := iter.ReadAllResults(peersIter) | |
| peers, err := iter.ReadAllResults(limitedIter) | |
| filteredIter := filters.ApplyFiltersToPeerRecordIter(peersIter, filterAddrs, filterProtocols) | |
| var limitedIter iter.ResultIter[*types.PeerRecord] = iter.Limit(filteredIter, recordsLimit) | |
| peers, err := iter.ReadAllResults(limitedIter) | |
| peersIter.Close() |
There was a problem hiding this comment.
Or even better to close limitedIter?
Problem
The Delegated Routing server applied the records limit before filter-addrs/filter-protocols ran, so records the filters dropped shrank /routing/v1/providers and /routing/v1/peers responses below the configured limit.
This Fix
The server now calls the delegate FindProviders/FindPeers with a limit of 0 (unbounded), reads the result iterator lazily, and applies the cap itself after filtering. A delegate that used the limit to end its walk early will now end it on Close instead.
See it in action in:
Changes