SharkTTY Desk 0.9.0 今天发布——新功能 页有面向用户的完整说明,你也可以 在这里下载。 我们在那边打头阵的功能是访客分享链接:用一个链接或二维码就能邀请别人进入你的 Mac, 不需要账号。这篇文章讲的是另一个故事——藏在幕后、远不如新功能适合演示的一次追凶: 整个应用彻底冻住,风火轮转个不停,背后的原因花了不少功夫才揪出来。三个各自独立的 根因,三个分别的修复,全部落进这个版本。
根因一:UI 线程上的一次阻塞式发送
你在 Desk 里查看另一台 Mac 时,画面和输入都走一条 WebRTC 数据通道,而在这条通道上 发送的代码是同步执行的——而且跑在 UI 线程上。正常情况下这完全不可见:发送几乎瞬间 返回。但 WebRTC 的 SCTP 传输层维护着一个固定大小的发送缓冲区,128 KiB,一旦对端跟不上、 缓冲区被填满——也就是背压——发送就可能阻塞,无限期地阻塞。而当这次阻塞发生在 UI 线程上, 受影响的就不只是一路视频流的卡顿,而是整个应用:菜单打不开,窗口不重绘,风火轮一直转, 直到上游终于让步。WebSocket 和 QUIC 传输路径从来没有这个问题——它们不走同一条阻塞式调用。
修复很直接:发送现在是非阻塞的。我们带走的教训很朴素:「它总是很快返回」是一个假设, 不是保证,UI 线程永远不该是那个去验证这个假设的角色。任何最坏情况是「阻塞到远端把 缓冲区排空为止」的调用,都不该同步跑在渲染循环里——不管这个最坏情况在实际中显得多罕见。
根因二:没人排空的管道
第二个根因是一个古老、众所周知、却很容易在不经意间重新引入的 Unix 陷阱。子进程输出
管道的内核缓冲区很小——大约 64 KB——如果没有人读取它,缓冲区会被填满,子进程在尝试
继续写入时被阻塞,而任何等待这个子进程退出的调用也会跟着一起阻塞:
waitUntilExit
永远不会返回,因为它等待的进程正卡在往一条没人监听的管道里写数据。
恰好符合这个形状的一次调用,在锁屏捕获辅助功能开启时,每 1.5 秒就在主线程上跑一次—— 一个周期性检查,一旦它的目标把管道填满,就可能把整个应用卡死在一个时钟节拍上,而且 不是一次,是反复发生。这正是它格外惹人厌的原因:不是一个罕见的边缘情况,而是一个 周期性定时器,精准地撞上了一个死锁。
修复分两部分:我们本来就不打算读取的输出,现在直接送去空设备而不是管道;我们确实 要消费的输出,则会在等待进程之前被排空。教训是:你创建的每一条管道都是一份「要去 读它」的契约——如果不打算读,就别创建;如果要读,就在阻塞等待写入方之前把它排空。
根因三:用得越久越慢的历史记录
第三个根因与其说是卡死,不如说是一场缓慢的失血:长时间的 mosh 终端会话一开始同步得 好好的,跑得越久就越明显地变慢。原因是算法层面的——保持终端历史同步的开销随历史 记录量呈平方级增长,O(H²)。不是锁,也不是泄漏,只是一种对五分钟会话毫无问题、对 五小时会话却越来越昂贵的做法——直到从外面看,它和另外两个根因造成的冻结看起来 一模一样。
修复用结构共享重构了历史记录,让长时间运行的会话保持快速,而不是随着时间越跑越慢。 教训是:平方级开销在任何快速测试和任何演示里都是隐形的,因为只有当会话跑得足够久、 H 变得足够大时它才会显形——这意味着发现它的永远是你真正的用户,在他们真正最长的 会话里,除非你主动去找它。
分享链接,现在是 https 形式
0.9.0 打头阵的功能是访客分享链接:在 Mac 上的「分享」面板里,你可以用一个链接或 二维码邀请别人进来,由主机一方控制过期时间、同时在线的查看人数上限、是否只读、 以及可选的账号白名单——每个分享都可以随时撤销。链接本身从不携带你 Mac 的永久连接 密钥:访客兑换的是云端签发的短时票据,而验证并执行过期、并发上限、只读模式和撤销的, 是你 Mac 上的 agent,而不是云端。还有一个早期、免费阶段的选项,可以把一次分享做成 按小时的独占预约:访客挑一个空闲的、以 UTC 对齐的小时时段,在那个时段内独占你的 Mac。
0.9.0 改变的是链接本身的形态:现在是一个 https 网址——
https://safafish.com/s/#s=<code>&n=<name>
——而不是一个原始的
ttyshare://
地址。https 链接能在任何设备的任何浏览器里打开,进入一个
中转页,
提供「在 SharkTTY 中打开」、在应用未安装时指向 App Store、并显示一个可供 iPhone
或 iPad 扫描的二维码。把这个 https 链接粘贴回已经装好应用的设备上,同样能正常往返。
有一点需要说清楚它现在还做不到什么:点击链接并不会自动瞬间打开 iOS 应用——那种交接
依赖一个我们还没发布的应用版本。今天你能得到的是:一个能在任何浏览器里打开、把链接
交给 SharkTTY 的页面——这已经比一个你一半访客的设备根本不认识的 URI scheme 友好
得多了,可以放心地贴进聊天里。
0.9.0 里的两个小改动
两个值得一提的小改动。在 Desk 的终端里——不论是 SSH、本地 shell 还是 mosh——按住 Option 点击现在会把 shell 光标移到点击处,和 Terminal.app、iTerm2 玩的是同一招: 通过合成方向键实现,主缓冲区和全屏应用的备用屏幕里都能用。放大镜(loupe)还有一个 小的精度 bug:一个节流窗口会丢弃一次拖拽的最后一次移动,导致放大镜有时会停在光标 实际停下位置的稍后一点。现在静止位置总能拿到最后的更新,放大内容会精确落在准星下方。
对以上任何内容有问题或 bug 报告?反馈板是最快找到我们的方式。