惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
T
Threatpost
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
M
MIT News - Artificial intelligence
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
S
Security Affairs
P
Proofpoint News Feed
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
S
Security @ Cisco Blogs
H
Hacker News: Front Page
Security Archives - TechRepublic
Security Archives - TechRepublic
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
月光博客
月光博客
量子位
博客园_首页
The Last Watchdog
The Last Watchdog
D
DataBreaches.Net
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
The Register - Security
The Register - Security
Schneier on Security
Schneier on Security
H
Help Net Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
F
Full Disclosure
C
Cyber Attacks, Cyber Crime and Cyber Security
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
L
Lohrmann on Cybersecurity
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
Microsoft Security Blog
Microsoft Security Blog

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) Go语言之闭包篇 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
给silly实现了一个ernro模块 | 重归混沌的BLOG
重归混沌 · 2026-05-03 · via 重归混沌的BLOG

去年重构 silly.net.tcp 模块时,为了识别 read 是否已经读到连接关闭,我参考了 Berkeley Sockets APIread 返回 0 的语义,并以返回空字符串来表示这一状态。

但由于设计的是 Full Read 模式,为了判断本次 read 是否获取到完整数据,我在返回 "" 的同时,将错误设置为 end of file

这样,当不关心具体错误原因时,只需判断 err 是否为 nil,即可得知当前读取的数据是否完整。

如果需要判断是否为 end of file,则可以在 err ~= nil 的前提下,检查返回的数据是否为空字符串;若为空,则表示读到了 end of file

而判断是否为 read timeout,则可以在发生错误时,通过 conn:isalive() 判断连接是否仍然存活,因为只有 read timeout 不会导致连接断开。

这里存在竞态场景,例如刚发生超时,随后连接又被关闭。此时 read 返回 nil, "read timeout",但 isalive() 却返回 false。不过连接此时本就已经断开,因此影响相对有限。

之所以没有将 end of fileread timeout 设计得更直观,是因为在现有抽象中,string 形式的 error 不具备可比较性。无法保证在其他上下文中不会返回语义不同但字面相同的 end of file。例如在 http 连接复用场景下,只要读满 content-length,也可能返回 end of file,但这并不意味着连接关闭。

最初设计用这两个返回值表达三种状态(正常、失败、end of file)时,我还自豪的认为已经将 2bit 的表达能力利用到了极致。

然而在随后几个月的实践中,即使作为作者,我也多次踩坑,经常不自觉地写出如下代码:


local conn = tcp.connect("127.0.0.1:6379")
while true do
    local dat, err = conn:read("\n")
    if not dat then
        break
    end
    ... do something with dat
end

这段代码在遇到 end of file 时会陷入死循环。

根本原因就在于,这种使用方式不直观,也不符合 Lua 的常见习惯,而且会带来额外的心智负担。

此外,由于向已关闭连接执行 write 时,返回的错误可能是 broken pipe 而非 end of file,因此该死循环并非必现,更增加了排查难度。

在之后使用 Claude 编写相关代码时,也多次遇到同样的问题,这促使我重新审视「符合直觉」的重要性。

-----------------

经过这两个月的反复权衡,我最终决定引入 silly.errno,并建立清晰的抽象边界来解决这一问题。

在当前的 silly 实现中,所有来自 C 层的错误码,在进入 Lua 层之前都会被转换为 string。这样做是因为Lua的字符串既具备良好的可读性,又不会带来额外的判断开销。

接下来需要明确一个边界:哪些 string 类型的 error 可以比较,哪些不应比较。

观察后发现,需要进行比较的错误,通常来自 C 底层,或是对其进行薄封装的模块,例如 silly.netsilly.net.tcpsilly.net.tlssilly.net.udp。

而在其之上的模块,一旦发生错误,要么是协议层自定义的错误(如 mysql/redis/http),要么属于不可恢复错误,本身不适合参与比较。

基于此,我引入了 silly.errno 类型:silly.netsilly.net.tcpsilly.net.tlssilly.net.udp 返回的 error 均为 silly.errno;其他模块则继续返回 stringsilly.errno 的值是可比较的,可以与 silly.errno.XXX 进行判断;而普通 string 类型的 error 不具备这一特性。

从类型关系上看,silly.errno 派生自 string,即它是 string 的子类型。这样设计的原因在于:当上层模块需要返回 string 类型的 error 时,可以将底层返回的 silly.errno 进行 upcast 后继续向上传递。一旦发生 upcast,即意味着该错误丧失了可比较性。

这一设计也预留了扩展空间。目前用于返回与比较的字符串均由 silly.errno 模块导出。若未来 string 难以支撑更复杂的需求,可以在不破坏兼容性的前提下,将 silly.errno 扩展为带有 __tostring 元方法的 table 类型。

ps. 由于我在标准库 errno.h 的错误码基础上扩展了一部分自定义错误码,为避免这些错误码对应的字符串与系统错误发生意外冲突,我在 Lua 层对错误字符串的格式做了轻微调整,类似这样:snprintf(buf, sizeof(buf), "%s (%d)", strerror(errno), errno);。

pps. 在设计该模块之初,一直问自己:是否有必要仅为 end of filetimeout 的可比较性,就重构整套 errno 机制。最终我的答案是:符合直觉应当高于一切,这也更符合最小知识依赖原则。