Skip to content

Commit

Permalink
Reduce CPU usage loading large numbers of items (#1768)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntharo committed May 8, 2022
1 parent a0daee7 commit 448e2be
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 27 deletions.
52 changes: 25 additions & 27 deletions src/BackendConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import * as utils from './utils.js';
import baseLogger from './logger.js';
import EventEmitter from './EventEmitter.js';

function remove(arr, what) {
let found = arr.indexOf(what);

while (found !== -1) {
arr.splice(found, 1);
found = arr.indexOf(what);
}
function removePending(q, name) {
delete q.pending[name];
q.pendingCount--;
}

class Connector extends EventEmitter {
Expand All @@ -35,10 +31,10 @@ class Connector extends EventEmitter {

queueLoad(languages, namespaces, options, callback) {
// find what needs to be loaded
const toLoad = [];
const pending = [];
const toLoadLanguages = [];
const toLoadNamespaces = [];
const toLoad = {};
const pending = {};
const toLoadLanguages = {};
const toLoadNamespaces = {};

languages.forEach((lng) => {
let hasAllNamespaces = true;
Expand All @@ -51,35 +47,36 @@ class Connector extends EventEmitter {
} else if (this.state[name] < 0) {
// nothing to do for err
} else if (this.state[name] === 1) {
if (pending.indexOf(name) < 0) pending.push(name);
if (pending[name] !== undefined) pending[name] = true;
} else {
this.state[name] = 1; // pending

hasAllNamespaces = false;

if (pending.indexOf(name) < 0) pending.push(name);
if (toLoad.indexOf(name) < 0) toLoad.push(name);
if (toLoadNamespaces.indexOf(ns) < 0) toLoadNamespaces.push(ns);
pending[name] = true;
toLoad[name] = true;
toLoadNamespaces[ns] = true;
}
});

if (!hasAllNamespaces) toLoadLanguages.push(lng);
if (!hasAllNamespaces) toLoadLanguages[lng] = true;
});

if (toLoad.length || pending.length) {
if (Object.keys(toLoad).length || Object.keys(pending).length) {
this.queue.push({
pending,
pendingCount: Object.keys(pending).length,
loaded: {},
errors: [],
callback,
});
}

return {
toLoad,
pending,
toLoadLanguages,
toLoadNamespaces,
toLoad: Object.keys(toLoad),
pending: Object.keys(pending),
toLoadLanguages: Object.keys(toLoadLanguages),
toLoadNamespaces: Object.keys(toLoadNamespaces),
};
}

Expand All @@ -103,17 +100,18 @@ class Connector extends EventEmitter {
// callback if ready
this.queue.forEach((q) => {
utils.pushPath(q.loaded, [lng], ns);
remove(q.pending, name);
removePending(q, name);

if (err) q.errors.push(err);

if (q.pending.length === 0 && !q.done) {
if (q.pendingCount === 0 && !q.done) {
// only do once per loaded -> this.emit('loaded', q.loaded);
Object.keys(q.loaded).forEach((l) => {
if (!loaded[l]) loaded[l] = [];
if (q.loaded[l].length) {
q.loaded[l].forEach((ns) => {
if (loaded[l].indexOf(ns) < 0) loaded[l].push(ns);
if (!loaded[l]) loaded[l] = {};
const loadedKeys = Object.keys(loaded[l]);
if (loadedKeys.length) {
loadedKeys.forEach((ns) => {
if (loadedKeys[ns] !== undefined) loaded[l][ns] = true;
});
}
});
Expand Down
39 changes: 39 additions & 0 deletions test/backend/backendConnector.perf.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import BackendConnector from '../../src/BackendConnector.js';
import BackendMock from './backendMock.js';
import Interpolator from '../../src/Interpolator.js';
import ResourceStore from '../../src/ResourceStore.js';
import { expect } from 'chai';

describe('BackendConnector performance test', () => {
let connector;

before(() => {
connector = new BackendConnector(
new BackendMock(),
new ResourceStore(),
{
interpolator: new Interpolator(),
},
{
backend: { loadPath: 'http://localhost:9876/locales/{{lng}}/{{ns}}.json' },
},
);
});

describe('#load', () => {
it('should load 10,000 items in under the 2 second timeout', (done) => {
const namespaces = [];
for (let i = 0; i < 10000; i++) {
namespaces.push(`namespace${i}`);
}
connector.load(['en'], namespaces, function (err) {
expect(err).to.be.not.ok;
expect(connector.store.getResourceBundle('en', 'namespace1')).to.eql({
status: 'nok',
retries: 0,
});
done();
});
});
});
});

0 comments on commit 448e2be

Please sign in to comment.