fix: start sub app in cluster (#5530)

* fix: start sub app

* fix: test

* fix: test

* fix: test

---------

Co-authored-by: CHENGLEI SHAO <Chareice>
This commit is contained in:
ChengLei Shao 2024-11-03 22:31:12 +08:00 committed by GitHub
parent 746b54e9c1
commit f81b942967
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 80 additions and 52 deletions

View File

@ -37,7 +37,11 @@ export class SyncMessageManager {
if (transaction) { if (transaction) {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
reject(new Error(`Publish message to ${channel} timeout, message: ${JSON.stringify(message)}`)); reject(
new Error(
`Publish message to ${channel} timeout, channel: ${channel}, message: ${JSON.stringify(message)}`,
),
);
}, 50000); }, 50000);
transaction.afterCommit(async () => { transaction.afterCommit(async () => {
@ -46,6 +50,7 @@ export class SyncMessageManager {
skipSelf: true, skipSelf: true,
...others, ...others,
}); });
resolve(r); resolve(r);
} catch (error) { } catch (error) {
reject(error); reject(error);

View File

@ -8,7 +8,7 @@
*/ */
import { Database } from '@nocobase/database'; import { Database } from '@nocobase/database';
import { MockServer, createMockServer } from '@nocobase/test'; import { createMockServer, MockServer } from '@nocobase/test';
import compose from 'koa-compose'; import compose from 'koa-compose';
import { parseBuilder, parseFieldAndAssociations, queryData } from '../actions/query'; import { parseBuilder, parseFieldAndAssociations, queryData } from '../actions/query';
@ -19,7 +19,7 @@ describe('formatter', () => {
beforeAll(async () => { beforeAll(async () => {
app = await createMockServer({ app = await createMockServer({
acl: true, acl: true,
plugins: ['users', 'auth', 'data-visualization'], plugins: ['users', 'auth', 'field-sort', 'data-visualization'],
}); });
db = app.db; db = app.db;
}); });

View File

@ -0,0 +1,46 @@
import { createMockCluster, waitSecond } from '@nocobase/test';
import { uid } from '@nocobase/utils';
describe('cluster', () => {
let cluster;
beforeEach(async () => {
cluster = await createMockCluster({
plugins: ['nocobase', 'field-sort', 'multi-app-manager'],
acl: false,
});
});
afterEach(async () => {
await cluster.destroy();
});
it('should start sub app after app created between nodes', async () => {
const [app1, app2] = cluster.nodes;
const name = `td_${uid()}`;
const fn = vi.fn();
app2.on('subAppStarted', async (subApp) => {
fn(subApp.name);
});
await app1.db.getRepository('applications').create({
values: {
name,
options: {
skipSupervisor: true,
plugins: [],
database: {
underscored: true,
},
},
},
context: {
waitSubAppInstall: true,
},
});
await waitSecond(5000);
expect(fn).toBeCalledWith(name);
});
});

View File

@ -129,43 +129,6 @@ describe('multiple apps', () => {
expect(await db.getRepository('applications').count()).toBe(0); expect(await db.getRepository('applications').count()).toBe(0);
}); });
it('should upgrade sub app', async () => {
await db.getRepository('applications').create({
values: {
name: 'test1',
options: {
plugins: ['nocobase'],
},
},
context: {
waitSubAppInstall: true,
},
});
await db.getRepository('applications').create({
values: {
name: 'test2',
options: {
plugins: ['nocobase'],
},
},
context: {
waitSubAppInstall: true,
},
});
await app.runCommand('restart');
await app.runCommand('upgrade');
// const subAppStatus = AppSupervisor.getInstance().getAppStatus(name);
// expect(subAppStatus).toEqual('running');
//
// const subApp = await AppSupervisor.getInstance().getApp(name);
// await subApp.runCommand('upgrade');
//
// await AppSupervisor.getInstance().removeApp(name);
// expect(await db.getRepository('applications').count()).toBe(1);
});
it('should list application with status', async () => { it('should list application with status', async () => {
const sub1 = `td_${uid()}`; const sub1 = `td_${uid()}`;
await db.getRepository('applications').create({ await db.getRepository('applications').create({

View File

@ -29,6 +29,12 @@ export class ApplicationModel extends Model {
name: appName, name: appName,
}; };
return new Application(subAppOptions); const subApp = new Application(subAppOptions);
subApp.on('afterStart', () => {
mainApp.emit('subAppStarted', subApp);
});
return subApp;
} }
} }

View File

@ -146,7 +146,7 @@ export class PluginMultiAppManagerServer extends Plugin {
async handleSyncMessage(message) { async handleSyncMessage(message) {
const { type } = message; const { type } = message;
if (type === 'startApp') { if (type === 'subAppStarted') {
const { appName } = message; const { appName } = message;
const model = await this.app.db.getRepository('applications').findOne({ const model = await this.app.db.getRepository('applications').findOne({
filter: { filter: {
@ -158,6 +158,10 @@ export class PluginMultiAppManagerServer extends Plugin {
return; return;
} }
if (AppSupervisor.getInstance().hasApp(appName)) {
return;
}
const subApp = model.registerToSupervisor(this.app, { const subApp = model.registerToSupervisor(this.app, {
appOptionsFactory: this.appOptionsFactory, appOptionsFactory: this.appOptionsFactory,
}); });
@ -208,22 +212,19 @@ export class PluginMultiAppManagerServer extends Plugin {
appOptionsFactory: this.appOptionsFactory, appOptionsFactory: this.appOptionsFactory,
}); });
subApp.on('afterStart', async () => {
this.sendSyncMessage({
type: 'subAppStarted',
appName: name,
});
});
// create database // create database
await this.appDbCreator(subApp, { await this.appDbCreator(subApp, {
transaction, transaction,
context: options.context, context: options.context,
}); });
this.sendSyncMessage(
{
type: 'startApp',
appName: name,
},
{
transaction,
},
);
const startPromise = subApp.runCommand('start', '--quickstart'); const startPromise = subApp.runCommand('start', '--quickstart');
if (options?.context?.waitSubAppInstall) { if (options?.context?.waitSubAppInstall) {
@ -289,6 +290,13 @@ export class PluginMultiAppManagerServer extends Plugin {
appOptionsFactory: self.appOptionsFactory, appOptionsFactory: self.appOptionsFactory,
}); });
subApp.on('afterStart', async () => {
this.sendSyncMessage({
type: 'subAppStarted',
appName: name,
});
});
// must skip load on upgrade // must skip load on upgrade
if (!loadButNotStart) { if (!loadButNotStart) {
await subApp.runCommand('start', '--quickstart'); await subApp.runCommand('start', '--quickstart');