Skip to content

Commit

Permalink
Revert #7276, but test garbage collection of ObservableQuery.
Browse files Browse the repository at this point in the history
Thanks to @francisu for pushing back on the reasoning behind #7276:
#7276 (comment)

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).
  • Loading branch information
benjamn committed Feb 12, 2021
1 parent 1dbff98 commit e396b2c
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 9 deletions.
69 changes: 65 additions & 4 deletions scripts/memory/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,29 @@ 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);

(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,
Expand All @@ -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();
},
});
})();
});
});
5 changes: 0 additions & 5 deletions src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit e396b2c

Please sign in to comment.