backend-app-api: fix ServiceRegistry instantiation race
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: blam <ben@blam.sh> Co-authored-by: Johan Haals <johan.haals@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
committed by
Johan Haals
parent
ef875b07c3
commit
405d485788
@@ -170,4 +170,31 @@ describe('ServiceRegistry', () => {
|
||||
expect(await factoryB('catalog')).toBe(await factoryB('catalog'));
|
||||
expect(await factoryA('catalog')).not.toBe(await factoryB('catalog'));
|
||||
});
|
||||
|
||||
it('should not call factory functions more than once', async () => {
|
||||
const innerFactory = jest.fn(async (pluginId: string) => {
|
||||
return { x: 1, pluginId };
|
||||
});
|
||||
const factory = jest.fn(async () => innerFactory);
|
||||
const myFactory = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
factory,
|
||||
});
|
||||
|
||||
const registry = new ServiceRegistry([myFactory]);
|
||||
|
||||
await Promise.all([
|
||||
registry.get(ref1)!('catalog')!,
|
||||
registry.get(ref1)!('catalog')!,
|
||||
registry.get(ref1)!('catalog')!,
|
||||
registry.get(ref1)!('scaffolder')!,
|
||||
registry.get(ref1)!('scaffolder')!,
|
||||
]);
|
||||
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
expect(innerFactory).toHaveBeenCalledTimes(2);
|
||||
expect(innerFactory).toHaveBeenCalledWith('catalog');
|
||||
expect(innerFactory).toHaveBeenCalledWith('scaffolder');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,13 @@ import {
|
||||
export class ServiceRegistry {
|
||||
readonly #providedFactories: Map<string, ServiceFactory>;
|
||||
readonly #loadedDefaultFactories: Map<Function, ServiceFactory>;
|
||||
readonly #implementations: Map<ServiceFactory, Map<string, unknown>>;
|
||||
readonly #implementations: Map<
|
||||
ServiceFactory,
|
||||
{
|
||||
factoryFunc: Promise<FactoryFunc<unknown>>;
|
||||
byPlugin: Map<string, Promise<unknown>>;
|
||||
}
|
||||
>;
|
||||
|
||||
constructor(factories: ServiceFactory<any>[]) {
|
||||
this.#providedFactories = new Map(factories.map(f => [f.service.id, f]));
|
||||
@@ -47,29 +53,31 @@ export class ServiceRegistry {
|
||||
factory = loadedFactory;
|
||||
}
|
||||
|
||||
let implementations = this.#implementations.get(factory);
|
||||
if (implementations) {
|
||||
if (implementations.has(pluginId)) {
|
||||
return implementations.get(pluginId) as T;
|
||||
}
|
||||
} else {
|
||||
implementations = new Map();
|
||||
this.#implementations.set(factory, implementations);
|
||||
let implementation = this.#implementations.get(factory);
|
||||
if (!implementation) {
|
||||
const factoryDeps = Object.fromEntries(
|
||||
Object.entries(factory.deps).map(([name, serviceRef]) => [
|
||||
name,
|
||||
this.get(serviceRef)!, // TODO: throw
|
||||
]),
|
||||
);
|
||||
|
||||
implementation = {
|
||||
factoryFunc: factory.factory(factoryDeps),
|
||||
byPlugin: new Map(),
|
||||
};
|
||||
|
||||
this.#implementations.set(factory, implementation);
|
||||
}
|
||||
|
||||
const factoryDeps = Object.fromEntries(
|
||||
Object.entries(factory.deps).map(([name, serviceRef]) => [
|
||||
name,
|
||||
this.get(serviceRef)!, // TODO: throw
|
||||
]),
|
||||
);
|
||||
let result = implementation.byPlugin.get(pluginId) as Promise<any>;
|
||||
if (!result) {
|
||||
result = implementation.factoryFunc.then(func => func(pluginId));
|
||||
|
||||
const factoryFunc = await factory.factory(factoryDeps);
|
||||
const implementation = await factoryFunc(pluginId);
|
||||
implementation.byPlugin.set(pluginId, result);
|
||||
}
|
||||
|
||||
implementations.set(pluginId, implementation);
|
||||
|
||||
return implementation as T;
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user