fix(multip-app-manager): init multiple Application instances while starting up a sub app (#1986)
* fix: init multiple Application instances while starting up sub app by Jacob * fix: import error by Jacob * feat: use async-mutex for locking process by Jacob * revert: unused update by Jacob * test: should get same obj ref when asynchronously access with same sub app name by Jacob * feat: add async-mutex dependency to multi-app-manager package.json by Jacob
This commit is contained in:
parent
2c9ce09304
commit
bc00bd161a
@ -9,7 +9,8 @@
|
|||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/server": "0.9.4-alpha.2"
|
"@nocobase/server": "0.9.4-alpha.2",
|
||||||
|
"async-mutex": "^0.3.2"
|
||||||
},
|
},
|
||||||
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
|
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
|
||||||
}
|
}
|
||||||
|
@ -172,4 +172,38 @@ describe('multiple apps create', () => {
|
|||||||
await app.start();
|
await app.start();
|
||||||
expect(app.appManager.applications.get(subAppName)).toBeDefined();
|
expect(app.appManager.applications.get(subAppName)).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get same obj ref when asynchronously access with same sub app name', async () => {
|
||||||
|
const subAppName = `t_${uid()}`;
|
||||||
|
|
||||||
|
const subApp = await app.db.getRepository('applications').create({
|
||||||
|
values: {
|
||||||
|
name: subAppName,
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.appManager.removeApplication(subAppName);
|
||||||
|
expect(app.appManager.applications.get(subAppName)).toBeUndefined();
|
||||||
|
|
||||||
|
const instances = [];
|
||||||
|
|
||||||
|
app.on('afterSubAppAdded', (subApp) => {
|
||||||
|
instances.push(subApp);
|
||||||
|
});
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
promises.push(
|
||||||
|
(async () => {
|
||||||
|
await app.appManager.getApplication(subAppName);
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
expect(instances.length).toBe(1);
|
||||||
|
expect(instances[0]).toBeDefined();
|
||||||
|
expect(instances[0].name == subAppName).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import lodash from 'lodash';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { ApplicationModel } from './models/application';
|
import { ApplicationModel } from './models/application';
|
||||||
|
import { Mutex } from 'async-mutex';
|
||||||
|
|
||||||
export type AppDbCreator = (app: Application, transaction?: Transactionable) => Promise<void>;
|
export type AppDbCreator = (app: Application, transaction?: Transactionable) => Promise<void>;
|
||||||
export type AppOptionsFactory = (appName: string, mainApp: Application) => any;
|
export type AppOptionsFactory = (appName: string, mainApp: Application) => any;
|
||||||
@ -69,6 +70,8 @@ export class PluginMultiAppManager extends Plugin {
|
|||||||
appDbCreator: AppDbCreator = defaultDbCreator;
|
appDbCreator: AppDbCreator = defaultDbCreator;
|
||||||
appOptionsFactory: AppOptionsFactory = defaultAppOptionsFactory;
|
appOptionsFactory: AppOptionsFactory = defaultAppOptionsFactory;
|
||||||
|
|
||||||
|
private beforeGetApplicationMutex = new Mutex();
|
||||||
|
|
||||||
setAppOptionsFactory(factory: AppOptionsFactory) {
|
setAppOptionsFactory(factory: AppOptionsFactory) {
|
||||||
this.appOptionsFactory = factory;
|
this.appOptionsFactory = factory;
|
||||||
}
|
}
|
||||||
@ -150,35 +153,37 @@ export class PluginMultiAppManager extends Plugin {
|
|||||||
this.app.on(
|
this.app.on(
|
||||||
'beforeGetApplication',
|
'beforeGetApplication',
|
||||||
async ({ appManager, name, options }: { appManager: AppManager; name: string; options: any }) => {
|
async ({ appManager, name, options }: { appManager: AppManager; name: string; options: any }) => {
|
||||||
if (appManager.applications.has(name)) {
|
await this.beforeGetApplicationMutex.runExclusive(async () => {
|
||||||
return;
|
if (appManager.applications.has(name)) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const applicationRecord = (await this.app.db.getRepository('applications').findOne({
|
const applicationRecord = (await this.app.db.getRepository('applications').findOne({
|
||||||
filter: {
|
filter: {
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
})) as ApplicationModel | null;
|
})) as ApplicationModel | null;
|
||||||
|
|
||||||
const instanceOptions = applicationRecord.get('options');
|
const instanceOptions = applicationRecord.get('options');
|
||||||
|
|
||||||
// skip standalone deployment application
|
// skip standalone deployment application
|
||||||
if (instanceOptions?.standaloneDeployment && appManager.runningMode !== 'single') {
|
if (instanceOptions?.standaloneDeployment && appManager.runningMode !== 'single') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!applicationRecord) {
|
if (!applicationRecord) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subApp = await applicationRecord.registerToMainApp(this.app, {
|
const subApp = await applicationRecord.registerToMainApp(this.app, {
|
||||||
appOptionsFactory: this.appOptionsFactory,
|
appOptionsFactory: this.appOptionsFactory,
|
||||||
|
});
|
||||||
|
|
||||||
|
// must skip load on upgrade
|
||||||
|
if (!options?.upgrading) {
|
||||||
|
await subApp.load();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// must skip load on upgrade
|
|
||||||
if (!options?.upgrading) {
|
|
||||||
await subApp.load();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user