From e0628c6197aff81c1b8678eb7d529d35df9a0786 Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 16 Mar 2025 01:11:03 +0800 Subject: [PATCH] core: bug fixes --- framework/framework/server.ts | 48 ++++++++++++++++---------- packages/hydrooj/src/handler/manage.ts | 6 ++-- packages/hydrooj/src/model/contest.ts | 7 ++-- packages/hydrooj/src/service/hmr.ts | 4 +-- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 7ecb9e1a8..74845bf18 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -550,7 +550,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } } - private async handleWS(ctx: KoaContext, HandlerClass, checker, conn, layer?) { + private async handleWS(ctx: KoaContext, HandlerClass, checker, conn?, layer?) { const { args } = ctx.HydroContext; const h = new HandlerClass(ctx, this.ctx); // FIXME: should pass type check @@ -581,6 +581,23 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); ctx.handler = h; h.conn = conn; const disposables = []; + let interval: NodeJS.Timeout; + let closed = false; + + const clean = async (err?: Error) => { + if (closed) return; + closed = true; + if (err) await h.onerror(err); + // FIXME: should pass type check + else (this.ctx.emit as any)('connection/close', h); + h.active = false; + for (const d of disposables) d(); + if (layer) layer.clients.delete(conn); + if (err && !layer) ctx.status = 500; + if (interval) clearInterval(interval); + h.cleanup?.(args); + }; + try { // FIXME: should pass type check await (this.ctx.parallel as any)('handler/create', h, 'ws'); @@ -591,18 +608,6 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); if (h.prepare) await h.prepare(args); // eslint-disable-next-line @typescript-eslint/no-shadow for (const { name, target } of h.__subscribe || []) disposables.push(this.ctx.on(name, target.bind(h))); - let closed = false; - let interval: NodeJS.Timeout; - const clean = () => { - if (closed) return; - closed = true; - // FIXME: should pass type check - (this.ctx.emit as any)('connection/close', h); - if (layer) layer.clients.delete(conn); - if (interval) clearInterval(interval); - for (const d of disposables) d(); - h.cleanup?.(args); - }; if (layer) { let lastHeartbeat = Date.now(); interval = setInterval(() => { @@ -615,7 +620,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); conn.on('pong', () => { lastHeartbeat = Date.now(); }); - conn.onmessage = (e) => { + conn.onmessage = async (e) => { lastHeartbeat = Date.now(); if (e.data === 'pong') return; if (e.data === 'ping') { @@ -629,7 +634,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); conn.close(); } try { - h.message?.(payload); + await h.message?.(payload); } catch (err) { logger.error(e); } @@ -637,15 +642,20 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } else ctx.body = stream; // FIXME: should pass type check await (this.ctx.parallel as any)('connection/active', h as any); + h.active = true; if (layer) { if (conn.readyState === conn.OPEN) { - conn.on('close', clean); + conn.on('close', () => clean()); + conn.on('error', (err) => clean(err)); conn.resume(); } else clean(); - } else stream.on('close', clean); + } else { + stream.on('close', () => clean()); + stream.on('error', (err) => clean(err)); + } } catch (e) { - await h.onerror(e); - if (!layer) ctx.status = 500; + // error during initialization (prepare, hooks) + await clean(e); } } diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index c12f28e9f..0b655e81e 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -183,7 +183,7 @@ class SystemConfigHandler extends SystemHandler { @requireSudo async get() { this.response.template = 'manage_config.html'; - let value = this.ctx.config.configSource; + let value; const processNode = (node: any, schema: Schema, parent?: any, accessKey?: string) => { if (['union', 'intersect'].includes(schema.type)) { @@ -199,7 +199,7 @@ class SystemConfigHandler extends SystemHandler { }; try { - value = Schema.intersect(this.ctx.config.settings)(yaml.load(value)); + value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource)); for (const schema of this.ctx.config.settings) processNode(value, schema); } catch (e) { } this.response.body = { @@ -218,7 +218,7 @@ class SystemConfigHandler extends SystemHandler { for (const item of schema.list) processNode(node, old, item, parent, accessKey); } if (parent && (schema.meta.secret === true || schema.meta.role === 'secret')) { - if (node === '[hidden]') parent[accessKey] = old[accessKey]; + if (node === '[hidden]') parent[accessKey] = old; // TODO support more types } if (schema.type === 'object') { diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index e1680a902..90008adb0 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -291,9 +291,8 @@ const oi = buildContestRule({ continue; } if (!detail[j.pid] || detail[j.pid].score < j.score || this.submitAfterAccept) { - detail[j.pid] = j; - display[j.pid] ||= {}; - display[j.pid] = j; + detail[j.pid] = { ...j }; + display[j.pid] = { ...j }; } } for (const i in display) { @@ -360,7 +359,7 @@ const oi = buildContestRule({ accepted[s.pid] = true; } } - const tsddict = (config.lockAt ? tsdoc.display : tsdoc.detail) || {}; + const tsddict = ((config.lockAt && isLocked(tdoc, new Date())) ? tsdoc.display : tsdoc.detail) || {}; for (const pid of tdoc.pids) { const index = `${tsdoc.uid}/${tdoc.domainId}/${pid}`; // eslint-disable-next-line @typescript-eslint/no-use-before-define diff --git a/packages/hydrooj/src/service/hmr.ts b/packages/hydrooj/src/service/hmr.ts index c60f5b0aa..1ab99545a 100644 --- a/packages/hydrooj/src/service/hmr.ts +++ b/packages/hydrooj/src/service/hmr.ts @@ -158,11 +158,11 @@ export default class HMR extends Service { for (const filename in require.cache) { const module = require.cache[filename]; const plugin = unwrapExports(module.exports); - if (typeof plugin !== 'object' || !plugin || !('apply' in plugin)) continue; + if (!plugin) continue; const runtime = this.ctx.registry.get(plugin); if (!runtime || this.declined.has(filename)) continue; pending.set(filename, plugin); - if (!plugin || !('sideEffect' in plugin) || !plugin['sideEffect']) this.declined.add(filename); + if (!plugin?.sideEffect) this.declined.add(filename); } for (const [filename, plugin] of pending) {