Files
IOT_SIM/server/src/compile-service.ts
T
xuyueandClaude Opus 5.5 6096992507
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
修复:编译服务、语言服务 stop() 后立即清空状态
测试文件之间共用编译服务:上一个文件结束时 stop() 只发了结束信号,下一个文件马上发来的请求
仍交给正在退出的旧进程,随后报"编译服务意外退出"(GitHub Actions 上偶发)。现在 stop() 立即
清空进程和待回复的请求,下一次请求启动新进程。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:42:02 +08:00

80 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Subprocess } from 'bun'
import type { Diagnostic } from '@iot/shared'
import { config } from './config'
import { readLines } from './runner-process'
export interface CompileResult {
ok: boolean
diagnostics: Diagnostic[]
ms: number
}
type Reply = ({ type: 'compiled' } & CompileResult) | { type: 'compileFailed'; message: string }
/**
* 常驻编译服务:全服务器共用一个 C# 编译进程(Roslyn 只加载一次),按请求编号对应回复。
* 进程意外退出时,挂起的请求全部失败,下一次请求自动重启。
*/
class CompileService {
private proc?: Subprocess<'pipe', 'pipe', 'inherit'>
private ready?: Promise<void>
private nextId = 1
private readonly pending = new Map<number, { resolve: (r: CompileResult) => void; reject: (e: Error) => void }>()
/** 编译工程目录;成功时程序集写到同一目录 */
async compile(dir: string): Promise<CompileResult> {
await this.start()
const id = this.nextId++
const result = new Promise<CompileResult>((resolve, reject) => this.pending.set(id, { resolve, reject }))
this.proc!.stdin.write(`${JSON.stringify({ id, dir })}\n`)
this.proc!.stdin.flush()
return result
}
/** 结束进程;立即清空状态,下一次请求会启动新进程(不会发给正在退出的旧进程) */
stop() {
const proc = this.proc
this.proc = undefined
this.ready = undefined
for (const waiter of this.pending.values()) waiter.reject(new Error('编译服务已停止'))
this.pending.clear()
proc?.kill()
}
/** 服务器启动时调用,提前完成编译器的加载和预热 */
start() {
if (this.ready) return this.ready
const proc = Bun.spawn(['dotnet', config.runnerDll, 'compile-server'], {
stdin: 'pipe',
stdout: 'pipe',
stderr: 'inherit',
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', HOME: process.env.HOME ?? '/tmp', DOTNET_CLI_TELEMETRY_OPTOUT: '1', DOTNET_NOLOGO: '1' },
})
this.proc = proc
this.ready = new Promise<void>((resolveReady) => {
void readLines(proc.stdout, (line) => {
const message = JSON.parse(line) as ({ type: 'ready' } | Reply) & { id?: number }
if (message.type === 'ready') {
resolveReady()
return
}
const waiter = this.pending.get(message.id!)
if (!waiter) return
this.pending.delete(message.id!)
if (message.type === 'compiled') waiter.resolve(message)
else waiter.reject(new Error(message.message))
})
})
proc.exited.then((code) => {
if (this.proc !== proc) return
this.proc = undefined
this.ready = undefined
for (const waiter of this.pending.values()) waiter.reject(new Error(`编译服务意外退出(${code})`))
this.pending.clear()
})
return this.ready
}
}
export const compileService = new CompileService()