Skip to content

Commit ecbb18d

Browse files
committed
fix: don't time out bb socket startup while the bb process is alive
The socket backend gave bb a hard 5s budget shared between socket file creation and connect, so a loaded prover spawning many bb processes at once (epoch top-tree) could fail startup with 'Timeout connecting to bb socket: unknown' before a single connect attempt was made, and the resulting plain Error was reported retry=false, costing the epoch. The backend now waits as long as the bb process is alive, failing fast with the real cause if it dies, with a single generous 60s backstop for a wedged process. Startup failures are wrapped as retryable ProvingErrors and the bb_prover wrap sites preserve the retry flag.
1 parent 72666f8 commit ecbb18d

6 files changed

Lines changed: 230 additions & 199 deletions

File tree

barretenberg/ts/src/bb_backends/node/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export async function createAsyncBackend(
2626
throw new Error('Native backend requires bb binary.');
2727
}
2828
logger(`Using native Unix socket backend: ${bbPath}`);
29-
const socket = new BarretenbergNativeSocketAsyncBackend(bbPath, options.threads, options.logger, options.unref);
29+
const socket = await BarretenbergNativeSocketAsyncBackend.new(
30+
bbPath,
31+
options.threads,
32+
options.logger,
33+
options.unref,
34+
);
3035
return new Barretenberg(socket, options);
3136
}
3237

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { jest } from '@jest/globals';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import { BarretenbergNativeSocketAsyncBackend } from './native_socket.js';
6+
7+
jest.setTimeout(30_000);
8+
9+
// Echo server speaking the bb msgpack socket protocol (4-byte LE length prefix), started after
10+
// an optional delay to simulate bb's startup time on a loaded machine.
11+
const ECHO_SERVER_JS = `
12+
const net = require('net');
13+
const socketPath = process.argv[2];
14+
const server = net.createServer(sock => {
15+
let buf = Buffer.alloc(0);
16+
sock.on('data', d => {
17+
buf = Buffer.concat([buf, d]);
18+
while (buf.length >= 4) {
19+
const len = buf.readUInt32LE(0);
20+
if (buf.length < 4 + len) break;
21+
const payload = buf.subarray(4, 4 + len);
22+
const out = Buffer.alloc(4);
23+
out.writeUInt32LE(payload.length, 0);
24+
sock.write(out);
25+
sock.write(payload);
26+
buf = buf.subarray(4 + len);
27+
}
28+
});
29+
});
30+
server.listen(socketPath);
31+
`;
32+
33+
// A fake bb binary: a bash script that optionally sleeps, then runs the echo server on the
34+
// socket path bb receives via `msgpack run --input <path>` ($4).
35+
function writeFakeBb(startupDelaySecs: number): string {
36+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-bb-'));
37+
const serverJs = path.join(dir, 'echo_server.cjs');
38+
fs.writeFileSync(serverJs, ECHO_SERVER_JS);
39+
const file = path.join(dir, 'bb');
40+
const sleep = startupDelaySecs > 0 ? `sleep ${startupDelaySecs}\n` : '';
41+
fs.writeFileSync(file, `#!/bin/bash\n${sleep}exec node ${serverJs} "$4"\n`, { mode: 0o755 });
42+
return file;
43+
}
44+
45+
function writeFakeBbScript(script: string): string {
46+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-bb-'));
47+
const file = path.join(dir, 'bb');
48+
fs.writeFileSync(file, script, { mode: 0o755 });
49+
return file;
50+
}
51+
52+
describe('BarretenbergNativeSocketAsyncBackend', () => {
53+
it('connects and echoes when bb starts promptly', async () => {
54+
const fakeBb = writeFakeBb(0);
55+
const backend = await BarretenbergNativeSocketAsyncBackend.new(fakeBb);
56+
const response = await backend.call(new Uint8Array([1, 2, 3, 4]));
57+
expect(response).toEqual(new Uint8Array([1, 2, 3, 4]));
58+
await backend.destroy();
59+
});
60+
61+
it('connects even when bb takes longer than 5s to create its socket', async () => {
62+
const fakeBb = writeFakeBb(7);
63+
const backend = await BarretenbergNativeSocketAsyncBackend.new(fakeBb);
64+
const response = await backend.call(new Uint8Array([42]));
65+
expect(response).toEqual(new Uint8Array([42]));
66+
await backend.destroy();
67+
});
68+
69+
it('fails with the exit cause when bb dies before creating its socket', async () => {
70+
const fakeBb = writeFakeBbScript(`#!/bin/bash\nexit 17\n`);
71+
await expect(BarretenbergNativeSocketAsyncBackend.new(fakeBb)).rejects.toThrow(
72+
/exited before socket connection was established \(code=17/,
73+
);
74+
});
75+
76+
it('fails with the spawn error when the bb binary does not exist', async () => {
77+
await expect(BarretenbergNativeSocketAsyncBackend.new('/nonexistent/bb-binary')).rejects.toThrow(
78+
/Native backend process error/,
79+
);
80+
});
81+
});

0 commit comments

Comments
 (0)