From e396b2cd4dd1b206f3739bd747f41f69365cb326 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 12 Feb 2021 14:51:27 -0500 Subject: [PATCH] Revert #7276, but test garbage collection of ObservableQuery. Thanks to @francisu for pushing back on the reasoning behind #7276: https://github.com/apollographql/apollo-client/pull/7276#issuecomment-776144217 Thanks to #7661, we now have a system for programmatically testing garbage collection of discarded objects, so we can actually enforce the expectation that the whole ObservableQuery is garbage collected after the last subscriber is removed (which tears down the ObservableQuery and removes it from the QueryManager). --- scripts/memory/tests.js | 69 ++++++++++++++++++++++++++++++++++--- src/core/ObservableQuery.ts | 5 --- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/scripts/memory/tests.js b/scripts/memory/tests.js index 1ef470ff913..077d82d42a0 100644 --- a/scripts/memory/tests.js +++ b/scripts/memory/tests.js @@ -48,9 +48,15 @@ function makeRegistry(callback, reject) { describe("garbage collection", () => { itAsync("should collect client.cache after client.stop()", (resolve, reject) => { + const expectedKeys = new Set([ + "client.cache", + "ObservableQuery", + ]); + const registry = makeRegistry(key => { - assert.strictEqual(key, "client.cache"); - resolve(); + if (expectedKeys.delete(key) && !expectedKeys.size) { + resolve(); + } }, reject); const localVar = makeVar(123); @@ -58,9 +64,13 @@ describe("garbage collection", () => { (function (client) { registry.register(client.cache, "client.cache"); - client.watchQuery({ + const obsQuery = client.watchQuery({ query: gql`query { local }`, - }).subscribe({ + }); + + registry.register(obsQuery, "ObservableQuery"); + + obsQuery.subscribe({ next(result) { assert.deepStrictEqual(result.data, { local: 123, @@ -84,4 +94,55 @@ describe("garbage collection", () => { }), })); }); + + itAsync("should collect ObservableQuery after tear-down", (resolve, reject) => { + const expectedKeys = new Set([ + "ObservableQuery", + ]); + + const registry = makeRegistry(key => { + // Referring to client here should keep the client itself alive + // until after the ObservableQuery is (or should have been) + // collected. Collecting the ObservableQuery just because the whole + // client instance was collected is not interesting. + assert.strictEqual(client instanceof ApolloClient, true); + + if (expectedKeys.delete(key) && !expectedKeys.size) { + resolve(); + } + }, reject); + + const localVar = makeVar(123); + + const client = new ApolloClient({ + cache: new InMemoryCache({ + typePolicies: { + Query: { + fields: { + local() { + return localVar(); + }, + }, + }, + }, + }), + }); + + (function () { + const obsQuery = client.watchQuery({ + query: gql`query { local }`, + }); + + registry.register(obsQuery, "ObservableQuery"); + + const sub = obsQuery.subscribe({ + next(result) { + assert.deepStrictEqual(result.data, { + local: 123, + }); + sub.unsubscribe(); + }, + }); + })(); + }); }); diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 2d2d8f09249..f84dcbf9a88 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -633,11 +633,6 @@ once, rather than every time you call fetchMore.`); delete this.reobserver; } - // Since the user-provided context object can retain arbitrarily large - // amounts of memory, we delete it when the ObservableQuery is torn - // down, to avoid the possibility of memory leaks. - delete this.options.context; - // stop all active GraphQL subscriptions this.subscriptions.forEach(sub => sub.unsubscribe()); this.subscriptions.clear();