Skip to content

Commit

Permalink
Ensure adapters and serializers are destroyed upon store destruction. (
Browse files Browse the repository at this point in the history
…emberjs#7020)

Prior to this change, adapters and serializers were never destroyed.
Commonly (and anecdotally) this is "fine" (heck this is the first report
of the issue in years!), but it is pretty common for adapters to do
async of their own (e.g. tracking adapter responses, exponential
backoffs, etc) and without `.destroy()` being called on them there is no
way for those adapters/serializer to ensure that async is canceled
appropriately.
  • Loading branch information
rwjblue authored and Eric Kelly committed Mar 6, 2020
1 parent c22c51c commit 1cd4496
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 5 deletions.
30 changes: 30 additions & 0 deletions packages/-ember-data/tests/integration/store/adapter-for-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { setupTest } from 'ember-qunit';
import { assign } from '@ember/polyfills';
import { run } from '@ember/runloop';

import { module, test } from 'qunit';
import Store from 'ember-data/store';

Expand Down Expand Up @@ -297,4 +300,31 @@ module('integration/store - adapterFor', function(hooks) {
);
assert.ok(jsonApiAdapter === adapter, 'We fell back to the -json-api adapter instance for the per-type adapter');
});

test('adapters are destroyed', async function(assert) {
let { owner } = this;
let didInstantiate = false;
let didDestroy = false;

class AppAdapter extends TestAdapter {
didInit() {
didInstantiate = true;
}

destroy() {
didDestroy = true;
}
}

owner.register('adapter:application', AppAdapter);

let adapter = store.adapterFor('application');

assert.ok(adapter instanceof AppAdapter, 'precond - We found the correct adapter');
assert.ok(didInstantiate, 'precond - We instantiated the adapter');

run(store, 'destroy');

assert.ok(didDestroy, 'adapter was destroyed');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { setupTest } from 'ember-qunit';
import { run } from '@ember/runloop';

import { module, test } from 'qunit';
import Store from 'ember-data/store';

Expand Down Expand Up @@ -410,4 +412,31 @@ module('integration/store - serializerFor', function(hooks) {
'We fell back to the -default serializer instance for the adapter defaultSerializer'
);
});

test('serializers are destroyed', async function(assert) {
let { owner } = this;
let didInstantiate = false;
let didDestroy = false;

class AppSerializer extends TestSerializer {
didInit() {
didInstantiate = true;
}

destroy() {
didDestroy = true;
}
}

owner.register('serializer:application', AppSerializer);

let serializer = store.serializerFor('application');

assert.ok(serializer instanceof AppSerializer, 'precond - We found the correct serializer');
assert.ok(didInstantiate, 'precond - We instantiated the serializer');

run(store, 'destroy');

assert.ok(didDestroy, 'serializer was destroyed');
});
});
16 changes: 14 additions & 2 deletions packages/adapter/addon/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
*
*
*
@module @ember-data/adapter
@main @ember-data/adapter
*/
Expand Down Expand Up @@ -669,6 +669,18 @@ export default EmberObject.extend({
shouldBackgroundReloadAll(store, snapshotRecordArray) {
return true;
},

/**
In some situations the adapter may need to perform cleanup when destroyed,
that cleanup can be done in `destroy`.
If not implemented, the store does not inform the adapter of destruction.
@method destroy [OPTIONAL]
@public
@optional
*/
destroy: null
});

export { BuildURLMixin } from './-private';
22 changes: 19 additions & 3 deletions packages/store/addon/-private/system/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2920,14 +2920,30 @@ const Store = Service.extend({
return serializer;
},

destroy() {
// enqueue destruction of any adapters/serializers we have created
for (let adapterName in this._adapterCache) {
let adapter = this._adapterCache[adapterName];
if (typeof adapter.destroy === 'function') {
adapter.destroy();
}
}

for (let serializerName in this._serializerCache) {
let serializer = this._serializerCache[serializerName];
if (typeof serializer.destroy === 'function') {
serializer.destroy();
}
}

return this._super();
},

willDestroy() {
this._super(...arguments);
this._pushedInternalModels = null;
this.recordArrayManager.destroy();

this._adapterCache = null;
this._serializerCache = null;

this.unloadAll();

if (DEBUG) {
Expand Down

0 comments on commit 1cd4496

Please sign in to comment.