From 36bc08ab942d0a4113178f1c131c9e7024f0c995 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Tue, 18 Oct 2016 02:08:29 -0700 Subject: [PATCH] feat(improved-cache): makes the adapter/serializer instance cache faster. Reduces the number of lookups, sets, expense of detection, allocations for fallbacks. Also includes additional internalModel cleanup and backburner/state machine optimizations, and deprecates store.lookupAdater and store.lookupSerializer --- addon/-private/system/store.js | 71 +++------- .../system/store/container-instance-cache.js | 128 +++++++++++------- tests/helpers/store.js | 2 +- 3 files changed, 101 insertions(+), 100 deletions(-) diff --git a/addon/-private/system/store.js b/addon/-private/system/store.js index 5358148d35b..085860f770d 100644 --- a/addon/-private/system/store.js +++ b/addon/-private/system/store.js @@ -4,7 +4,7 @@ import Ember from 'ember'; import Model from 'ember-data/model'; -import { instrument, assert, warn, runInDebug } from "ember-data/-private/debug"; +import { instrument, assert, deprecate, warn, runInDebug } from "ember-data/-private/debug"; import _normalizeLink from "ember-data/-private/system/normalize-link"; import normalizeModelName from "ember-data/-private/system/normalize-model-name"; import { InvalidError } from 'ember-data/adapters/errors'; @@ -97,7 +97,6 @@ const { normalize, peekAll, peekRecord, - retrieveManagedInstance, serializerFor, typeMapFor, typeMapFor_allocate @@ -116,7 +115,6 @@ const { 'normalize', 'peekAll', 'peekRecord', - 'retrieveManagedInstance', 'serializerFor', 'typeMapFor', 'typeMapFor_allocate' @@ -210,7 +208,8 @@ Store = Service.extend({ store: this }); this._pendingSave = []; - this._instanceCache = new ContainerInstanceCache(getOwner(this)); + this._instanceCache = new ContainerInstanceCache(getOwner(this), this); + //Used to keep track of all the find requests that need to be coalesced this._pendingFetch = Map.create(); }, @@ -267,13 +266,11 @@ Store = Service.extend({ @return DS.Adapter */ defaultAdapter: Ember.computed('adapter', function() { - var adapter = get(this, 'adapter'); + let adapter = get(this, 'adapter'); assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name', typeof adapter === 'string'); - adapter = this.retrieveManagedInstance('adapter', adapter); - - return adapter; + return this.adapterFor(adapter); }), // ..................... @@ -2456,7 +2453,9 @@ Store = Service.extend({ assert("You need to pass a model name to the store's adapterFor method", isPresent(modelName)); assert(`Passing classes to store.adapterFor has been removed. Please pass a dasherized string instead of ${Ember.inspect(modelName)}`, typeof modelName === 'string'); - return this.lookupAdapter(modelName); + let normalizedModelName = normalizeModelName(modelName); + + return this._instanceCache.get('adapter', normalizedModelName); }, _adapterRun(fn) { @@ -2493,60 +2492,34 @@ Store = Service.extend({ assert("You need to pass a model name to the store's serializerFor method", isPresent(modelName)); assert(`Passing classes to store.serializerFor has been removed. Please pass a dasherized string instead of ${Ember.inspect(modelName)}`, typeof modelName === 'string'); - var fallbacks = [ - 'application', - this.adapterFor(modelName).get('defaultSerializer'), - '-default' - ]; + let normalizedModelName = normalizeModelName(modelName); - var serializer = this.lookupSerializer(modelName, fallbacks); - return serializer; - }, - - /** - Retrieve a particular instance from the - container cache. If not found, creates it and - placing it in the cache. - - Enabled a store to manage local instances of - adapters and serializers. - - @method retrieveManagedInstance - @private - @param {String} modelName the object modelName - @param {String} name the object name - @param {Array} fallbacks the fallback objects to lookup if the lookup for modelName or 'application' fails - @return {Ember.Object} - */ - retrieveManagedInstance(type, modelName, fallbacks) { - heimdall.increment(retrieveManagedInstance); - var normalizedModelName = normalizeModelName(modelName); - - var instance = this._instanceCache.get(type, normalizedModelName, fallbacks); - set(instance, 'store', this); - return instance; + return this._instanceCache.get('serializer', normalizedModelName); }, lookupAdapter(name) { - return this.retrieveManagedInstance('adapter', name, this.get('_adapterFallbacks')); + deprecate(`Use of lookupAdapter is deprecated, use adapterFor instead.`, { + id: 'ds.store.lookupAdapter', + until: '3.0' + }); + return this.adapterFor(name); }, - _adapterFallbacks: Ember.computed('adapter', function() { - var adapter = this.get('adapter'); - return ['application', adapter, '-json-api']; - }), - - lookupSerializer(name, fallbacks) { - return this.retrieveManagedInstance('serializer', name, fallbacks); + lookupSerializer(name) { + deprecate(`Use of lookupSerializer is deprecated, use serializerFor instead.`, { + id: 'ds.store.lookupSerializer', + until: '3.0' + }); + return this.serializerFor(name); }, willDestroy() { this._super(...arguments); this.recordArrayManager.destroy(); + this._instanceCache.destroy(); this.unloadAll(); } - }); function deserializeRecordId(store, key, relationship, id) { diff --git a/addon/-private/system/store/container-instance-cache.js b/addon/-private/system/store/container-instance-cache.js index a5127af3238..08c8cf1e0c0 100644 --- a/addon/-private/system/store/container-instance-cache.js +++ b/addon/-private/system/store/container-instance-cache.js @@ -1,7 +1,15 @@ /* global heimdall */ import Ember from 'ember'; import EmptyObject from "ember-data/-private/system/empty-object"; -const assign = Ember.assign || Ember.merge; +const { set } = Ember; + +const { + __get, + _instanceFor +} = heimdall.registerMonitor('system.store.container-instance-cache', + '__get', + '_instanceFor' +); /* * The `ContainerInstanceCache` serves as a lazy cache for looking up @@ -12,71 +20,86 @@ const assign = Ember.assign || Ember.merge; * when the preferred lookup fails. For example, say you try to look up `adapter:post`, * but there is no entry (app/adapters/post.js in EmberCLI) for `adapter:post` in the registry. * - * The `fallbacks` array passed will then be used; the first entry in the fallbacks array - * that exists in the container will then be cached for `adapter:post`. So, the next time you - * look up `adapter:post`, you'll get the `adapter:application` instance (or whatever the fallback - * was if `adapter:application` doesn't exist). + * When an adapter or serializer is unfound, getFallbacks will be invoked with the current namespace + * ('adapter' or 'serializer') and the 'preferredKey' (usually a modelName). The method should return + * an array of keys to check against. + * + * The first entry in the fallbacks array that exists in the container will then be cached for + * `adapter:post`. So, the next time you look up `adapter:post`, you'll get the `adapter:application` + * instance (or whatever the fallback was if `adapter:application` doesn't exist). * * @private * @class ContainerInstanceCache * */ -export default function ContainerInstanceCache(owner) { - this._owner = owner; - this._cache = new EmptyObject(); -} +export default class ContainerInstanceCache { + constructor(owner, store) { + this._owner = owner; + this._store = store; + this._namespaces = { + adapter: new EmptyObject(), + serializer: new EmptyObject() + }; + } -const { - __get, - instanceFor -} = heimdall.registerMonitor('system.store.container-instance-cache', - 'get', - 'instanceFor' -); + get(namespace, preferredKey) { + heimdall.increment(__get); + let cache = this._namespaces[namespace]; -ContainerInstanceCache.prototype = new EmptyObject(); + if (cache[preferredKey]) { + return cache[preferredKey]; + } -assign(ContainerInstanceCache.prototype, { - get(type, preferredKey, fallbacks) { - heimdall.increment(__get); - let cache = this._cache; - let preferredLookupKey = `${type}:${preferredKey}`; + let preferredLookupKey = `${namespace}:${preferredKey}`; - if (!(preferredLookupKey in cache)) { - let instance = this.instanceFor(preferredLookupKey) || this._findInstance(type, fallbacks); - if (instance) { - cache[preferredLookupKey] = instance; - } + let instance = this._instanceFor(preferredLookupKey) || this._findInstance(namespace, this._fallbacksFor(namespace, preferredKey)); + if (instance) { + cache[preferredKey] = instance; + set(instance, 'store', this._store); } - return cache[preferredLookupKey]; - }, - _findInstance(type, fallbacks) { + return cache[preferredKey]; + } + + _fallbacksFor(namespace, preferredKey) { + if (namespace === 'adapter') { + return ['application', this._store.get('adapter'), '-json-api']; + } + + // serializer + return [ + 'application', + this.get('adapter', preferredKey).get('defaultSerializer'), + '-default' + ]; + } + + _findInstance(namespace, fallbacks) { + let cache = this._namespaces[namespace]; + for (let i = 0, length = fallbacks.length; i < length; i++) { let fallback = fallbacks[i]; - let lookupKey = `${type}:${fallback}`; - let instance = this.instanceFor(lookupKey); - if (instance) { - return instance; + if (cache[fallback]) { + return cache[fallback]; } - } - }, - instanceFor(key) { - heimdall.increment(instanceFor); - let cache = this._cache; - if (!cache[key]) { - let instance = this._owner.lookup(key); + let lookupKey = `${namespace}:${fallback}`; + let instance = this._instanceFor(lookupKey); + if (instance) { - cache[key] = instance; + cache[fallback] = instance; + return instance; } } - return cache[key]; - }, + } - destroy() { - let cache = this._cache; + _instanceFor(key) { + heimdall.increment(_instanceFor); + return this._owner.lookup(key); + } + + destroyCache(cache) { let cacheEntries = Object.keys(cache); for (let i = 0, length = cacheEntries.length; i < length; i++) { @@ -86,12 +109,17 @@ assign(ContainerInstanceCache.prototype, { cacheEntry.destroy(); } } - this._owner = null; - }, + } - constructor: ContainerInstanceCache, + destroy() { + this.destroyCache(this._namespaces.adapter); + this.destroyCache(this._namespaces.serializer); + this._namespaces = null; + this._store = null; + this._owner = null; + } toString() { return 'ContainerInstanceCache'; } -}); +} diff --git a/tests/helpers/store.js b/tests/helpers/store.js index a2a4ef22545..4b87186e428 100644 --- a/tests/helpers/store.js +++ b/tests/helpers/store.js @@ -65,7 +65,7 @@ export default function setupStore(options) { return env; } -export {setupStore}; +export { setupStore }; export function createStore(options) { return setupStore(options).store;