-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Previously, server startup worked like this: - `new ApolloServer` - If no gateway, calculate schema and schema derived data immediately - If gateway, kick off gateway.load from the end of the constructor, and if it async-throws, log an error once and make the server kinda broken forever - At various spots in the framework integration code, call (but don't await) the protected `willStart` function, which is an async function that first waits for the gateway to load the schema if necessary and then runs serverWillStart plugin functions; save the Promise returned by calling this. - At request time in the framework integration code, await that Promise. And also, if there's no schema, fail with an error. Now server startup works like this: - ApolloServer represents its state explicitly with a new ServerState - `new ApolloServer` - If no gateway, initialize all the schema-derived state directly like before (though the state now lives inside ServerState) - If gateway, the constructor DOES NOT KICK OFF `gateway.load()` - You can now call `await server.start()` yourself, which will first await `gateway.load` if necessary, and then await all serverWillStart calls. - If you're using `apollo-server` rather than an integration, `server.listen()` will just transparently do this for you; explicit `start()` is just for integrations! - The integration places that used to call willStart now call `server.ensureStarting()` instead which will kick off server.start in the background if you didn't (and log any errors thrown). - The places that used to await promiseWillStart no longer do so; generally right after that code we end up calling `graphqlServerOptions` - `graphqlServerOptions` now awaits `server.ensureStarted` which will start the server if necessary and throw if it threw. The overall change to user experience: - If you're using `apollo-server`, startup errors will cause `listen` to reject; no code changes are necessary. - If you're using an integration you are encouraged to call `await server.start()` yourself immediately after the constructor, which will let you detect startup errors. - But if you don't do that, the server will call `start` itself eventually. When you try to execute your first GraphQL request, `start` will happen if it hasn't already. Also an integration call like `server.applyMiddleware` will initiate a background `start`. If startup fails, the startup error will be logged on *every* failed graphql request, not just the first time like happened before. - If you have your own ApolloServer subclass that calls the protected `willStart` method, it won't work before that method is gone. Consider whether you can eliminate that call by just calling `start`, or perhaps call `ensureStarting` instead. This is close enough to backwards-compatible to be appropriate for a v2 minor release. We are likely to make `start()` required in Apollo Server 3 (other than for `apollo-server`). Also: - Previously we used the deprecated `ApolloServer.schema` field to determine whether to install ApolloServerPluginInlineTrace, which we want to have active by default for federated schemas only. If you're using a gateway, this field isn't actually set at the time that ensurePluginInstantiation reads it. That's basically OK because we don't want to turn on the plugin automatically in the gateway, but in the interest of avoiding use of the deprecated field, I refactored it so that `ApolloServerPluginInlineTrace` is installed by default (ie, if you don't install your own version or install `ApolloServerPluginInlineTraceDisabled`) without checking the schema, and then (if it's installed automatically) it decides whether or not to be active by checking the schema at `serverWillStart` time. - Similarly, schema reporting now throws in its `serverWillStart` if the schema is federated, instead of in `ensurePluginInstantiation`. (This does mean that if you're not using the new `start()` or `apollo-server`, that failure won't make your app fail as fast as if the `ApolloServer` constructor threw.) - Fix some fastify tests that used a fixed listen port to not do that. - I am doing my best to never accidentally run `prettier` on whole files and instead to very carefully select specific blocks of the file to format them several times per minute. Apparently I screwed up once and ran it once on `packages/apollo-server-core/src/ApolloServer.ts`. The ratio of "prettier changes" to "actual changes" in that file is low enough that I'd rather just leave the changes in this PR rather than spending time carefully reverting them. (It's one of the files I work on the most and being able to keep it prettier-clean will be helpful anyway.) - Replace a hacky workaround for the lack of `start` in the op reg tests! - Replace a use of a `Barrier` class I added recently in tests with the `@josephg/resolvable` npm package, which does basically the same thing. Use that package in new tests and in the core state machine itself. - While running tests I found that some test files hung if run separately due to lack of cleanup. I ended up refactoring the cache tests to: - make who is responsible for calling cache.close more consistent - make the Redis client mocks self-contained mocks of the ioredis API instead of starting with an actual ioredis implementation and mocking out some internals - clean up Jest fake timers when a certain test is done I'm not super certain exactly which of these changes fixed the hangs but it does seem better this way. (Specifically I think the fake timer fix, which I did last, is what actually fixed it, but the other changes made it easier for me to reason about what was going on.) Can factor out into another PR if helpful. Fixes #4921. Fixes apollographql/federation#335. TODO: - [ ] Go through all docs and READMEs that have 'FIXME start' and add calls to start. This involves verifying that you can actually do top-level await in the contexts that matter. (eg if it turns out that you really can't call await before you assign a handler in Lambda, that's interesting and may require some other changes to this PR!) - [ ] Actually document start() in the apollo-server reference - [ ] Document start() in all the integrations references - [ ] CHANGELOG - [ ] consider whether removing the protected willStart function is OK
- Loading branch information
Showing
46 changed files
with
816 additions
and
506 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 43 additions & 28 deletions
71
packages/apollo-server-cache-redis/src/__mocks__/ioredis.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,53 @@ | ||
const IORedis = jest.genMockFromModule('ioredis'); | ||
class Redis { | ||
private keyValue = {}; | ||
private timeouts = new Set<NodeJS.Timer>(); | ||
|
||
const keyValue = {}; | ||
async del(key: string) { | ||
delete this.keyValue[key]; | ||
return true; | ||
} | ||
|
||
const deleteKey = key => { | ||
delete keyValue[key]; | ||
return Promise.resolve(true); | ||
}; | ||
async get(key: string) { | ||
if (this.keyValue[key]) { | ||
return this.keyValue[key].value; | ||
} | ||
} | ||
|
||
const getKey = key => { | ||
if (keyValue[key]) { | ||
return Promise.resolve(keyValue[key].value); | ||
async mget(...keys: string[]) { | ||
return keys.map((key) => { | ||
if (this.keyValue[key]) { | ||
return this.keyValue[key].value; | ||
} | ||
}); | ||
} | ||
|
||
return Promise.resolve(undefined); | ||
}; | ||
async set(key, value, type, ttl) { | ||
this.keyValue[key] = { | ||
value, | ||
ttl, | ||
}; | ||
if (ttl) { | ||
const timeout = setTimeout(() => { | ||
this.timeouts.delete(timeout); | ||
delete this.keyValue[key]; | ||
}, ttl * 1000); | ||
this.timeouts.add(timeout); | ||
} | ||
return true; | ||
} | ||
|
||
nodes() { | ||
return []; | ||
} | ||
|
||
const mGetKey = (key, cb) => getKey(key).then(val => [val]); | ||
async flushdb() {} | ||
|
||
const setKey = (key, value, type, ttl) => { | ||
keyValue[key] = { | ||
value, | ||
ttl, | ||
}; | ||
if (ttl) { | ||
setTimeout(() => { | ||
delete keyValue[key]; | ||
}, ttl * 1000); | ||
async quit() { | ||
this.timeouts.forEach((t) => clearTimeout(t)); | ||
} | ||
return Promise.resolve(true); | ||
}; | ||
} | ||
|
||
IORedis.prototype.del.mockImplementation(deleteKey); | ||
IORedis.prototype.get.mockImplementation(getKey); | ||
IORedis.prototype.mget.mockImplementation(mGetKey); | ||
IORedis.prototype.set.mockImplementation(setKey); | ||
// Use the same mock as Redis.Cluster. | ||
(Redis as any).Cluster = Redis; | ||
|
||
export default IORedis; | ||
export default Redis; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.