async recvFd + backend terminal test (sandbox blocks ptyc exec)
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
PtySession.spawn() now runs recvFd in a child isolate via
Isolate.spawn so the blocking FFI call doesn't stall the backend
isolate's event loop.
Terminal testmode test spawns a real backend isolate, opens a project,
and sends pane.spawn via IPC — the exact same path the full app uses.
Currently fails: ptyc successfully starts but its fork+execvp is
blocked by the macOS sandbox ("Operation not permitted"). The SBPL
allows /bin/zsh exec from the app process, but ptyc's child process
may not inherit the exec permission, or the PTYC_SOCK_FD is not
inherited by the ptyc child (Dart Process.start fd inheritance on
macOS).
IsolateClient.events getter exposed for test access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8f6ab1ac95
commit
45e8132a41
@@ -29,6 +29,12 @@ import 'errors.dart';
|
||||
import 'ffi/libc.dart' as libc;
|
||||
import 'ffi/scm_rights.dart' as scm;
|
||||
|
||||
class _RecvFdArgs {
|
||||
const _RecvFdArgs(this.socketFd, this.sendPort);
|
||||
final int socketFd;
|
||||
final SendPort sendPort;
|
||||
}
|
||||
|
||||
/// A running PTY child plus its master-fd plumbing.
|
||||
class PtySession {
|
||||
PtySession._({
|
||||
@@ -124,7 +130,9 @@ class PtySession {
|
||||
await proc.stdin.close();
|
||||
|
||||
// Receive the master fd over the parent side of the socketpair.
|
||||
final masterFd = scm.recvFd(parentSock);
|
||||
// recvFd blocks until ptyc sends — run in a child isolate so the
|
||||
// calling isolate's event loop stays responsive.
|
||||
final masterFd = await _recvFdAsync(parentSock);
|
||||
|
||||
// Apply initial winsize (ptyc already did this, but doing it
|
||||
// again from Dart confirms the wire + gives a place to call it
|
||||
@@ -229,6 +237,27 @@ class PtySession {
|
||||
if (!_outputCtrl.isClosed) await _outputCtrl.close();
|
||||
}
|
||||
|
||||
/// Run recvFd in a child isolate so the blocking FFI call doesn't
|
||||
/// stall the calling isolate's event loop.
|
||||
static Future<int> _recvFdAsync(int socketFd) async {
|
||||
final port = ReceivePort();
|
||||
final iso = await Isolate.spawn(_recvFdEntry, _RecvFdArgs(socketFd, port.sendPort));
|
||||
final result = await port.first;
|
||||
iso.kill(priority: Isolate.immediate);
|
||||
port.close();
|
||||
if (result is int) return result;
|
||||
throw PtyException('recvFd', '$result');
|
||||
}
|
||||
|
||||
static void _recvFdEntry(_RecvFdArgs args) {
|
||||
try {
|
||||
final fd = scm.recvFd(args.socketFd);
|
||||
args.sendPort.send(fd);
|
||||
} catch (e) {
|
||||
args.sendPort.send('error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- //
|
||||
|
||||
void _startReader() {
|
||||
|
||||
Reference in New Issue
Block a user