--- article.previous.md
+++ article.md
@@ -1,55 +1,61 @@
-# 《Mac is running out of time》全文(校正版)
+# 《Mac is running out of time》
> 来源:YouTube [The PrimeTime]
> 时长:00:10:24
> 视频ID:Q9GAJ_ka4l4
> 链接:https://www.youtube.com/watch?v=Q9GAJ_ka4l4
-> 说明:本文已由 AI 进行全文校正,修正了语音识别错误和口语填充词。
+> 说明:以下文字按视频原讲述顺序整理,口语、重复和明显噪声已做轻度收束。
---
-有一类 bug,仅凭其表现条件就能立刻判断出它的本质。举个典型的例子:如果你发现程序出了问题,只要在代码里加一个 console.log 打印语句,bug 就消失了;而当你删掉这个打印语句后,bug 又立刻重现——这几乎可以确定是一个与时间相关的 bug。因为打印操作本身会带来性能开销,稍微拖慢了程序运行速度,恰好让原本因时序问题暴露的缺陷不再触发。一旦移除打印,系统恢复到原来的执行节奏,bug 便重新出现。这类现象在软件开发中非常著名,被称为“Hyzen Bug”——即一种在被观察或调试时就会消失或行为改变的软件缺陷。
+## 49天的死亡倒计时:Mac的隐藏定时炸弹
-简单来说,仅仅去“看”它,它就自己不见了。这类问题通常都和时间有关。还有一类 bug,如果我只说出一个数字,可能有些人立刻就会冒冷汗。准备好了吗。49天、17小时、2分钟、47秒。
+有一类 bug,仅凭其表现条件就能立刻判断出它的本质。举个典型的例子:如果你发现程序出了问题,只要在代码里加一个 console.log 打印语句,bug 就消失了。而当你删掉这个打印语句后,bug 又立刻重现——这几乎可以确定是一个与时间相关的 bug。因为打印操作本身会带来性能开销,稍微拖慢了程序运行速度,恰好让原本因时序问题暴露的缺陷不再触发。一旦移除打印,系统恢复到原来的执行节奏,bug 便重新出现。
-我知道大多数人听到这个数字一脸茫然,但希望看完这段视频后,你也会感到一阵寒意——因为这是一个极其糟糕、令人窒息的时间点,而且你应该立刻意识到问题所在。在深入之前,先来个小提醒:别再让你的 AI 代理在电脑上随意联网了。这是最容易自取灭亡的操作。因此,今天视频的赞助商 Kernel.sh 正是为此而生——一个超快、开源的 AI 代理基础设施,让你的代理能真正访问互联网。它能在不到 30 毫秒内启动一个甚至上千个云端浏览器,认证也自动处理。
+这类现象在软件开发中非常著名,被称为“Hyzen Bug”——即一种在被观察或调试时就会消失或行为改变的软件缺陷。简单来说,仅仅去“看”它,它就自己不见了。这类问题通常都和时间有关。
-目前已有超过 3,000 支团队在生产环境中使用,包括 Framer 和 Cash App。别再给你的代理“装弱化器”了,让他们用上真正的浏览器吧。现在就去 kernel.sh,让他们自由上网。欢迎回来。我想向大家展示一份非常精彩的技术文档,可惜最近网站改版,导致所有文字变成白色,即使我手动调整 CSS 也无法改变,系统就是拒绝变色。
+还有一类 bug,如果我只说出一个数字,可能有些人立刻就会冒冷汗。准备好了吗?49天、17小时、2分钟、47秒。我知道大多数人听到这个数字一脸茫然,但希望看完这段视频后,你也会感到一阵寒意——因为这是一个极其糟糕、令人窒息的时间点,而且你应该立刻意识到问题所在。
-所以只能打开阅读器来看。这篇文章标题是《我们在 Mac OS TCP 网络中发现了一个倒计时炸弹》,它会在精确的 49 天、17 小时、2 分钟、47 秒后引爆。这个发现来自一家名为 Photon 的公司,他们用一批 Mac 电脑持续监控 iMessage 服务。奇怪的是,这些 Mac 每次运行到 exactly 49 天 17 小时 2 分钟 47 秒时,就会突然崩溃——内存开始失控飙升,最终完全无法建立新的 TCP 连接。这听起来很诡异,对吧。
+在深入之前,先来个小提醒:别再让你的 AI 代理在电脑上随意联网了。这是最容易自取灭亡的操作。因此,今天视频的赞助商 Kernel.sh 正是为此而生——一个超快、开源的 AI 代理基础设施,让你的代理能真正访问互联网。它能在不到 30 毫秒内启动一个甚至上千个云端浏览器,认证也自动处理。目前已有超过 3,000 支团队在生产环境中使用,包括 Framer 和 Cash App。别再给你的代理“装弱化器”了,让他们用上真正的浏览器吧。现在就去 kernel.sh,让他们自由上网。
-事实上,这个问题并非只存在于 Photon 公司的设备,而是存在于所有 Mac 之中。只要你让一台 Mac 连续开机超过 49 天 17 小时 2 分钟 47 秒,就一定会遇到同样的问题:当 TCP 连接数达到某个临界点后,系统将彻底无法创建新连接,整台电脑变得像一块“砖头”一样瘫痪。要理解这一点,首先要了解 TCP 连接的基本原理。当你从电脑发起一个 TCP 连接时,会占用本地的一个端口。一台 Mac 有大约 65,000 个可用端口,但实际用于主动连接的只有约一半。
+我想向大家展示一份非常精彩的技术文档,可惜最近网站改版,导致所有文字变成白色,即使我手动调整 CSS 也无法改变,系统就是拒绝变色。只能打开阅读器来看。这篇文章标题是《我们在 Mac OS TCP 网络中发现了一个倒计时炸弹》,它会在精确的 49 天、17 小时、2 分钟、47 秒后引爆。
-当连接关闭后,这个端口并不会立即可用,必须等待一小段时间——这就是所谓的“TIME_WAIT”状态。为什么需要等待。因为互联网很大,数据包可能在传输途中延迟、绕路,甚至在云中“迷路”。如果端口被立刻重用,那些延迟到达的数据包可能会误入新连接,造成混乱和数据损坏,从而彻底破坏连接。因此,系统必须等待一段时间,确保旧连接的所有残留数据包都已消失。
+这个发现来自一家名为 Photon 的公司,他们用一批 Mac 电脑持续监控 iMessage 服务。奇怪的是,这些 Mac 每次运行到 exactly 49 天 17 小时 2 分钟 47 秒时,就会突然崩溃——内存开始失控飙升,最终完全无法建立新的 TCP 连接。这听起来很诡异,对吧?
-这个等待时间在标准中被定义为 30 秒左右。30 秒后,端口才真正释放,可以再次使用。第二个关键点是整数溢出(integer overflow)。虽然大多数人都知道,但为了完整起见,我还是快速解释一下:一个字节由 8 位组成,假设所有位都是 1,也就是二进制 11111111(十进制 255)。如果再加上 1,会发生什么。
+事实上,这个问题并非只存在于 Photon 公司的设备,而是存在于所有 Mac 之中。只要你让一台 Mac 连续开机超过 49 天 17 小时 2 分钟 47 秒,就一定会遇到同样的问题:当 TCP 连接数达到某个临界点后,系统将彻底无法创建新连接,整台电脑变得像一块“砖头”一样瘫痪。
-每一位都会从 1 变成 0,并产生进位,一直传递到最高位,最终结果变成 00000000,同时溢出标志位被置为 1。这就是典型的整数溢出。顺便说一句,刚才那条画得特别直的线,简直堪称手绘艺术——那种近乎完美的直线,连我妈都认不出来。不过话说回来,这条线确实太棒了,你总得说点什么吧。我觉得你得夸一夸它。
+要理解这一点,首先要了解 TCP 连接的基本原理。当你从电脑发起一个 TCP 连接时,会占用本地的一个端口。一台 Mac 有大约 65,000 个可用端口,但实际用于主动连接的只有约一半。当连接关闭后,这个端口并不会立即可用,必须等待一小段时间——这就是所谓的“TIME_WAIT”状态。
-最后,你将进位的1带入,结果变成0,再进位一次,溢出位就会被置为1。顺便说一句,那是一条垂直的直线,非常罕见。好了,各位,这真是一段很棒的视频。你们得说点什么,比如“这条线看起来太棒了”,我可期待着呢。这意味着你的数值从255——一个无符号字节所能表示的最大值——直接跳回了0。
+为什么需要等待?因为互联网很大,数据包可能在传输途中延迟、绕路,甚至在云中“迷路”。如果端口被立刻重用,那些延迟到达的数据包可能会误入新连接,造成混乱和数据损坏,从而彻底破坏连接。因此,系统必须等待一段时间,确保旧连接的所有残留数据包都已消失。
-真是令人难过的一天。这种情况被称为整数溢出:你一路飙升到最大值,却瞬间被扔回起点。如果我再努力一点,我觉得还能把这个图画得更好。记住这一点。现在,既然你已经了解了这两个关键信息,Photon最终做了一个实验。
+这个等待时间在标准中被定义为 30 秒左右。30 秒后,端口才真正释放,可以再次使用。
-实验是这样的:我们拿一台即将耗尽寿命的Mac,它几乎要跨过那个临界点,或者已经频繁崩溃。在倒计时五分钟时,我们开始向它发送海量的客户端连接请求。理论上,内部应该达到一种端口占用的平衡状态:每当发起一个新的请求,之前30秒前的旧连接就应该被释放。而他们在这里看到的情况正是如此。抱歉,文字没显示出来,但这里你可以看到,活跃连接数达到了约200个。
+第二个关键点是整数溢出(integer overflow)。虽然大多数人都知道,但为了完整起见,我还是快速解释一下:一个字节由 8 位组成,假设所有位都是 1,也就是二进制 11111111(十进制 255)。如果再加上 1,会发生什么?每一位都会从 1 变成 0,并产生进位,一直传递到最高位,最终结果变成 00000000,同时溢出标志位被置为 1。这就是典型的整数溢出。
-连接不断建立,每30秒就有新连接开启。但当接近这个数量时,你会发现连接数持续攀升,不断累积。实际上,新的连接已经无法建立。原因其实有点出人意料。如果你跳转到旧版 Apple OSS 的 dash XNU 仓库,就能找到真正的 Apple XNU 分发版本——也就是你 Mac 上运行的系统内核。
+顺便说一句,刚才那条画得特别直的线,简直堪称手绘艺术——那种近乎完美的直线,连我妈都认不出来。不过话说回来,这条线确实太棒了,你总得说点什么吧?我觉得你得夸一夸它。
-你可以去查看安全机制、手册页,或者看看 Apple 的许可证。我不确定……哦天,别展示这个。天哪,Apple 的许可证太多了,完全不像 MIT 那种风格。不管怎样,如果你进入 tcp_subr.c 文件,会发现一个非常漂亮的函数,叫做 Calculate TCP Clock。这个函数的作用是同步整个 TCP 协议栈的时间信息,因为每次发送数据包时都会附带时间戳。
+最后,你将进位的1带入,结果变成0,再进位一次,溢出位就会被置为1。顺便说一句,那是一条垂直的直线,非常罕见。好了,各位,这真是一段很棒的视频。你们得说点什么,比如“这条线看起来太棒了”,我可期待着呢。
-它也用来判断某个连接是否已超时,从而释放对应的端口。而这一切都依赖于一个单一的时钟。你看这里,这是获取当前毫秒级时间的方式。如果你习惯用 date.now 或者 odin 的 tick_now 这类方式,可能会觉得这简直不可思议。但这其实是老派获取时间信息的方法,实际上和 Chrome 的 Performance API 非常相似,对吧。
+## 从端口到时间:理解TCP的脆弱机制
-确实如此。而问题就出现在这一行代码上。你注意到它取的是 tv_sec——虽然不知道 tv 具体代表什么,但先接受它——即从机器启动以来经过的秒数,乘以一千,转换成毫秒。接着,它把这个值强制转换为 Uint32。那么,Uint32 的问题在哪。
+这意味着你的数值从255——一个无符号字节所能表示的最大值——直接跳回了0。真是令人难过的一天。这种情况被称为整数溢出:你一路飙升到最大值,却瞬间被扔回起点。如果我再努力一点,我觉得还能把这个图画得更好。记住这一点。
-因为它只能表示大约42亿个数值。秒数乘以千后变成毫秒,而42亿毫秒大约等于49天17小时2分钟47秒。一旦超过这个时间,系统就会发生回滚,从最大值直接跳回0。此时,当前的 TCP 时间又回到了一个极小的数值。然后,系统会加载所有正在进行的 TCP 连接共享的时间戳。
+现在,既然你已经了解了这两个关键信息,Photon最终做了一个实验。实验是这样的:我们拿一台即将耗尽寿命的Mac,它几乎要跨过那个临界点,或者已经频繁崩溃。在倒计时五分钟时,我们开始向它发送海量的客户端连接请求。理论上,内部应该达到一种端口占用的平衡状态:每当发起一个新的请求,之前30秒前的旧连接就应该被释放。而他们在这里看到的情况正是如此。抱歉,文字没显示出来,但这里你可以看到,活跃连接数达到了约200个。连接不断建立,每30秒就有新连接开启。但当接近这个数量时,你会发现连接数持续攀升,不断累积。实际上,新的连接已经无法建立。
-这个临时时间与当前时间进行比较,判断:“嘿,当前临时时间是否小于现在的时间。”——答案是“是的”,于是更新时间。但问题就出在这儿:当前时间刚刚完成一轮回滚,变成了一个很小的数字,比如5000、1000甚至500,不管具体是多少,反正都很小。因此,这个判断条件永远不成立。结果就是,TCP 协议栈内部的时间永远不会向前推进。
+原因其实有点出人意料。如果你跳转到旧版 Apple OSS 的 dash XNU 仓库,就能找到真正的 Apple XNU 分发版本——也就是你 Mac 上运行的系统内核。你可以去查看安全机制、手册页,或者看看 Apple 的许可证。我不确定……哦天,别展示这个。天哪,Apple 的许可证太多了,完全不像 MIT 那种风格。不管怎样,如果你进入 tcp_subr.c 文件,会发现一个非常漂亮的函数,叫做 Calculate TCP Clock。这个函数的作用是同步整个 TCP 协议栈的时间信息,因为每次发送数据包时都会附带时间戳。它也用来判断某个连接是否已超时,从而释放对应的端口。而这一切都依赖于一个单一的时钟。
-也就是说,30秒的 TIME_WAIT 超时永远无法达成。这意味着,旧的端口永远无法被释放。一旦连接数超过大约32000个,你就再也无法建立任何新的 TCP 连接。当然,现有的连接依然正常工作,但所有新连接都会彻底失效。天啊,这全是因为一个 Uint32。
+你看这里,这是获取当前毫秒级时间的方式。如果你习惯用 date.now 或者 odin 的 tick_now 这类方式,可能会觉得这简直不可思议。但这其实是老派获取时间信息的方法,实际上和 Chrome 的 Performance API 非常相似,对吧?而问题就出现在这一行代码上。你注意到它取的是 tv_sec——虽然不知道 tv 具体代表什么,但先接受它——即从机器启动以来经过的秒数,乘以一千,转换成毫秒。接着,它把这个值强制转换为 Uint32。
-你为了节省四个字节,坚持用32位整数而不是64位,结果让所有人都遭殃了。事实上,这本质上和 Y2K38 问题一模一样——只不过 Y2K38 是从1970年开始计算的秒数,使用的是 int32,只能表示21亿秒,大约在2038年左右就会回滚到过去。总之,我觉得这实在太有意思了。这是一个极其精妙的漏洞,强烈建议大家去看原文。文章写得非常深入,真的值得一读。
+那么,Uint32 的问题在哪?因为它只能表示大约42亿个数值。秒数乘以千后变成毫秒,而42亿毫秒大约等于49天17小时2分钟47秒。一旦超过这个时间,系统就会发生回滚,从最大值直接跳回0。此时,当前的 TCP 时间又回到了一个极小的数值。然后,系统会加载所有正在进行的 TCP 连接共享的时间戳。这个临时时间与当前时间进行比较,判断:“嘿,当前临时时间是否小于现在的时间?”——答案是“是的”,于是更新时间。
-我就是对这类技术细节着迷。所以,如果你曾经让 Mac 持续运行太久,然后突然一切崩溃,那很可能就是这个原因。名字叫 PrimeTime,还真是名副其实。
+## 真相揭露:一个32位计时器的致命回绕
+
+但问题就出在这儿:当前时间刚刚完成一轮回滚,变成了一个很小的数字,比如5000、1000甚至500,不管具体是多少,反正都很小。因此,这个判断条件永远不成立。结果就是,TCP 协议栈内部的时间永远不会向前推进。也就是说,30秒的 TIME_WAIT 超时永远无法达成。这意味着,旧的端口永远无法被释放。一旦连接数超过大约32000个,你就再也无法建立任何新的 TCP 连接。当然,现有的连接依然正常工作,但所有新连接都会彻底失效。
+
+天啊,这全是因为一个 Uint32。你为了节省四个字节,坚持用32位整数而不是64位,结果让所有人都遭殃了。事实上,这本质上和 Y2K38 问题一模一样——只不过 Y2K38 是从1970年开始计算的秒数,使用的是 int32,只能表示21亿秒,大约在2038年左右就会回滚到过去。
+
+总之,我觉得这实在太有意思了。这是一个极其精妙的漏洞,强烈建议大家去看原文。文章写得非常深入,真的值得一读。我就是对这类技术细节着迷。所以,如果你曾经让 Mac 持续运行太久,然后突然一切崩溃,那很可能就是这个原因。名字叫 PrimeTime,还真是名副其实。
---
-*全文由 AI 转录并校正生成,仅供参考。*
+*本文由 AI 基于转写整理,仅供复查参考。*