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:
icezohu 2023-06-06 10:47:50 +08:00 committed by GitHub
parent 2c9ce09304
commit bc00bd161a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 24 deletions

View File

@ -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"
} }

View File

@ -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();
});
}); });

View File

@ -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();
}
}, },
); );