Add sequentialize to prevent parralel loading of cipher keys (#7)
* Add sequentialize to prevent parralel loading of cipher keys Massively improves start up performance of extensions * Add tests for sequentialize * Fix sequentialize as it was caching calls for all instances together * Add sequentialize to the functions that have internal caches * Adding sequentialize to getOrgKeys makes big performance difference * Update cipher.service.ts * Update collection.service.ts * Update folder.service.ts
This commit is contained in:
parent
3a34d3b174
commit
04014a8e78
|
@ -0,0 +1,141 @@
|
||||||
|
import { sequentialize } from '../../../src/misc/sequentialize';
|
||||||
|
|
||||||
|
describe('sequentialize decorator', () => {
|
||||||
|
it('should call the function once', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
promises.push(foo.bar(1));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
expect(foo.calls).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function once for each instance of the object', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
const foo2 = new Foo();
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
promises.push(foo.bar(1));
|
||||||
|
promises.push(foo2.bar(1));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
expect(foo.calls).toBe(1);
|
||||||
|
expect(foo2.calls).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function once with key function', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
promises.push(foo.baz(1));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
expect(foo.calls).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function again when already resolved', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
await foo.bar(1);
|
||||||
|
expect(foo.calls).toBe(1);
|
||||||
|
await foo.bar(1);
|
||||||
|
expect(foo.calls).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function again when already resolved with a key function', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
await foo.baz(1);
|
||||||
|
expect(foo.calls).toBe(1);
|
||||||
|
await foo.baz(1);
|
||||||
|
expect(foo.calls).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function for each argument', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
await Promise.all([
|
||||||
|
foo.bar(1),
|
||||||
|
foo.bar(1),
|
||||||
|
foo.bar(2),
|
||||||
|
foo.bar(2),
|
||||||
|
foo.bar(3),
|
||||||
|
foo.bar(3),
|
||||||
|
]);
|
||||||
|
expect(foo.calls).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the function for each argument with key function', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
await Promise.all([
|
||||||
|
foo.baz(1),
|
||||||
|
foo.baz(1),
|
||||||
|
foo.baz(2),
|
||||||
|
foo.baz(2),
|
||||||
|
foo.baz(3),
|
||||||
|
foo.baz(3),
|
||||||
|
]);
|
||||||
|
expect(foo.calls).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct result for each call', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
const allRes = [];
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
foo.bar(1).then((res) => allRes.push(res)),
|
||||||
|
foo.bar(1).then((res) => allRes.push(res)),
|
||||||
|
foo.bar(2).then((res) => allRes.push(res)),
|
||||||
|
foo.bar(2).then((res) => allRes.push(res)),
|
||||||
|
foo.bar(3).then((res) => allRes.push(res)),
|
||||||
|
foo.bar(3).then((res) => allRes.push(res)),
|
||||||
|
]);
|
||||||
|
expect(foo.calls).toBe(3);
|
||||||
|
expect(allRes.length).toBe(6);
|
||||||
|
allRes.sort();
|
||||||
|
expect(allRes).toEqual([2, 2, 4, 4, 6, 6]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct result for each call with key function', async () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
const allRes = [];
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
foo.baz(1).then((res) => allRes.push(res)),
|
||||||
|
foo.baz(1).then((res) => allRes.push(res)),
|
||||||
|
foo.baz(2).then((res) => allRes.push(res)),
|
||||||
|
foo.baz(2).then((res) => allRes.push(res)),
|
||||||
|
foo.baz(3).then((res) => allRes.push(res)),
|
||||||
|
foo.baz(3).then((res) => allRes.push(res)),
|
||||||
|
]);
|
||||||
|
expect(foo.calls).toBe(3);
|
||||||
|
expect(allRes.length).toBe(6);
|
||||||
|
allRes.sort();
|
||||||
|
expect(allRes).toEqual([3, 3, 6, 6, 9, 9]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
calls = 0;
|
||||||
|
|
||||||
|
@sequentialize()
|
||||||
|
bar(a) {
|
||||||
|
this.calls++;
|
||||||
|
return new Promise((res) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res(a * 2);
|
||||||
|
}, Math.random() * 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@sequentialize((args) => args[0])
|
||||||
|
baz(a) {
|
||||||
|
this.calls++;
|
||||||
|
return new Promise((res) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res(a * 3);
|
||||||
|
}, Math.random() * 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Use as a Decorator on async functions, it will prevent multiple 'active' calls as the same time
|
||||||
|
*
|
||||||
|
* If a promise was returned from a previous call to this function, that hasn't yet resolved it will
|
||||||
|
* be returned, instead of calling the original function again
|
||||||
|
*
|
||||||
|
* Results are not cached, once the promise has returned, the next call will result in a fresh call
|
||||||
|
*/
|
||||||
|
export function sequentialize(key: (args: any[]) => string = JSON.stringify) {
|
||||||
|
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
||||||
|
const originalMethod: () => Promise<any> = descriptor.value;
|
||||||
|
|
||||||
|
const caches = new Map<any, Map<string, Promise<any>>>();
|
||||||
|
const getCache = (obj: any) => {
|
||||||
|
let cache = caches.get(obj);
|
||||||
|
if (cache) {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
cache = new Map<string, Promise<any>>();
|
||||||
|
caches.set(obj, cache);
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: function(...args: any[]) {
|
||||||
|
const argsKey = key(args);
|
||||||
|
const cache = getCache(this);
|
||||||
|
|
||||||
|
let res = cache.get(argsKey);
|
||||||
|
if (res) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = originalMethod.apply(this, args)
|
||||||
|
.then((val: any) => {
|
||||||
|
cache.delete(argsKey);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
cache.delete(argsKey);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
cache.set(argsKey, res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { EncryptionType } from '../../enums/encryptionType';
|
||||||
|
|
||||||
import { CryptoService } from '../../abstractions/crypto.service';
|
import { CryptoService } from '../../abstractions/crypto.service';
|
||||||
|
|
||||||
|
import { sequentialize } from '../../misc/sequentialize';
|
||||||
import { Utils } from '../../misc/utils';
|
import { Utils } from '../../misc/utils';
|
||||||
|
|
||||||
export class CipherString {
|
export class CipherString {
|
||||||
|
@ -89,6 +90,7 @@ export class CipherString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@sequentialize((args) => args[0])
|
||||||
async decrypt(orgId: string): Promise<string> {
|
async decrypt(orgId: string): Promise<string> {
|
||||||
if (this.decryptedValue) {
|
if (this.decryptedValue) {
|
||||||
return Promise.resolve(this.decryptedValue);
|
return Promise.resolve(this.decryptedValue);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { StorageService } from '../abstractions/storage.service';
|
||||||
|
|
||||||
import { ConstantsService } from './constants.service';
|
import { ConstantsService } from './constants.service';
|
||||||
|
|
||||||
|
import { sequentialize } from '../misc/sequentialize';
|
||||||
import { Utils } from '../misc/utils';
|
import { Utils } from '../misc/utils';
|
||||||
|
|
||||||
const Keys = {
|
const Keys = {
|
||||||
|
@ -164,6 +165,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
return this.privateKey;
|
return this.privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@sequentialize()
|
||||||
async getOrgKeys(): Promise<Map<string, SymmetricCryptoKey>> {
|
async getOrgKeys(): Promise<Map<string, SymmetricCryptoKey>> {
|
||||||
if (this.orgKeys != null && this.orgKeys.size > 0) {
|
if (this.orgKeys != null && this.orgKeys.size > 0) {
|
||||||
return this.orgKeys;
|
return this.orgKeys;
|
||||||
|
|
Loading…
Reference in New Issue