Skip to content

Commit ddce036

Browse files
(AS4 + #6398): Usage reporting: don't throw errors if willResolveField is called "late" (#7747)
Porting #6398 from AS3. This fix was unintentionally left out of AS4. Fixes #7746 The minimum version of `graphql` officially supported by Apollo Server 4 as a peer dependency, v16.6.0, contains a [serious bug that can crash your Node server](graphql/graphql-js#3528). This bug is fixed in the immediate next version, `graphql@16.7.0`, and we **strongly encourage you to upgrade your installation of `graphql` to at least v16.7.0** to avoid this bug. (For backwards compatibility reasons, we cannot change Apollo Server 4's minimum peer dependency, but will change it when we release Apollo Server 5.) Apollo Server 4 contained a particular line of code that makes triggering this crashing bug much more likely. This line was already removed in Apollo Server v3.8.2 (see #6398) but the fix was accidentally not included in Apollo Server 4. We are now including this change in Apollo Server 4, which will **reduce** the likelihood of hitting this crashing bug for users of `graphql` v16.6.0. That said, taking this `@apollo/server` upgrade **does not prevent** this bug from being triggered in other ways, and the real fix to this crashing bug is to upgrade `graphql`. --------- Co-authored-by: David Glasser <glasser@apollographql.com>
1 parent 1849cc9 commit ddce036

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

.changeset/late-coats-fry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@apollo/server': patch
3+
---
4+
5+
The minimum version of `graphql` officially supported by Apollo Server 4 as a peer dependency, v16.6.0, contains a [serious bug that can crash your Node server](https://github.com/graphql/graphql-js/issues/3528). This bug is fixed in the immediate next version, `graphql@16.7.0`, and we **strongly encourage you to upgrade your installation of `graphql` to at least v16.7.0** to avoid this bug. (For backwards compatibility reasons, we cannot change Apollo Server 4's minimum peer dependency, but will change it when we release Apollo Server 5.)
6+
7+
Apollo Server 4 contained a particular line of code that makes triggering this crashing bug much more likely. This line was already removed in Apollo Server v3.8.2 (see #6398) but the fix was accidentally not included in Apollo Server 4. We are now including this change in Apollo Server 4, which will **reduce** the likelihood of hitting this crashing bug for users of `graphql` v16.6.0. That said, taking this `@apollo/server` upgrade **does not prevent** this bug from being triggered in other ways, and the real fix to this crashing bug is to upgrade `graphql`.

docs/source/migration.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ If you're using Node.js 12, upgrade your runtime before upgrading to Apollo Serv
379379

380380
### `graphql`
381381

382-
Apollo Server has a peer dependency on [`graphql`](https://www.npmjs.com/package/graphql) (the core JS GraphQL implementation). Apollo Server 4 supports `graphql` v16.6.0 and later. (Apollo Server 3 supports `graphql` v15.3.0 through v16.)
382+
Apollo Server has a peer dependency on [`graphql`](https://www.npmjs.com/package/graphql) (the core JS GraphQL implementation). Apollo Server 4 supports `graphql` v16.6.0 and later, but we *strongly* recommend using at least v16.7.0 due to a [bug in `graphql`](https://github.com/graphql/graphql-js/issues/3528) which can crash your server. (Apollo Server 3 supports `graphql` v15.3.0 through v16.)
383383

384384
If you're using an older version of `graphql`, upgrade it to a supported version before upgrading to Apollo Server 4.
385385

packages/server/src/plugin/traceTreeBuilder.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,49 @@ export class TraceTreeBuilder {
8585
throw internalError('willResolveField called before startTiming!');
8686
}
8787
if (this.stopped) {
88-
throw internalError('willResolveField called after stopTiming!');
88+
// We've been stopped, which means execution is done... and yet we're
89+
// still resolving more fields? How can that be? Shouldn't we throw an
90+
// error or something?
91+
//
92+
// Well... we used to do exactly that. But this "shouldn't happen" error
93+
// showed up in practice! Turns out that graphql-js can actually continue
94+
// to execute more fields indefinitely long after `execute()` resolves!
95+
// That's because parallelism on a selection set is implemented using
96+
// `Promise.all`, and as soon as one of its arguments (ie, one field)
97+
// throws an error, the combined Promise resolves, but there's no
98+
// "cancellation" of the Promises that are the other arguments to
99+
// `Promise.all`. So the code contributing to those Promises keeps on
100+
// chugging away indefinitely.
101+
//
102+
// Concrete example: let’s say you have
103+
//
104+
// { x y { ARBITRARY_SELECTION_SET } }
105+
//
106+
// where x has a non-null return type, and x and y both have resolvers
107+
// that return Promises. And let’s say that x returns a Promise that
108+
// rejects (or resolves to null). What this means is that we’re going to
109+
// eventually end up with `data: null` (nothing under y will actually
110+
// matter), but graphql-js execution will continue running whatever is
111+
// under ARBITRARY_SELECTION_SET without any sort of short circuiting. In
112+
// fact, the Promise returned from execute itself can happily resolve
113+
// while execution is still chugging away on an arbitrary amount of fields
114+
// under that ARBITRARY_SELECTION_SET. There’s no way to detect from the
115+
// outside "all the execution related to this operation is done", nor to
116+
// "short-circuit" execution so that it stops going.
117+
//
118+
// So, um. We will record any field whose execution we manage to observe
119+
// before we "stop" the TraceTreeBuilder (whether it is one that actually
120+
// ends up in the response or whether it gets thrown away due to null
121+
// bubbling), but if we get any more fields afterwards, we just ignore
122+
// them rather than throwing a confusing error.
123+
//
124+
// (That said, the error we used to throw here generally was hidden
125+
// anyway, for the same reason: it comes from a branch of execution that
126+
// ends up not being included in the response. But
127+
// https://github.com/graphql/graphql-js/pull/3529 means that this
128+
// sometimes crashed execution anyway. Our error never caught any actual
129+
// problematic cases, so keeping it around doesn't really help.)
130+
return () => {};
89131
}
90132

91133
const path = info.path;

0 commit comments

Comments
 (0)