)
Herdr Unix PTY实现fd、actor、backend三层拆解完整指南【免费下载链接】herdrthe runtime your coding agents live on项目地址: https://gitcode.com/GitHub_Trending/her/herdrHerdr 是一个面向编码 Agent 的终端运行时terminal multiplexer它拥有你每个 AI Agent 的终端让 Claude Code、Codex、Cursor 等工具可以在后台持续运行。要做到这一点底层必须有一个稳定、非阻塞、可交接的 Unix PTY伪终端子系统。这篇文章带你拆解 src/pty/ 目录下的三层设计fd 工具层、actor 调度层、backend 派生层看看一个生产级的 PTY 是如何用 Rust 搭起来的。一、为什么终端复用器需要专门的 PTY 层对新手来说PTYpseudo terminal可以理解为模拟终端操作系统提供一对 fd——master给父进程Herdr读写slave作为子进程比如你的 Agent CLI的标准输入输出。子进程以为自己跑在真实终端里于是vim、claude这些 TUI 程序才能正常渲染光标、颜色和尺寸。Herdr 的 PTY 子系统只包含 4 个文件职责划分非常清晰文件角色一句话职责src/pty/fd.rsfd 工具层封装 dup、非阻塞、poll、wake pipe 等底层 fd 操作src/pty/actor/unix.rsactor 调度层每个面板一条专属 I/O 线程处理输入、输出、resize、交接src/pty/backend/unix.rsbackend 派生层用 portable-pty 打开 PTY 对并启动子进程src/pty/mod.rs模块入口按平台条件编译暴露上述模块这种小文件、强边界的拆分是理解整篇文章的关键底层能力可单测、调度逻辑可隔离、平台差异被收敛到一处。二、第一层fd.rs——把裸文件描述符变成可靠的工具fd.rs 只有 200 多行却覆盖了 Unix 下 PTY 编程最棘手的几个点1. dup CLOEXEC防止 fd 泄漏进子进程duplicate_cloexec_fd 复制 fd 的同时打上FD_CLOEXEC标志。为什么重要如果 master fd 泄漏到子进程子进程退出后 Herdr 可能拿不到预期的 EOF 信号。backend/unix.rs 的测试 专门断言派生完成后父进程里只多出一个 PTY fd这是一个非常严格的泄漏检查。2. 非阻塞 wake pipe不阻塞 UI 的核心机制set_nonblocking 给 master fd 设置O_NONBLOCK读写永不卡死线程create_wake_pipe 创建一对非阻塞 pipe写入端克隆给所有调用者读取端交给 actor 线程。这个 wake pipe 是整个设计的点睛之笔actor 线程平时阻塞在poll上等待 PTY 有数据零 CPU 占用任何外部线程想让它立刻干活只需往 pipe 写 1 个字节——poll立即返回。fd.rs 顶部注释 明确说idle 超时只是兜底正常响应靠 PTY 和 wake 的就绪事件驱动。3. poll 的正确姿势poll_pty_and_wake 同时轮询 PTY fd 和 wake fd处理了三类容易写错的细节EINTR信号打断时按 deadline 重算剩余超时再重试POLLNVAL/POLLERR视为 PTY 异常直接报 BrokenPipe返回结构体 PtyWakeReadiness把可读 / 可写 / wake 就绪三种信号打包给上层。另外 resize_pty_fd 通过TIOCSWINSZ修改行列尺寸并同时换算像素尺寸供支持网格协议的终端使用。三、第二层actor——每个面板一条专属 I/O 线程actor/unix.rs 是三层中最核心的一层。它解决一个朴素的问题PTY 读写可能阻塞UI 主循环绝不能被它拖住——于是每个面板pane派生一条名为herdr-pty-{pane_id}的专属线程spawn_inner所有 I/O 串行发生在这条线程里天然免锁竞争。对外接口一个 Handle三种通道PtyIoActorHandle 持有三样东西data 通道1024 容量用户按键WriteUserInput、带确认的输入提交SubmitUserInputcontrol 通道resize、handoff交接、shutdown 等低频控制命令wake 写入端每次入队命令后调用 wake_actor确保 actor 立刻醒来处理。调用方全部使用try_send非阻塞入队队列满就返回WouldBlock绝不让 UI 线程等待——这是典型的背压backpressure设计。主循环一个 while 循环看懂全部run() 每轮做四件事排空 control / data 命令 ↓ 应用 pending 控制resize / nudge / 终端响应 ↓ 尝试 flush 待写数据非阻塞写多少算多少 ↓ poll(PTY wake pipe)按就绪状态读数据或写数据两个值得记住的工程细节写入是半完成状态机flush_pending_writes_once 用current_write_offset记录每块数据的写进度WouldBlock时直接返回下轮接着写。这样几 MB 的大块输入测试里甚至测了 4MB也能安全落盘输入提交是三段式SubmitUserInput会先写文本 → 等待 delay → 再写回车SubmissionPhase 状态机保证即使延迟期间有别的输入排队回车也一定在文本之后、排队输入之前避免半个 prompt的竞态。Handoff把运行中的终端安全移交给别的进程这是 Herdr detach 不停工作能力的地基。begin_handoff 让 actor 进入Quiesced静默状态先排空命令队列里已入队的写入在 2 秒超时HANDOFF_DRAIN_TIMEOUT内把 pending 数据全部刷进 PTY状态切到Quiesced之后 duplicate_for_handoff 才允许复制 master fd 给新宿主失败可 rollback_handoff 回滚或 release_after_commit 彻底释放。状态机Running → Quiesced → ReleasedActorState配合 单元测试矩阵覆盖了并发 handoff 拒绝、静默期间停止读取、回滚后恢复等边界。四、第三层backend——用 portable-pty 派生进程backend/unix.rs 非常短它是 Unix 与 Windows 的平台分叉点Windows 走 actor.rs 内的 ConPTY 实现spawn_with_portable_pty 的流程native_pty_system().openpty()按初始行列尺寸打开 PTY 对取出 master fddup 一份带 CLOEXEC 的副本交给 actor对应 fd 层工具在 slave 端spawn_command(cmd)启动目标进程drop 原始 pair父进程只保留SpawnedPty { master_fd, child }。返回的child句柄让 Herdr 可以追踪进程存活、做前台进程组查询ForegroundProcessGroup命令这是判断 Agent 是 working 还是 blocked的底层依据。五、三层如何协作一次按键的完整旅程把三层串起来你在面板里敲一个字符的旅程是UI 层拿到 PtyIoActorHandletry_write_user_input非阻塞入队并写 1 字节到wake pipefd 层面板专属actor 线程的poll因 wake fd 就绪立即返回无需等 1 秒 idle 超时actor 把字节追加进pending_writespoll报 PTY 可写后分块刷出进度用偏移量记录Agent 输出经 slave 端流回 master fdactor 非阻塞read8KB 缓冲通过on_read回调交给上层渲染若此时正在 detach/handoffbackend 派生的 child 与 master fd 保证会话在静默排空后完整移交而不是被杀死。六、从源码里能学到的 5 个实战经验能用 fd 工具函数就绝不裸写unsafefd.rs 把所有 unsafe 收进 6 个小函数上层只见安全 APIwake pipe 是 epoll 的手动唤醒方案poll阻塞 1 字节唤醒比忙轮询省电得多串行化 I/O 是最简单的并发方案每面板一条线程命令按序执行测试里 背压期间仍持续读输出的用例 证明读写互不拖累超时全部可回滚handoff、提交、release 每个等待都有 reply channel 超时失败路径清晰测试即文档actor 的 20 余个测试 用 Unix socket pair 假装 PTY覆盖 EOF、背压、竞态、唤醒时序——这套测试本身就是最好的 API 说明。七、延伸阅读在仓库里继续深挖想读什么去哪里PTY 模块总览src/pty/mod.rsfd 底层工具与 wake pipesrc/pty/fd.rsUnix actor 主循环与 handoffsrc/pty/actor/unix.rsWindows ConPTY actor 对照src/pty/actor.rsportable-pty 进程派生src/pty/backend/unix.rs面板终端状态与渲染src/pane/terminal/会话恢复与持久化src/persist/官方概念文档docs/next/website/src/content/docs/concepts.mdx一句话总结Herdr 的 Unix PTY 实现把操作系统 fd 的脏活dup/非阻塞/poll沉到 fd 层把线程与状态机的调度交给 actor 层把跨平台进程派生收敛到 backend 层——三层各自小到可以通读合在一起却支撑起了 Agent 长时间后台运行、detach 不断线这些产品级能力。【免费下载链接】herdrthe runtime your coding agents live on项目地址: https://gitcode.com/GitHub_Trending/her/herdr创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考