mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #3383 from logto-io/gao-optimize-tenant-disposal
refactor(core): optimize tenant disposal
This commit is contained in:
commit
1d1cd46ccf
4 changed files with 87 additions and 33 deletions
|
@ -17,6 +17,18 @@ const logListening = (type: 'core' | 'admin' = 'core') => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTenant = async (tenantId: string) => {
|
||||||
|
try {
|
||||||
|
return await tenantPool.get(tenantId);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof TenantNotFoundError) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default async function initApp(app: Koa): Promise<void> {
|
export default async function initApp(app: Koa): Promise<void> {
|
||||||
app.use(async (ctx, next) => {
|
app.use(async (ctx, next) => {
|
||||||
if (EnvSet.values.isDomainBasedMultiTenancy && ctx.URL.pathname === '/status') {
|
if (EnvSet.values.isDomainBasedMultiTenancy && ctx.URL.pathname === '/status') {
|
||||||
|
@ -33,18 +45,20 @@ export default async function initApp(app: Koa): Promise<void> {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tenant = await getTenant(tenantId);
|
||||||
|
|
||||||
|
if (tenant instanceof TenantNotFoundError) {
|
||||||
|
ctx.status = 404;
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tenant = await tenantPool.get(tenantId);
|
tenant.requestStart();
|
||||||
await tenant.run(ctx, next);
|
await tenant.run(ctx, next);
|
||||||
|
tenant.requestEnd();
|
||||||
return;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof TenantNotFoundError) {
|
tenant.requestEnd();
|
||||||
ctx.status = 404;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,10 +52,6 @@ export class EnvSet {
|
||||||
return this.#pool;
|
return this.#pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
get poolSafe() {
|
|
||||||
return this.#pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
get oidc() {
|
get oidc() {
|
||||||
if (!this.#oidc) {
|
if (!this.#oidc) {
|
||||||
return throwNotLoadedError();
|
return throwNotLoadedError();
|
||||||
|
|
|
@ -34,23 +34,15 @@ export default class Tenant implements TenantContext {
|
||||||
return new Tenant(envSet, id);
|
return new Tenant(envSet, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#requestCount = 0;
|
||||||
|
#onRequestEmpty?: () => Promise<void>;
|
||||||
|
|
||||||
public readonly provider: Provider;
|
public readonly provider: Provider;
|
||||||
public readonly queries: Queries;
|
public readonly queries: Queries;
|
||||||
public readonly libraries: Libraries;
|
public readonly libraries: Libraries;
|
||||||
|
public readonly run: MiddlewareType;
|
||||||
|
|
||||||
public readonly app: Koa;
|
private readonly app: Koa;
|
||||||
|
|
||||||
get run(): MiddlewareType {
|
|
||||||
if (
|
|
||||||
EnvSet.values.isPathBasedMultiTenancy &&
|
|
||||||
// If admin URL Set is specified, consider that URL first
|
|
||||||
!(EnvSet.values.adminUrlSet.deduplicated().length > 0 && this.id === adminTenantId)
|
|
||||||
) {
|
|
||||||
return mount('/' + this.id, this.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mount(this.app);
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor(public readonly envSet: EnvSet, public readonly id: string) {
|
private constructor(public readonly envSet: EnvSet, public readonly id: string) {
|
||||||
const queries = new Queries(envSet.pool);
|
const queries = new Queries(envSet.pool);
|
||||||
|
@ -129,5 +121,56 @@ export default class Tenant implements TenantContext {
|
||||||
|
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
|
|
||||||
|
const { isPathBasedMultiTenancy, adminUrlSet } = EnvSet.values;
|
||||||
|
this.run =
|
||||||
|
isPathBasedMultiTenancy &&
|
||||||
|
// If admin URL Set is specified, consider that URL first
|
||||||
|
!(adminUrlSet.deduplicated().length > 0 && this.id === adminTenantId)
|
||||||
|
? mount('/' + this.id, this.app)
|
||||||
|
: mount(this.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public requestStart() {
|
||||||
|
this.#requestCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public requestEnd() {
|
||||||
|
if (this.#requestCount > 0) {
|
||||||
|
this.#requestCount -= 1;
|
||||||
|
|
||||||
|
if (this.#requestCount === 0) {
|
||||||
|
void this.#onRequestEmpty?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to dispose the tenant resources. If there are any pending requests, this function will wait for them to end with 5s timeout.
|
||||||
|
*
|
||||||
|
* Currently this function only ends the database pool.
|
||||||
|
*
|
||||||
|
* @returns Resolves `true` for a normal disposal and `'timeout'` for a timeout.
|
||||||
|
*/
|
||||||
|
public async dispose() {
|
||||||
|
if (this.#requestCount <= 0) {
|
||||||
|
await this.envSet.end();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<true | 'timeout'>((resolve) => {
|
||||||
|
const timeout = setTimeout(async () => {
|
||||||
|
this.#onRequestEmpty = undefined;
|
||||||
|
await this.envSet.end();
|
||||||
|
resolve('timeout');
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
this.#onRequestEmpty = async () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
await this.envSet.end();
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ import LRUCache from 'lru-cache';
|
||||||
import Tenant from './Tenant.js';
|
import Tenant from './Tenant.js';
|
||||||
|
|
||||||
export class TenantPool {
|
export class TenantPool {
|
||||||
protected cache = new LRUCache<string, Tenant>({
|
protected cache = new LRUCache<string, Promise<Tenant>>({
|
||||||
max: 100,
|
max: 100,
|
||||||
dispose: async (tenant) => {
|
dispose: async (entry) => {
|
||||||
await tenant.envSet.end();
|
const tenant = await entry;
|
||||||
|
void tenant.dispose();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ export class TenantPool {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Init tenant:', tenantId);
|
console.log('Init tenant:', tenantId);
|
||||||
const newTenant = await Tenant.create(tenantId);
|
const newTenant = Tenant.create(tenantId);
|
||||||
this.cache.set(tenantId, newTenant);
|
this.cache.set(tenantId, newTenant);
|
||||||
|
|
||||||
return newTenant;
|
return newTenant;
|
||||||
|
@ -26,10 +27,10 @@ export class TenantPool {
|
||||||
|
|
||||||
async endAll(): Promise<void> {
|
async endAll(): Promise<void> {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.cache.dump().map(([, tenant]) => {
|
this.cache.dump().map(async ([, entry]) => {
|
||||||
const { poolSafe } = tenant.value.envSet;
|
const tenant = await entry.value;
|
||||||
|
|
||||||
return poolSafe?.end();
|
return tenant.envSet.end();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue