From 3e3de0ae079686c2507982c0130155ce4af09f7f Mon Sep 17 00:00:00 2001 From: cruldra Date: Mon, 19 Oct 2026 08:38:54 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(vscode):=20webhook=20?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E7=99=BB=E8=AE=B0=E5=85=88=E5=86=99=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E6=96=87=E4=BB=B6=E5=86=8D=E6=94=B9=E5=90=8D=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E8=A2=AB=E5=8D=A0=E7=AB=AF=E5=8F=A3=E7=9A=84?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E5=BD=93=E6=88=90=E9=99=88=E6=97=A7=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E5=88=A0=E6=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vscode/src/webhook/relay.ts | 6 +++++- vscode/src/webhook/server.test.ts | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/vscode/src/webhook/relay.ts b/vscode/src/webhook/relay.ts index 38c7f33..5b662b4 100644 --- a/vscode/src/webhook/relay.ts +++ b/vscode/src/webhook/relay.ts @@ -36,9 +36,13 @@ function processAlive(pid: number): boolean { export class PeerRegistry { constructor(private readonly dir: string) {} + /** 先写进临时文件再改名成端口号,别的窗口列登记时就不会读到还没写进 pid 的空文件、把它当成已退出的窗口删掉。 */ async add(port: number): Promise { await fsp.mkdir(this.dir, { recursive: true }) - await fsp.writeFile(path.join(this.dir, String(port)), String(process.pid)) + const file = path.join(this.dir, String(port)) + const temp = `${file}.${process.pid}.tmp` + await fsp.writeFile(temp, String(process.pid)) + await fsp.rename(temp, file) } async remove(port: number): Promise { diff --git a/vscode/src/webhook/server.test.ts b/vscode/src/webhook/server.test.ts index 4507546..e72edfe 100644 --- a/vscode/src/webhook/server.test.ts +++ b/vscode/src/webhook/server.test.ts @@ -2,10 +2,11 @@ import type { IncomingHttpHeaders } from 'node:http' import type { WebhookEvent } from './eventParser' import { Buffer } from 'node:buffer' import { createHmac } from 'node:crypto' -import { mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs' +import { promises as fsp, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { PeerRegistry } from './relay' import { handleWebhookRequest, WebhookServer } from './server' const mocks = vi.hoisted(() => ({ log: vi.fn() })) @@ -206,6 +207,26 @@ describe('同一台机器多个窗口共用端口', () => { await vi.waitFor(() => expect(readdirSync(dir)).toEqual([])) }) + it('登记文件写到一半时别的窗口来列登记,不会把它当成陈旧条目删掉', async () => { + const registry = new PeerRegistry(path.join(peerRoot, '17421')) + const remove = vi.spyOn(registry, 'remove') + const realWriteFile = fsp.writeFile.bind(fsp) + const write = vi.spyOn(fsp, 'writeFile').mockImplementation(async (file, data) => { + // 文件已经建好、pid 还没写进去的那一刻,占着端口的窗口正好来列登记。 + await realWriteFile(file, '') + await registry.ports() + await realWriteFile(file, data) + }) + try { + await registry.add(50000) + } + finally { + write.mockRestore() + } + expect(remove).not.toHaveBeenCalled() + expect(await registry.ports()).toEqual([50000]) + }) + it('同一端口重复 start 不会自己挤掉自己', async () => { const hub = open() await hub.server.start(0)