mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
Orca answered a terminal query by withholding the write until a probe proved the slave's ECHO bit was clear. That was the wrong mechanism, and it is now gone: replies are written in the caller's turn and their echo is contained on the output side, where it always was. Withholding never removed an echo. The wait was bounded and always ended in a write, so the output-side projections were doing the work the whole time — including the readline rewrite, which happens with the tty already raw and which therefore no reading of the ECHO bit can predict. What withholding did add was an asynchronous write path, and that is what let one reply overtake another and land in the next program's stdin (#15559), what produced a re-entrancy inversion inside its own flush, and what four rounds of regressions have lived in. The last thing it covered was the verbatim echo of a `stty -echoctl` tty. That shape is now projected directly. It starts with ESC, so it is matched only when complete and never held as a partial: holding it would take a bare trailing ESC from the query parser and an expired hold would release it raw, so a query torn at its own ESC would never be answered. Complete-match-only is what makes the shape safe to project at all. Measured on a real pty: a cooked-mode master write is both echoed AND delivered — ECHO copies the bytes without consuming them from the slave's input queue, so a program arming raw mode with TCSANOW/TCSADRAIN (libuv's setRawMode, hence every Node agent) still reads them. Only a TCSAFLUSH switcher discards it, which it does on every terminal, none of which gates a reply on termios state. Deletes the pending-write queue, the async stty probe, the poll budget and probe rate limit, the deadline-driven flush, and the answer/ answerInOrder split. Replies now leave in call order by construction. No packaging, native or CI surface is touched.
385 lines
12 KiB
JavaScript
385 lines
12 KiB
JavaScript
import { spawn } from 'node:child_process'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { posix } from 'node:path'
|
|
import { performance } from 'node:perf_hooks'
|
|
import process from 'node:process'
|
|
|
|
import { createJiti } from 'jiti'
|
|
|
|
const DEFAULT_SAMPLES = 20
|
|
const DEFAULT_WARMUPS = 3
|
|
|
|
function parseArgs(argv) {
|
|
const options = {
|
|
distro: null,
|
|
nativeRepo: null,
|
|
mountedRepo: null,
|
|
samples: DEFAULT_SAMPLES,
|
|
warmups: DEFAULT_WARMUPS,
|
|
loginDelayMs: 0
|
|
}
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index]
|
|
const value = argv[++index]
|
|
if (!value) {
|
|
throw new Error(`${arg} requires a value`)
|
|
}
|
|
if (arg === '--distro') {
|
|
options.distro = value
|
|
} else if (arg === '--native-repo') {
|
|
options.nativeRepo = value
|
|
} else if (arg === '--mounted-repo') {
|
|
options.mountedRepo = value
|
|
} else if (arg === '--samples') {
|
|
options.samples = Number(value)
|
|
} else if (arg === '--warmups') {
|
|
options.warmups = Number(value)
|
|
} else if (arg === '--login-delay-ms') {
|
|
options.loginDelayMs = Number(value)
|
|
} else {
|
|
throw new Error(`Unknown argument: ${arg}`)
|
|
}
|
|
}
|
|
for (const [name, value, minimum] of [
|
|
['samples', options.samples, 5],
|
|
['warmups', options.warmups, 0],
|
|
['login-delay-ms', options.loginDelayMs, 0]
|
|
]) {
|
|
if (!Number.isInteger(value) || value < minimum || value > 1_000) {
|
|
throw new Error(`--${name} must be an integer between ${minimum} and 1000`)
|
|
}
|
|
}
|
|
if (!options.nativeRepo || !options.mountedRepo) {
|
|
throw new Error('--native-repo and --mounted-repo are required')
|
|
}
|
|
return options
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, {
|
|
env: options.env,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
windowsHide: true
|
|
})
|
|
const stdout = []
|
|
const stderr = []
|
|
child.stdout.on('data', (chunk) => stdout.push(chunk))
|
|
child.stderr.on('data', (chunk) => stderr.push(chunk))
|
|
child.on('error', reject)
|
|
child.on('close', (status) => {
|
|
const result = {
|
|
status,
|
|
stdout: Buffer.concat(stdout),
|
|
stderr: Buffer.concat(stderr)
|
|
}
|
|
if (status === 0 || options.allowFailure) {
|
|
resolve(result)
|
|
} else {
|
|
reject(new Error(`${command} exited ${status}: ${result.stderr.toString('utf8').trim()}`))
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function wslArgs(distro, args) {
|
|
return ['-d', distro, '--exec', ...args]
|
|
}
|
|
|
|
async function resolveDistro(requested) {
|
|
if (requested) {
|
|
return requested
|
|
}
|
|
const result = await run('wsl.exe', ['--list', '--quiet'], {
|
|
env: { ...process.env, WSL_UTF8: '1' }
|
|
})
|
|
const distro = result.stdout
|
|
.toString('utf8')
|
|
.replaceAll('\0', '')
|
|
.split(/\r?\n/)
|
|
.map((value) => value.trim())
|
|
.find(Boolean)
|
|
if (!distro) {
|
|
throw new Error('No WSL distro is installed')
|
|
}
|
|
return distro
|
|
}
|
|
|
|
function percentile(samples, value) {
|
|
const sorted = [...samples].sort((left, right) => left - right)
|
|
return sorted[Math.ceil((value / 100) * sorted.length) - 1]
|
|
}
|
|
|
|
function summarize(samples) {
|
|
return {
|
|
samples: samples.length,
|
|
medianMs: Number(percentile(samples, 50).toFixed(1)),
|
|
p95Ms: Number(percentile(samples, 95).toFixed(1)),
|
|
minMs: Number(Math.min(...samples).toFixed(1)),
|
|
maxMs: Number(Math.max(...samples).toFixed(1))
|
|
}
|
|
}
|
|
|
|
function assertRepoPath(path, expectedPrefix) {
|
|
if (!path.startsWith(expectedPrefix) || path.includes('\0') || path.includes('\n')) {
|
|
throw new Error(`Unexpected benchmark repository path: ${path}`)
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
if (process.platform !== 'win32') {
|
|
throw new Error('This benchmark requires a Windows host with WSL')
|
|
}
|
|
const options = parseArgs(process.argv.slice(2))
|
|
assertRepoPath(options.nativeRepo, '/')
|
|
assertRepoPath(options.mountedRepo, '/mnt/')
|
|
const distro = await resolveDistro(options.distro)
|
|
const jiti = createJiti(import.meta.url)
|
|
const { buildWslLoginShellCommand, quotePosixShell } = await jiti.import(
|
|
'../../src/shared/wsl-login-shell-command.ts'
|
|
)
|
|
|
|
const loginProbe = buildWslLoginShellCommand(
|
|
`printf '\\n__ORCA_PATH__%s\\n__ORCA_GIT__%s\\n__ORCA_HOME__%s\\n' "$PATH" "$(command -v git)" "$HOME"`
|
|
)
|
|
const probe = await run('wsl.exe', wslArgs(distro, ['/bin/sh', '-lc', loginProbe]))
|
|
const probeText = probe.stdout.toString('utf8')
|
|
const loginPath = /__ORCA_PATH__(.*)/.exec(probeText)?.[1]?.trim()
|
|
const gitPath = /__ORCA_GIT__(.*)/.exec(probeText)?.[1]?.trim()
|
|
const loginHome = /__ORCA_HOME__(.*)/.exec(probeText)?.[1]?.trim()
|
|
if (!loginPath || !gitPath?.startsWith('/') || !loginHome?.startsWith('/')) {
|
|
throw new Error(`Could not resolve login-shell Git environment: ${probeText.trim()}`)
|
|
}
|
|
const outputMarker = `__ORCA_GIT_OUTPUT_${process.pid}__\n`
|
|
|
|
const runGit = async (mode, repo, args) => {
|
|
const startedAt = performance.now()
|
|
let result
|
|
if (mode === 'login') {
|
|
const command = [
|
|
`cd ${quotePosixShell(repo)} &&`,
|
|
`printf ${quotePosixShell(outputMarker)} &&`,
|
|
`LC_ALL=C LANG=C ${quotePosixShell('git')}`,
|
|
...args.map(quotePosixShell)
|
|
].join(' ')
|
|
const delay = options.loginDelayMs > 0 ? `sleep ${options.loginDelayMs / 1_000}; ` : ''
|
|
const script = `${delay}${buildWslLoginShellCommand(command)}`
|
|
result = await run('wsl.exe', wslArgs(distro, ['/bin/sh', '-lc', script]))
|
|
const markerOffset = result.stdout.indexOf(outputMarker)
|
|
if (markerOffset === -1) {
|
|
throw new Error('Login shell did not emit the Git output marker')
|
|
}
|
|
result.stdout = result.stdout.subarray(markerOffset + Buffer.byteLength(outputMarker))
|
|
} else {
|
|
result = await run(
|
|
'wsl.exe',
|
|
wslArgs(distro, [
|
|
'/usr/bin/env',
|
|
`PATH=${loginPath}`,
|
|
`HOME=${loginHome}`,
|
|
'LC_ALL=C',
|
|
'LANG=C',
|
|
gitPath,
|
|
'-C',
|
|
repo,
|
|
...args
|
|
])
|
|
)
|
|
}
|
|
return { ...result, elapsedMs: performance.now() - startedAt }
|
|
}
|
|
|
|
const measure = async (label, runArm) => {
|
|
for (let index = 0; index < options.warmups; index += 1) {
|
|
await runArm('login')
|
|
await runArm('fast')
|
|
}
|
|
const samples = { login: [], fast: [] }
|
|
for (let index = 0; index < options.samples; index += 1) {
|
|
const order = index % 2 === 0 ? ['login', 'fast'] : ['fast', 'login']
|
|
const results = []
|
|
for (const mode of order) {
|
|
const result = await runArm(mode)
|
|
samples[mode].push(result.elapsedMs)
|
|
results.push(result)
|
|
}
|
|
if (
|
|
results[0].status !== results[1].status ||
|
|
!results[0].stdout.equals(results[1].stdout) ||
|
|
!results[0].stderr.equals(results[1].stderr)
|
|
) {
|
|
throw new Error(
|
|
`${label} correctness mismatch between ${order.join(' and ')} arms:\n` +
|
|
`${order[0]}=${JSON.stringify({ stdout: results[0].stdout.toString('utf8'), stderr: results[0].stderr.toString('utf8') })}\n` +
|
|
`${order[1]}=${JSON.stringify({ stdout: results[1].stdout.toString('utf8'), stderr: results[1].stderr.toString('utf8') })}`
|
|
)
|
|
}
|
|
}
|
|
const login = summarize(samples.login)
|
|
const fast = summarize(samples.fast)
|
|
return {
|
|
login,
|
|
fast,
|
|
medianSpeedup: Number((login.medianMs / fast.medianMs).toFixed(2)),
|
|
medianSavedMs: Number((login.medianMs - fast.medianMs).toFixed(1))
|
|
}
|
|
}
|
|
|
|
const benchmarkRepo = async (repo) => {
|
|
const branch = (
|
|
await runGit('fast', repo, ['symbolic-ref', '--quiet', '--short', 'HEAD'])
|
|
).stdout
|
|
.toString('utf8')
|
|
.trim()
|
|
const fixtureName = `.orca-wsl-git-shell-benchmark-${process.pid}-${randomUUID()}.txt`
|
|
const fixturePath = posix.join(repo, fixtureName)
|
|
const prepareStage = async () => {
|
|
await runGit('fast', repo, ['reset', '--quiet', '--', fixtureName])
|
|
await run(
|
|
'wsl.exe',
|
|
wslArgs(distro, [
|
|
'/bin/sh',
|
|
'-c',
|
|
'printf "benchmark\\n" > "$1"',
|
|
'orca-wsl-git-shell-benchmark',
|
|
fixturePath
|
|
])
|
|
)
|
|
}
|
|
const cleanupStage = async () => {
|
|
await runGit('fast', repo, ['reset', '--quiet', '--', fixtureName])
|
|
await run('wsl.exe', wslArgs(distro, ['/bin/rm', '-f', '--', fixturePath]))
|
|
}
|
|
|
|
const operations = {
|
|
status: [
|
|
'-c',
|
|
'core.quotePath=false',
|
|
'status',
|
|
'--porcelain=v2',
|
|
'--branch',
|
|
'--untracked-files=all'
|
|
],
|
|
numstat: ['-c', 'core.quotePath=false', 'diff', '-z', '--numstat', '-M'],
|
|
upstream: [
|
|
'for-each-ref',
|
|
'--format=%(refname)%00%(upstream)%00%(upstream:short)',
|
|
'--count=1',
|
|
`refs/heads/${branch}`
|
|
]
|
|
}
|
|
const result = {}
|
|
for (const [name, args] of Object.entries(operations)) {
|
|
result[name] = await measure(`${repo}:${name}`, (mode) => runGit(mode, repo, args))
|
|
}
|
|
const created = await run(
|
|
'wsl.exe',
|
|
wslArgs(distro, [
|
|
'/bin/sh',
|
|
'-c',
|
|
'set -C; printf "benchmark\\n" > "$1"',
|
|
'orca-wsl-git-shell-benchmark',
|
|
fixturePath
|
|
]),
|
|
{ allowFailure: true }
|
|
)
|
|
if (created.status !== 0) {
|
|
throw new Error(`Could not exclusively create benchmark fixture: ${fixturePath}`)
|
|
}
|
|
try {
|
|
result.stageRefresh = await measure(`${repo}:stage-refresh`, async (mode) => {
|
|
await prepareStage()
|
|
const startedAt = performance.now()
|
|
await runGit('login', repo, ['add', '--', fixtureName])
|
|
const status = await runGit(mode, repo, operations.status)
|
|
const numstat = await runGit(mode, repo, [
|
|
'-c',
|
|
'core.quotePath=false',
|
|
'diff',
|
|
'-z',
|
|
'--cached',
|
|
'--numstat',
|
|
'-M'
|
|
])
|
|
return {
|
|
status: 0,
|
|
stdout: Buffer.concat([status.stdout, numstat.stdout]),
|
|
stderr: Buffer.concat([status.stderr, numstat.stderr]),
|
|
elapsedMs: performance.now() - startedAt
|
|
}
|
|
})
|
|
} finally {
|
|
await cleanupStage()
|
|
}
|
|
return result
|
|
}
|
|
|
|
const verifyProductionRunner = async (repo) => {
|
|
const windowsRepo = (
|
|
await run('wsl.exe', wslArgs(distro, ['/usr/bin/wslpath', '-w', repo]))
|
|
).stdout
|
|
.toString('utf8')
|
|
.trim()
|
|
const args = [
|
|
'-c',
|
|
'core.quotePath=false',
|
|
'status',
|
|
'--porcelain=v2',
|
|
'--branch',
|
|
'--untracked-files=all'
|
|
]
|
|
const [{ gitExecFileAsync }, { getWslGitReadEnvironment }, expected] = await Promise.all([
|
|
jiti.import('../../src/main/git/runner.ts'),
|
|
jiti.import('../../src/main/git/wsl-git-read-environment.ts'),
|
|
runGit('fast', repo, args)
|
|
])
|
|
await getWslGitReadEnvironment(distro)
|
|
const startedAt = performance.now()
|
|
const actual = await gitExecFileAsync(args, {
|
|
cwd: windowsRepo,
|
|
preferWslDirectGit: true,
|
|
wslDistro: distro
|
|
})
|
|
if (
|
|
!Buffer.from(actual.stdout).equals(expected.stdout) ||
|
|
!Buffer.from(actual.stderr).equals(expected.stderr)
|
|
) {
|
|
throw new Error(`Production runner output mismatch for ${windowsRepo}`)
|
|
}
|
|
return {
|
|
windowsRepo,
|
|
environmentPrimed: true,
|
|
elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
|
|
outputBytes: expected.stdout.length
|
|
}
|
|
}
|
|
|
|
const result = {
|
|
distro,
|
|
gitPath,
|
|
gitVersion: (await runGit('fast', options.nativeRepo, ['--version'])).stdout
|
|
.toString('utf8')
|
|
.trim(),
|
|
samples: options.samples,
|
|
warmups: options.warmups,
|
|
injectedLoginDelayMs: options.loginDelayMs,
|
|
loginProbePreambleBytes: Buffer.byteLength(probeText.split('__ORCA_PATH__', 1)[0]),
|
|
guestProcessShape: {
|
|
login: 'sh -> interactive login shell -> git',
|
|
fast: 'env -> git'
|
|
},
|
|
native: await benchmarkRepo(options.nativeRepo),
|
|
mounted: await benchmarkRepo(options.mountedRepo),
|
|
productionRunnerVerification: {
|
|
native: await verifyProductionRunner(options.nativeRepo),
|
|
mounted: await verifyProductionRunner(options.mountedRepo)
|
|
}
|
|
}
|
|
console.log(JSON.stringify(result, null, 2))
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : String(error))
|
|
process.exitCode = 1
|
|
})
|