mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: judge: add concurrency control to task consumer (#719)
Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
1 parent
8b10f51430
commit
6b759cbe63
9 files changed
+128
-81
No files matched your search
@@ -90,7 +90,10 @@ export async function postInit(ctx) {
|
|||||||
await (new JudgeTask(session, JSON.parse(JSON.stringify(Object.assign(rdoc, t))))).handle().catch(logger.error);
|
await (new JudgeTask(session, JSON.parse(JSON.stringify(Object.assign(rdoc, t))))).handle().catch(logger.error);
|
||||||
};
|
};
|
||||||
const parallelism = Math.max(getConfig('parallelism'), 2);
|
const parallelism = Math.max(getConfig('parallelism'), 2);
|
||||||
for (let i = 1; i < parallelism; i++) TaskModel.consume({ type: 'judge' }, handle);
|
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism);
|
||||||
|
ctx.on('system/setting', () => {
|
||||||
|
taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2));
|
||||||
|
});
|
||||||
TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
|
TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
|
||||||
TaskModel.consume({ type: 'generate' }, handle);
|
TaskModel.consume({ type: 'generate' }, handle);
|
||||||
}
|
}
|
||||||
@@ -21,14 +21,13 @@ import record from '../model/record';
|
|||||||
import * as setting from '../model/setting';
|
import * as setting from '../model/setting';
|
||||||
import storage from '../model/storage';
|
import storage from '../model/storage';
|
||||||
import * as system from '../model/system';
|
import * as system from '../model/system';
|
||||||
import task from '../model/task';
|
import task, { Consumer } from '../model/task';
|
||||||
import user from '../model/user';
|
import user from '../model/user';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
import { updateJudge } from '../service/monitor';
|
import { updateJudge } from '../service/monitor';
|
||||||
import {
|
import {
|
||||||
ConnectionHandler, Handler, post, subscribe, Types,
|
ConnectionHandler, Handler, post, subscribe, Types,
|
||||||
} from '../service/server';
|
} from '../service/server';
|
||||||
import { sleep } from '../utils';
|
|
||||||
|
|
||||||
const logger = new Logger('judge');
|
const logger = new Logger('judge');
|
||||||
|
|
||||||
@@ -45,6 +44,8 @@ function parseCaseResult(body: TestCase): Required<TestCase> {
|
|||||||
function processPayload(body: Partial<JudgeResultBody>) {
|
function processPayload(body: Partial<JudgeResultBody>) {
|
||||||
const $set: Partial<RecordDoc> = {};
|
const $set: Partial<RecordDoc> = {};
|
||||||
const $push: any = {};
|
const $push: any = {};
|
||||||
|
const $unset: any = {};
|
||||||
|
const $inc: any = {};
|
||||||
if (body.cases?.length) {
|
if (body.cases?.length) {
|
||||||
const c = body.cases.map(parseCaseResult);
|
const c = body.cases.map(parseCaseResult);
|
||||||
$push.testCases = { $each: c };
|
$push.testCases = { $each: c };
|
||||||
@@ -64,13 +65,18 @@ function processPayload(body: Partial<JudgeResultBody>) {
|
|||||||
if (Number.isFinite(body.memory)) $set.memory = body.memory;
|
if (Number.isFinite(body.memory)) $set.memory = body.memory;
|
||||||
if (body.progress !== undefined) $set.progress = body.progress;
|
if (body.progress !== undefined) $set.progress = body.progress;
|
||||||
if (body.subtasks) $set.subtasks = body.subtasks;
|
if (body.subtasks) $set.subtasks = body.subtasks;
|
||||||
return { $set, $push };
|
if (body.addProgress) $inc.progress = body.addProgress;
|
||||||
|
return {
|
||||||
|
$set, $push, $unset, $inc,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function next(body: Partial<JudgeResultBody>) {
|
export async function next(body: Partial<JudgeResultBody>) {
|
||||||
body.rid = new ObjectId(body.rid);
|
body.rid = new ObjectId(body.rid);
|
||||||
const { $set, $push } = processPayload(body);
|
const {
|
||||||
const rdoc = await record.update(body.domainId, body.rid, $set, $push, {}, body.addProgress ? { progress: body.addProgress } : {});
|
$set, $push, $unset, $inc,
|
||||||
|
} = processPayload(body);
|
||||||
|
const rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset, $inc);
|
||||||
bus.broadcast('record/change', rdoc, $set, $push, body);
|
bus.broadcast('record/change', rdoc, $set, $push, body);
|
||||||
return rdoc;
|
return rdoc;
|
||||||
}
|
}
|
||||||
@@ -210,25 +216,25 @@ export class JudgeFileUpdateHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JudgeConnectionHandler extends ConnectionHandler {
|
export class JudgeConnectionHandler extends ConnectionHandler {
|
||||||
category = '#judge';
|
category = '#judge';
|
||||||
processing: Task[] = [];
|
|
||||||
started = false;
|
|
||||||
closed = false;
|
|
||||||
query: any = { type: { $in: ['judge', 'generate'] } };
|
query: any = { type: { $in: ['judge', 'generate'] } };
|
||||||
rdocs: Record<string, RecordDoc> = {};
|
|
||||||
concurrency = 1;
|
concurrency = 1;
|
||||||
queue: PQueue = new PQueue({ concurrency: 1 });
|
consumer: Consumer = null;
|
||||||
callback: (res?: any) => void = null;
|
startTimeout: NodeJS.Timeout = null;
|
||||||
|
tasks: Record<string, {
|
||||||
|
resolve: (_: any) => void,
|
||||||
|
queue: PQueue,
|
||||||
|
domainId: string,
|
||||||
|
t: Task,
|
||||||
|
}> = {};
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
logger.info('Judge daemon connected from ', this.request.ip);
|
logger.info('Judge daemon connected from ', this.request.ip);
|
||||||
this.sendLanguageConfig();
|
this.sendLanguageConfig();
|
||||||
// TODO deprecated, just for compatibility
|
// TODO deprecated, just for compatibility
|
||||||
setTimeout(() => {
|
this.startTimeout = setTimeout(() => {
|
||||||
if (this.started || this.closed) return;
|
this.consumer ||= task.consume(this.query, this.newTask.bind(this), true, this.concurrency);
|
||||||
this.newTask().catch((e) => logger.error(e));
|
|
||||||
this.started = true;
|
|
||||||
}, 15000);
|
}, 15000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,31 +243,23 @@ class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
this.send({ language: setting.langs });
|
this.send({ language: setting.langs });
|
||||||
}
|
}
|
||||||
|
|
||||||
async newTask() {
|
async newTask(t: Task) {
|
||||||
while (!this.closed) {
|
|
||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
if (this.processing.length >= this.concurrency) {
|
|
||||||
await Promise.race([
|
|
||||||
sleep(500),
|
|
||||||
new Promise((resolve) => {
|
|
||||||
this.callback = resolve;
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const t = await task.getFirst(this.query);
|
|
||||||
if (!t) {
|
|
||||||
await sleep(500);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const rdoc = await record.get(t.domainId, t.rid);
|
const rdoc = await record.get(t.domainId, t.rid);
|
||||||
if (!rdoc) continue;
|
if (!rdoc) return;
|
||||||
|
|
||||||
|
const rid = rdoc._id.toHexString();
|
||||||
|
let resolve: (_: any) => void;
|
||||||
|
const p = new Promise((r) => { resolve = r; });
|
||||||
|
this.tasks[rid] = {
|
||||||
|
queue: new PQueue({ concurrency: 1 }),
|
||||||
|
domainId: rdoc.domainId,
|
||||||
|
resolve,
|
||||||
|
t,
|
||||||
|
};
|
||||||
this.send({ task: { ...rdoc, ...t } });
|
this.send({ task: { ...rdoc, ...t } });
|
||||||
this.rdocs[rdoc._id.toHexString()] = rdoc;
|
this.tasks[rid].queue.add(() => next({ status: STATUS.STATUS_FETCHED, domainId: rdoc.domainId, rid: rdoc._id }));
|
||||||
this.processing.push(t);
|
await p;
|
||||||
await next({ status: STATUS.STATUS_FETCHED, domainId: rdoc.domainId, rid: rdoc._id });
|
delete this.tasks[rid];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async message(msg) {
|
async message(msg) {
|
||||||
@@ -271,47 +269,47 @@ class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
logger[method]('%o', omit(msg, keys));
|
logger[method]('%o', omit(msg, keys));
|
||||||
}
|
}
|
||||||
if (['next', 'end'].includes(msg.key)) {
|
if (['next', 'end'].includes(msg.key)) {
|
||||||
const rdoc = this.rdocs[msg.rid];
|
const t = this.tasks[msg.key];
|
||||||
if (!rdoc) return;
|
if (!t) return;
|
||||||
msg.domainId = rdoc.domainId;
|
msg.domainId = t.domainId;
|
||||||
if (msg.key === 'next') this.queue.add(() => next(msg));
|
if (msg.key === 'next') t.queue.add(() => next(msg));
|
||||||
if (msg.key === 'end') {
|
if (msg.key === 'end') {
|
||||||
this.processing = this.processing.filter((t) => t.rid.toHexString() !== msg.rid);
|
if (!msg.nop) t.queue.add(() => end({ judger: this.user._id, ...msg }));
|
||||||
delete this.rdocs[msg.rid];
|
t.resolve(null);
|
||||||
this.callback?.();
|
|
||||||
if (!msg.nop) {
|
|
||||||
this.queue.add(() => end({ judger: this.user._id, ...msg }).catch((e) => logger.error(e)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (msg.key === 'status') {
|
} else if (msg.key === 'status') {
|
||||||
await updateJudge(msg.info);
|
await updateJudge(msg.info);
|
||||||
} else if (msg.key === 'prio' && typeof msg.prio === 'number') {
|
} else if (msg.key === 'prio' && typeof msg.prio === 'number') {
|
||||||
// TODO deprecated, use config instead
|
// TODO deprecated, use config instead
|
||||||
this.query.priority = { $gt: msg.prio };
|
this.query.priority = { $gt: msg.prio };
|
||||||
|
this.consumer?.setQuery(this.query);
|
||||||
} else if (msg.key === 'config') {
|
} else if (msg.key === 'config') {
|
||||||
if (Number.isSafeInteger(msg.prio)) {
|
if (Number.isSafeInteger(msg.prio)) {
|
||||||
this.query.priority = { $gt: msg.prio };
|
this.query.priority = { $gt: msg.prio };
|
||||||
|
this.consumer?.setQuery(this.query);
|
||||||
}
|
}
|
||||||
if (Number.isSafeInteger(msg.concurrency) && msg.concurrency > 0) {
|
if (Number.isSafeInteger(msg.concurrency) && msg.concurrency > 0) {
|
||||||
this.concurrency = msg.concurrency;
|
this.concurrency = msg.concurrency;
|
||||||
this.callback?.();
|
this.consumer?.setConcurrency(msg.concurrency);
|
||||||
}
|
}
|
||||||
if (msg.lang instanceof Array && msg.lang.every((i) => typeof i === 'string')) {
|
if (msg.lang instanceof Array && msg.lang.every((i) => typeof i === 'string')) {
|
||||||
this.query.lang = { $in: msg.lang };
|
this.query.lang = { $in: msg.lang };
|
||||||
|
this.consumer?.setQuery(this.query);
|
||||||
}
|
}
|
||||||
} else if (msg.key === 'start') {
|
} else if (msg.key === 'start') {
|
||||||
if (this.started) return;
|
clearTimeout(this.startTimeout);
|
||||||
this.newTask().catch((e) => logger.error(e));
|
this.consumer ||= task.consume(this.query, this.newTask.bind(this), true, this.concurrency);
|
||||||
this.started = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
this.closed = true;
|
clearTimeout(this.startTimeout);
|
||||||
|
this.consumer?.destroy();
|
||||||
logger.info('Judge daemon disconnected from ', this.request.ip);
|
logger.info('Judge daemon disconnected from ', this.request.ip);
|
||||||
await Promise.all(this.processing.map(async (t) => {
|
await Promise.all(Object.values(this.tasks).map(async ({ t }) => {
|
||||||
await record.reset(t.domainId, t.rid, false);
|
const rdoc = await record.reset(t.domainId, t.rid, false);
|
||||||
return await task.add(t);
|
bus.broadcast('record/change', rdoc);
|
||||||
|
return task.add(t);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
pretest = false;
|
pretest = false;
|
||||||
tdoc: Tdoc;
|
tdoc: Tdoc;
|
||||||
applyProjection = false;
|
applyProjection = false;
|
||||||
|
queue: Map<string, () => Promise<any>> = new Map();
|
||||||
|
throttleQueueClear: () => void;
|
||||||
|
|
||||||
@param('tid', Types.ObjectId, true)
|
@param('tid', Types.ObjectId, true)
|
||||||
@param('pid', Types.ProblemId, true)
|
@param('pid', Types.ProblemId, true)
|
||||||
@@ -301,6 +303,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
this.checkPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN);
|
this.checkPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN);
|
||||||
this.allDomain = true;
|
this.allDomain = true;
|
||||||
}
|
}
|
||||||
|
this.throttleQueueClear = throttle(this.queueClear, 100, { trailing: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async message(msg: { rids: string[] }) {
|
async message(msg: { rids: string[] }) {
|
||||||
@@ -338,15 +341,25 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null;
|
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null;
|
||||||
}
|
}
|
||||||
if (this.applyProjection && typeof rdoc.input !== 'string') rdoc = contest.applyProjection(tdoc, rdoc, this.user);
|
if (this.applyProjection && typeof rdoc.input !== 'string') rdoc = contest.applyProjection(tdoc, rdoc, this.user);
|
||||||
if (this.pretest) this.send({ rdoc: omit(rdoc, ['code', 'input']) });
|
if (this.pretest) this.queueSend(rdoc._id.toHexString(), async () => ({ rdoc: omit(rdoc, ['code', 'input']) }));
|
||||||
else {
|
else {
|
||||||
this.send({
|
this.queueSend(rdoc._id.toHexString(), async () => ({
|
||||||
html: await this.renderHTML('record_main_tr.html', {
|
html: await this.renderHTML('record_main_tr.html', {
|
||||||
rdoc, udoc, pdoc, tdoc, allDomain: this.allDomain,
|
rdoc, udoc, pdoc, tdoc, allDomain: this.allDomain,
|
||||||
}),
|
}),
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queueSend(rid: string, fn: () => Promise<any>) {
|
||||||
|
this.queue.set(rid, fn);
|
||||||
|
this.throttleQueueClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
async queueClear() {
|
||||||
|
await Promise.all([...this.queue.values()].map(async (fn) => this.send(await fn())));
|
||||||
|
this.queue.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecordDetailConnectionHandler extends ConnectionHandler {
|
class RecordDetailConnectionHandler extends ConnectionHandler {
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ class Consumer {
|
|||||||
consuming: boolean;
|
consuming: boolean;
|
||||||
running?: any;
|
running?: any;
|
||||||
|
|
||||||
constructor(public filter: any, public func: Function, public destoryOnError = true) {
|
constructor(public filter: any, public func: Function, public destroyOnError = true) {
|
||||||
this.consuming = true;
|
this.consuming = true;
|
||||||
this.consume();
|
this.consume();
|
||||||
bus.on('app/exit', this.destory);
|
bus.on('app/exit', this.destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume() {
|
async consume() {
|
||||||
@@ -51,12 +51,12 @@ class Consumer {
|
|||||||
} else await sleep(1000);
|
} else await sleep(1000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
if (this.destoryOnError) this.destory();
|
if (this.destroyOnError) this.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async destory() {
|
async destroy() {
|
||||||
this.consuming = false;
|
this.consuming = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,37 +25,70 @@ async function getFirst(query: Filter<Task>) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Consumer {
|
export class Consumer {
|
||||||
consuming: boolean;
|
consuming: boolean;
|
||||||
|
processing: Set<Task> = new Set();
|
||||||
running?: any;
|
running?: any;
|
||||||
|
notify: (res?: any) => void;
|
||||||
|
|
||||||
constructor(public filter: any, public func: Function, public destoryOnError = true) {
|
constructor(public filter: any, public func: (t: Task) => Promise<void>, public destroyOnError = true, private concurrency = 1) {
|
||||||
this.consuming = true;
|
this.consuming = true;
|
||||||
this.consume();
|
this.consume();
|
||||||
bus.on('app/exit', this.destory);
|
bus.on('app/exit', this.destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume() {
|
async consume() {
|
||||||
while (this.consuming) {
|
while (this.consuming) {
|
||||||
try {
|
try {
|
||||||
|
if (this.processing.size >= this.concurrency) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
this.notify = resolve;
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const res = await getFirst(this.filter);
|
const res = await getFirst(this.filter);
|
||||||
if (res) {
|
if (!res) {
|
||||||
this.running = res;
|
let timeout: NodeJS.Timeout = null;
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await this.func(res);
|
await new Promise((resolve) => {
|
||||||
this.running = null;
|
timeout = setTimeout(resolve, 1000 / (this.concurrency - this.processing.size));
|
||||||
// eslint-disable-next-line no-await-in-loop
|
this.notify = resolve;
|
||||||
} else await sleep(1000);
|
});
|
||||||
|
clearTimeout(timeout);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.processing.add(res);
|
||||||
|
this.func(res)
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err);
|
||||||
|
if (this.destroyOnError) this.destroy();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing.delete(res);
|
||||||
|
this.notify?.();
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
if (this.destoryOnError) this.destory();
|
if (this.destroyOnError) this.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async destory() {
|
async destroy() {
|
||||||
this.consuming = false;
|
this.consuming = false;
|
||||||
|
this.notify?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConcurrency(concurrency: number) {
|
||||||
|
this.concurrency = concurrency;
|
||||||
|
this.notify?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
setQuery(query: string) {
|
||||||
|
this.filter = query;
|
||||||
|
this.notify?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +128,8 @@ class TaskModel {
|
|||||||
|
|
||||||
static getFirst = getFirst;
|
static getFirst = getFirst;
|
||||||
|
|
||||||
static consume(query: any, cb: Function, destoryOnError = true) {
|
static consume(query: any, cb: (t: Task) => Promise<void>, destroyOnError = true, concurrency = 1) {
|
||||||
return new Consumer(query, cb, destoryOnError);
|
return new Consumer(query, cb, destroyOnError, concurrency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -417,7 +417,6 @@ export function Connection(
|
|||||||
h.compression = new Shorty();
|
h.compression = new Shorty();
|
||||||
conn.send('shorty');
|
conn.send('shorty');
|
||||||
}
|
}
|
||||||
conn.pause();
|
|
||||||
if (h._prepare) await h._prepare(args);
|
if (h._prepare) await h._prepare(args);
|
||||||
if (h.prepare) await h.prepare(args);
|
if (h.prepare) await h.prepare(args);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
@@ -606,6 +605,7 @@ ${ctx.response.status} ${endTime - startTime}ms ${ctx.response.length}`);
|
|||||||
socket.close(1003, 'Websocket Error');
|
socket.close(1003, 'Websocket Error');
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
});
|
});
|
||||||
|
socket.pause();
|
||||||
const ctx: any = app.createContext(request, {} as any);
|
const ctx: any = app.createContext(request, {} as any);
|
||||||
await domainLayer(ctx, () => baseLayer(ctx, () => layers[1](ctx, () => userLayer(ctx, () => { }))));
|
await domainLayer(ctx, () => baseLayer(ctx, () => layers[1](ctx, () => userLayer(ctx, () => { }))));
|
||||||
for (const manager of router.wsStack) {
|
for (const manager of router.wsStack) {
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export default class Editor extends DOMAttachedObject {
|
|||||||
if (hasFocus) this.focus();
|
if (hasFocus) this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
destory() {
|
destroy() {
|
||||||
this.detach();
|
this.detach();
|
||||||
if (this.vditor?.destroy) this.vditor.destroy();
|
if (this.vditor?.destroy) this.vditor.destroy();
|
||||||
else if (this.editor?.dispose) this.editor.dispose();
|
else if (this.editor?.dispose) this.editor.dispose();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async function startEdit(filename, value, fileCategory = 'file') {
|
|||||||
});
|
});
|
||||||
const action = await promise;
|
const action = await promise;
|
||||||
value = (editor.value() as string).replace(/\r\n/g, '\n');
|
value = (editor.value() as string).replace(/\r\n/g, '\n');
|
||||||
editor.destory();
|
editor.destroy();
|
||||||
if (action !== 'ok') return null;
|
if (action !== 'ok') return null;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.vjEditor) this.vjEditor.destory();
|
if (this.vjEditor) this.vjEditor.destroy();
|
||||||
if (this.model) this.model.dispose();
|
if (this.model) this.model.dispose();
|
||||||
if (this.editor) this.editor.dispose();
|
if (this.editor) this.editor.dispose();
|
||||||
this.disposable.map((i) => i.dispose());
|
this.disposable.map((i) => i.dispose());
|
||||||
|
|||||||
Reference in new issue
Block a user