-
Notifications
You must be signed in to change notification settings - Fork 257
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
Fix fetch group processing handling of dependencies #2622
Fix fetch group processing handling of dependencies #2622
Conversation
🦋 Changeset detectedLatest commit: 847e95a The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
👷 Deploy request for apollo-federation-docs pending review.Visit the deploys page to approve it
|
87292ae
to
4b4e0a4
Compare
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
4b4e0a4
to
359f410
Compare
query-planner-js/src/buildPlan.ts
Outdated
for (const [g, edges] of r1) { | ||
const newEdges = this.mergeRemaingsAndRemoveIfFound(g, edges, r2); | ||
if (newEdges.length == 0) { | ||
toHandle.push(g); | ||
readyToHandle.push(g); | ||
} else { | ||
unhandled.push([g, newEdges]) | ||
} | ||
} | ||
unhandled.push(...r2); |
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.
Bit confused here, is there a reason this
unhandled.push(...r2);
isn't
for (const [g, edges] of r2) {
if (edges.length == 0) {
readyToHandle.push(g);
} else {
unhandled.push([g, edges])
}
}
instead?
It would seem that if processGroup()
returned an unhandled
where some of its groups had no edges, then those groups are "ready to handle". Or at least with the current code, since processGroups()
loops over multiple/n
groups, the last group's zero-edge unhandled
s won't be considered "ready to handle", while the previous n-1
groups' will.
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.
You're right this was fishy here. The intend though was that UnhandledGroups
would never contain group whose "edges" are empty (both methods that modify it make sure to remove groups as soon as their edges are all empty). But the "bug" was indeed that processGroup
could "generate" an UnhandledGroups
with "zero-edges" groups and that's where the code was confused. I've addressed in the small refactor I did (it now does genuinely avoid created zero-edge entries in UnhandledGroups
, well, at least unless I brain-farted again obviously).
query-planner-js/src/buildPlan.ts
Outdated
const [canHandle, newRemaining] = this.mergeRemainings(remainingNext, unhandled); | ||
remainingNext = newRemaining; | ||
nextGroups = canHandle.concat(next); | ||
remainingNext = this.mergeRemainings(remainingNext, unhandled, next); |
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'm confused about the purpose of this call to mergeRemainings()
.
My understanding is:
processGroups()
is passedremainingNext
.- Along the way,
processGroups()
repeatedly merges itsremainingNext
with theunhandled
s it receives fromprocessGroup()
(moving a group tonext
whenever it gets down to zero edges). It doesn't modify the originalremainingNext
, and makes copies instead. processGroups()
returns the newremainingNext
along withnext
.- Then on this line, we merge the old
remainingNext
with the newremainingNext
.
It seems like this merge might add back groups to remainingNext
that were transferred over to next
in processGroups()
, which feels like a bug.
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.
You're right, this was unnecessary (and probably dangerous).
The overall code around this was admittedly a bit confusing, so I did a small refactor to hopefully clear things up a bit. It should be somewhat equivalent to what we have before (save the real bug you rightfully pointed in the previous comment) but I think encapsulate things a tad better/is more explicit. It's probably not perfect, I think this processing code could maybe be cleaned up som more, but I'll leave that for later (started a slightly bigger refactor and messed it up, so figured I should leave that separate from this bug-fixing).
query-planner-js/src/buildPlan.ts
Outdated
readyToHandle: FetchGroup[], | ||
): UnhandledGroups { | ||
const unhandled: UnhandledGroups = []; | ||
for (const current of currentUnhandled) { |
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.
minor nit: To be consistent with mergeRemainings()
/make code more clear, could you change current
to e.g. [g, edges]
? (edges
is more clear than current[1]
)
f975062
to
ba2af08
Compare
The code that process `FetchGroup` in dependency order has a bug that, for some kind of dependencies, can finish with some group not handled, even though they could and should have been. In practice, this shows itself by failing the post-processing assertion that double-check that no groups are left unhandled.
ba2af08
to
3c5719c
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.
Thanks for making the changes! There's some comments below, but nothing really blocking this PR from merging.
const groupIsOnlyParentOfAllChildren = children.every(g => g.parents().length === 1); | ||
if (groupIsOnlyParentOfAllChildren) { | ||
const state = ProcessingState.forChildrenOfProcessedGroup(group, children); | ||
if (state.next.length > 0) { |
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.
Note that there's a logic change here.
Before this change, this if
block executes only if all children have exactly one parent. After this change, this if
block executes if at least one child has exactly one parent.
This might be fine/intentional, given that you've also swapped out children
down below for state.next
. Though in that case, you should update the comment below to say e.g. ready children
instead of just children
.
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.
Yes, sorry for not pointing out that change myself. That was intentional: the old code was more restrictive than made sense and I think I made that initially out of convenience, but it wasn't hard to "fix" that as I refactored, so did it. I did updated the comment though, good point.
@@ -2473,86 +2582,55 @@ class FetchDependencyGraph { | |||
deferredGroups: allDeferredGroups, | |||
} = this.processRootMainGroups({ |
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.
Note that because processRootMainGroups()
doesn't take the old state
/return a new state
, presumably when it eventually calls updateForProcessedGroups()
for the state
it internally creates, it won't alter the separate state
in the upstream processGroups()
call. This was the case before this PR too though, so it's probably fine to punt that to another PR if you'd like. (It might also be the case this was intentional.)
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.
You know what, you're right. I hesitating changing it and for some reason didn't do it, but there is probably case where that could be a genuine issue, so took the liberty to fix this quickly (it's cleaner if anything anyway).
7848ca0
to
e24b631
Compare
e24b631
to
847e95a
Compare
The code that process
FetchGroup
in dependency order has a bug that, for some types of dependencies, can finish with some group not handled, even though they could and should have been.In practice, this shows itself by failing the post-processing assertion that double-check that no groups are left unhandled.
The error message when this triggered looks like
Root groups X should have no remaining groups unhandled (...)
(whereX
is a dump of the fetch groups involved and so this can be large and hard to parse, but basically the assertion error starts with "Root groups").