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)