From 5730eff5606d1c19106636cd6a6258ab6e846a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 22 Sep 2026 11:14:18 +0800 Subject: [PATCH 1/6] add .agents skills for bee.* lua modules Split per-module Lua API docs into .agents/skills/bee-/SKILL.md, with an index at .agents/bee-lua-api.md and a reference from AGENT.md. --- .agents/bee-lua-api.md | 80 ++++++++++++ .agents/skills/bee-async/SKILL.md | 118 ++++++++++++++++++ .agents/skills/bee-channel/SKILL.md | 90 ++++++++++++++ .agents/skills/bee-crash/SKILL.md | 34 ++++++ .agents/skills/bee-debugging/SKILL.md | 39 ++++++ .agents/skills/bee-epoll/SKILL.md | 77 ++++++++++++ .agents/skills/bee-filesystem/SKILL.md | 96 +++++++++++++++ .agents/skills/bee-filewatch/SKILL.md | 75 ++++++++++++ .agents/skills/bee-platform/SKILL.md | 48 ++++++++ .agents/skills/bee-select/SKILL.md | 68 +++++++++++ .agents/skills/bee-serialization/SKILL.md | 61 ++++++++++ .agents/skills/bee-socket/SKILL.md | 142 ++++++++++++++++++++++ .agents/skills/bee-subprocess/SKILL.md | 111 +++++++++++++++++ .agents/skills/bee-sys/SKILL.md | 54 ++++++++ .agents/skills/bee-thread/SKILL.md | 57 +++++++++ .agents/skills/bee-time/SKILL.md | 49 ++++++++ .agents/skills/bee-windows/SKILL.md | 59 +++++++++ AGENT.md | 2 + 18 files changed, 1260 insertions(+) create mode 100644 .agents/bee-lua-api.md create mode 100644 .agents/skills/bee-async/SKILL.md create mode 100644 .agents/skills/bee-channel/SKILL.md create mode 100644 .agents/skills/bee-crash/SKILL.md create mode 100644 .agents/skills/bee-debugging/SKILL.md create mode 100644 .agents/skills/bee-epoll/SKILL.md create mode 100644 .agents/skills/bee-filesystem/SKILL.md create mode 100644 .agents/skills/bee-filewatch/SKILL.md create mode 100644 .agents/skills/bee-platform/SKILL.md create mode 100644 .agents/skills/bee-select/SKILL.md create mode 100644 .agents/skills/bee-serialization/SKILL.md create mode 100644 .agents/skills/bee-socket/SKILL.md create mode 100644 .agents/skills/bee-subprocess/SKILL.md create mode 100644 .agents/skills/bee-sys/SKILL.md create mode 100644 .agents/skills/bee-thread/SKILL.md create mode 100644 .agents/skills/bee-time/SKILL.md create mode 100644 .agents/skills/bee-windows/SKILL.md diff --git a/.agents/bee-lua-api.md b/.agents/bee-lua-api.md new file mode 100644 index 00000000..57e34e17 --- /dev/null +++ b/.agents/bee-lua-api.md @@ -0,0 +1,80 @@ +# bee.lua Lua API 索引 + +各模块的详细 skill 见 `.agents/skills/bee-/SKILL.md`。权威签名在 `meta/*.lua`(LuaLS 注解),行为契约在 `test/test_*.lua`。 + +## 模块 → skill + +| 模块 | skill | 用途 | +|------|-------|------| +| `bee.platform` | [bee-platform](skills/bee-platform/SKILL.md) | 平台/编译器/架构信息(纯数据表) | +| `bee.filesystem` | [bee-filesystem](skills/bee-filesystem/SKILL.md) | 路径与文件系统操作 | +| `bee.socket` | [bee-socket](skills/bee-socket/SKILL.md) | TCP/UDP/Unix socket | +| `bee.select` | [bee-select](skills/bee-select/SKILL.md) | select 风格多路复用 | +| `bee.epoll` | [bee-epoll](skills/bee-epoll/SKILL.md) | epoll 风格多路复用(Windows 走 IOCP) | +| `bee.async` | [bee-async](skills/bee-async/SKILL.md) | 异步 I/O(IOCP / io_uring / GCD) | +| `bee.time` | [bee-time](skills/bee-time/SKILL.md) | 墙钟 / 单调 / 线程 CPU 时间 | +| `bee.thread` | [bee-thread](skills/bee-thread/SKILL.md) | 线程 | +| `bee.channel` | [bee-channel](skills/bee-channel/SKILL.md) | 线程间通信 | +| `bee.serialization` | [bee-serialization](skills/bee-serialization/SKILL.md) | 序列化(线程/通道的底层) | +| `bee.subprocess` | [bee-subprocess](skills/bee-subprocess/SKILL.md) | 子进程与管道 | +| `bee.filewatch` | [bee-filewatch](skills/bee-filewatch/SKILL.md) | 文件监控 | +| `bee.sys` | [bee-sys](skills/bee-sys/SKILL.md) | 可执行文件路径、文件锁 | +| `bee.crash` | [bee-crash](skills/bee-crash/SKILL.md) | 崩溃 dump | +| `bee.debugging` | [bee-debugging](skills/bee-debugging/SKILL.md) | 断点 / 调试器探测 | +| `bee.windows` | [bee-windows](skills/bee-windows/SKILL.md) | Windows 专有工具 | + +## 跨模块约定 + +- 模块都在 `bee.*` 命名空间:`local socket = require "bee.socket"`。 +- **三态返回值**(socket / epoll / select 等非阻塞接口):成功 → 值;`false` → 需等待(非错误);`nil, errmsg` → 失败/对端关闭。参数校验错误才 `error()`。 +- 句柄类对象用 to-be-closed 管理:`local fd = assert(socket.create "tcp")`。 +- 子进程管道是标准 `file*`,用 `:read "a"` / `:write` / `:close`。 +- 线程/通道传值经 `bee.serialization`,只支持 `nil/boolean/number/string/table/light C function`。 + +## 构建与测试 + +```bash +luamake # 编译 + 测试 +luamake -notest # 只编译 +luamake test -v # 只测试,详细输出 +luamake test -v # 只跑名称匹配的用例(如 socket.test_udp) +``` + +测试基于 ltest,文件在 `test/`: + +```lua +local lt = require "ltest" +local m = lt.test "module" + +function m:test_case() + lt.assertEquals(a, b) + lt.assertNil(x); lt.assertIsUserdata(fd); lt.assertTrue(cond) + lt.assertError(function () ... end) + lt.assertErrorMsgEquals("max_completions is less than or equal to zero.", async.create, 0) + lt.failure "msg" +end +``` + +常用辅助: + +- `test/shell.lua` — `shell:runlua(script, spawn_options)` 起带正确 `package.cpath` 的 Lua 子进程;`shell:add_readonly/del_readonly`;`shell:pwd()`;`shell.is_luamake`。 +- `test/supported.lua` — `supported "symlink"` / `supported "hardlink"` 特性探测(结果缓存)。 +- `test/test_skip.lua` — 按平台 `lt.skip "module.test_name"` 跳过用例。 +- `test/test.lua` — 入口:设置 `package.path/cpath`、按平台装载测试文件、`lt.run()` 后 `os.exit`。 + +## 可选链(编译期 patch) + +`?.` / `?:` / `?[...]` / `f?(...)` 是 vendored Lua 的补丁语法,仅当 `luamake -optchain` 构建时可用: + +```lua +local a = obj?.a?.b?.c -- 链上任一环节为 nil 即短路为 nil +local v = t?[1]?[2] +local r = obj?:method(args) -- 只保护接收者,方法本身不存在仍报错 +local x = f?(1, 2) +``` + +- 只有 `nil` 短路,`false` 会照常报错。 +- 短路时参数/键不会被求值;接收者只求值一次。 +- 短路只覆盖链本身:`(nothing?.b).c`、`nothing?.b + 1` 仍报错。 +- 不可作为赋值目标。 +- `test/test_optional_chain.lua` 还锁定了生成的字节码布局。 diff --git a/.agents/skills/bee-async/SKILL.md b/.agents/skills/bee-async/SKILL.md new file mode 100644 index 00000000..8d586e4d --- /dev/null +++ b/.agents/skills/bee-async/SKILL.md @@ -0,0 +1,118 @@ +--- +name: bee-async +description: 用 bee.async 做跨平台异步 I/O(create 实例、submit_read/submit_write/submit_accept/submit_connect/submit_file_read/submit_file_write/submit_poll 投递、readbuf/writebuf 缓冲区、poll/wait 返回 completion 迭代器、SUCCESS/CLOSE/ERROR/CANCEL 与 OP_* 常量,Windows 需 associate)。当需要高吞吐异步收发、非阻塞文件 I/O 或基于完成事件的事件循环时使用。 +--- + +# bee.async + +`require "bee.async"`,对应 `meta/async.lua`、`test/test_async.lua`。macOS 用 GCD,Windows 用 IOCP,Linux 用 io_uring/epoll。 + +模型:**一次投递 → 一次 completion**,投递时传入的 `udata`(token)原样回传。 + +## API + +```lua +local async = require "bee.async" + +local as = assert(async.create([max_completions = 64])) -- <=0 报 "max_completions is less than or equal to zero." + +as:associate(fd) -- Windows/IOCP 必需,其他平台 no-op +as:associate_file(file) -- 文件 I/O 前必须调用(io.open 得到的 file*) +as:cancel(fd) -- 取消该 fd 上所有未完成操作 +as:poll() / as:wait([timeout_ms]) -- 非阻塞 / 阻塞,返回完成事件迭代器 +as:stop() +``` + +投递接口: + +```lua +as:submit_read(rb, fd, udata) --> true | false(背压) | nil, err +as:submit_write(wb, fd, udata) --> true | nil, err +as:submit_accept(listen_fd, udata) +as:submit_connect(fd, host, port, udata) -- 也接受 bee.endpoint 重载 +as:submit_file_read(file, len [, offset = 0], udata) +as:submit_file_write(file, data [, offset = 0], udata) +as:submit_poll(fd, udata) -- 只监听可读,不消费数据 +``` + +缓冲区: + +```lua +local wb = assert(async.writebuf([hwm = 65536])) +wb:write(data) --> true 表示缓冲 >= hwm,调用方应自行背压 +wb:buffered() --> 当前排队字节数 +wb:close() -- 流关闭时丢弃未发数据 + +local rb = assert(async.readbuf(bufsize)) -- 向上取整到 2 的幂;<=0 报 "bufsize must be positive" +rb:read([n]) --> string | nil(数据不足);n 省略取全部可用 +rb:readline([sep = "\r\n"]) --> string | nil(未找到分隔符) +``` + +## completion 迭代器 + +```lua +for op, udata, status, data, errcode in as:wait(timeout_ms) do + -- op : OP_READ / OP_WRITE / OP_ACCEPT / OP_CONNECT / OP_FILE_READ / OP_FILE_WRITE / OP_POLL + -- status : SUCCESS / CLOSE / ERROR / CANCEL + -- data : accept -> 新 socket userdata;file_read -> 读到的字符串;其余 -> 传输字节数 +end +``` + +写入的完成事件 `bytes` 恒为 `0`(数据已由 C 层 drain 完,包括 partial write 重试)。 + +## 完整示例(取自 `test_async.lua`) + +```lua +local as = assert(async.create(64)) + +-- 服务端/客户端都要先 associate +local sfd = assert(socket.create "tcp") +assert(as:associate(sfd)) +assert(sfd:bind("127.0.0.1", 0)); assert(sfd:listen()) + +local _, port = sfd:info "socket":value() + +local cfd = assert(socket.create "tcp") +assert(as:associate(cfd)) +local ok, err = cfd:connect("127.0.0.1", port) -- 可能返回 false(等待中),用 submit_connect 更常见 +assert(ok ~= nil, err) + +-- 接受连接(测试里用 select 等可读,再 sfd:accept 并 associate) +assert(as:submit_accept(sfd, "accept_token")) -- completion 的 data 即新 socket userdata + +-- 写 +local wb = assert(async.writebuf(64 * 1024)) +wb:write "hello" +assert(as:submit_write(wb, cfd, "write_token")) + +local op, token, status, bytes +for _op, _tok, _st, _data in as:wait(1000) do -- wait/poll 返回的是迭代器 + op, token, status, bytes = _op, _tok, _st, _data + break +end +-- op == async.OP_WRITE, token == "write_token", status == async.SUCCESS, bytes == 0 + +-- 读(数据在 ring buffer 里自取) +local rb = assert(async.readbuf(64)) +assert(as:submit_read(rb, newfd, { id = 42 })) +-- 收到 OP_READ + SUCCESS 后: +local data = rb:read(5) -- 精确字节数;不足返回 nil +local line = rb:readline() -- 或按行取 +``` + +文件 I/O: + +```lua +local rf = assert(io.open(path, "rb")) +assert(as:associate_file(rf)) +assert(as:submit_file_read(rf, 128, 0, "fread")) +-- completion: op == OP_FILE_READ, data == 读到的字符串(不是字节数) +``` + +## 注意事项 + +- `associate` / `associate_file` 必须在**首次提交 I/O 之前**完成;重复 `associate` 同一 socket 是允许的。 +- `submit_read` 有背压:ring buffer 空闲不足返回 `false`,重试前需先 `rb:read()` 腾出空间。 +- 对端关闭时读操作产生 `status == CLOSE`,不是 `ERROR`。 +- 关闭 fd 前建议 `as:cancel(fd)`,确保未完成操作及时回收(Windows 上尤其重要)。 +- `submit_poll` 只通知可读,典型用途是监听 `channel:fd()` 后自行 `channel:pop()`。 diff --git a/.agents/skills/bee-channel/SKILL.md b/.agents/skills/bee-channel/SKILL.md new file mode 100644 index 00000000..4016b2d8 --- /dev/null +++ b/.agents/skills/bee-channel/SKILL.md @@ -0,0 +1,90 @@ +--- +name: bee-channel +description: 用 bee.channel 做线程间通信(create/query/destroy 命名通道、box:push/pop 序列化传递、box:fd 接入 epoll/select 等待可读)。当需要多线程收发消息、或搭建 worker 请求-响应模型时使用。 +--- + +# bee.channel + +`require "bee.channel"`,对应 `meta/channel.lua`、`test/test_channel.lua`。 + +## API + +```lua +local channel = require "bee.channel" + +local box = channel.create(name) -- 名称必须唯一,重复则 error: "Duplicate channel 'test'" +local box = channel.query(name) --> box | nil, err +channel.destroy(name) -- 清空数据并销毁 + +box:push(...) -- 序列化后入队(类型限制同 bee.serialization) +local ok, ... = box:pop() -- ok == false 表示通道为空(此时第二个返回值为 nil) +box:fd() --> lightuserdata -- 用于 epoll/select 等可读 +``` + +`pop` 逐条出队,FIFO: + +```lua +local chan = channel.create "test" +chan:push(1024); chan:push(1025) +assert(chan:pop() == 1024) +assert(chan:pop() == 1025) +local ok = chan:pop() -- false,通道已空 +channel.destroy "test" +``` + +## 用法:worker + 请求/响应 + +```lua +local req = channel.create "testReq" +local res = channel.create "testRes" + +local thd = thread.create([[ + local thread = require "bee.thread" + local channel = require "bee.channel" + local req = channel.query "testReq" + local res = channel.query "testRes" + local function dispatch(ok, what, ...) + if not ok then return end + if what == "exit" then return true end + res:push(what, ...) + end + while not dispatch(req:pop()) do + thread.sleep(0) -- 空转等待 + end +]]) + +req:push("echo", 1, { A = { B = "C" } }) +local ok, what, arg = res:pop() -- 阻塞式轮询 +req:push "exit" +thread.wait(thd) +channel.destroy "testReq"; channel.destroy "testRes" +``` + +## 用法:用 fd 参与多路复用(避免空转) + +worker 端监听 `req:fd()`,取到 `EPOLLIN` 后循环 `pop` 直到取空(`test_channel:test_fd`): + +```lua +local epfd = epoll.create(16) +epfd:event_add(req:fd(), epoll.EPOLLIN) +for _, event in epfd:wait() do + if event & (epoll.EPOLLERR | epoll.EPOLLHUP) ~= 0 then error "unknown error" end + if event & epoll.EPOLLIN ~= 0 then + while true do + local ok, what, ... = req:pop() + if not ok then break end + -- 分发;收到 "exit" 则 return + end + end +end +``` + +主线程侧同理监听 `res:fd()`;`bee.async` 里可用 `as:submit_poll(chan:fd(), udata)`。 + +## 注意事项 + +- 通道是**全局命名**的:`channel.query` 在别的线程里靠名字找回同一个通道,因此名字要唯一且双方约定一致。 +- `create` 一个已存在的名字会 `error`;`test_reset_1` 说明 `destroy` 后可以重新 `create` 同名通道。 +- 传的数据经序列化,不能传 userdata / `thread` / 普通 Lua function(报错文案见 `bee-serialization`)。 +- 通道内数据在 `destroy` 时被清空,不要依赖销毁后还能 `pop`。 +- 双向通信要建两个通道(req/res),单个通道是单向队列。 diff --git a/.agents/skills/bee-crash/SKILL.md b/.agents/skills/bee-crash/SKILL.md new file mode 100644 index 00000000..af52863d --- /dev/null +++ b/.agents/skills/bee-crash/SKILL.md @@ -0,0 +1,34 @@ +--- +name: bee-crash +description: 用 bee.crash 安装崩溃处理器、在进程崩溃时落 dump 文件(create_handler、dump 路径与 "-" 关闭落盘)。当需要捕获 native crash 现场、生成崩溃报告或想显式关闭 dump 写入时使用。 +--- + +# bee.crash + +`require "bee.crash"`,对应 `meta/crash.lua`、`binding/lua_crash.cpp`、`test/test.lua`。 + +## API + +```lua +local crash = require "bee.crash" + +local handler = crash.create_handler(dump_path) --> handler userdata +``` + +- `dump_path` 是**目录**:崩溃日志写成 `/crash_.log`。 +- `dump_path` 传 `"-"` 时**关闭落盘**(崩溃时只把日志打印到控制台),测试入口就是这么用的: + +```lua +-- test/test.lua +local crash = require "bee.crash" +local _ = crash.create_handler "-" +``` + +- handler 的 userdata 没有额外方法,靠 `` / GC 管理生命周期。 + +## 注意事项 + +- 只在 Windows + MSVC(且非 address sanitizer)构建下真正生效,其他平台是 `empty_handler`,构造调用是 **no-op**(见 `bee/crash/handler.h`)。因此跨平台代码可以无条件调用。 +- 路径是 `luaL_checkstring`,必须传字符串;非 Windows 平台不会校验路径是否存在。 +- 用途是捕获 native 层崩溃(段错误、未处理异常),Lua 的 `pcall` 错误栈不在其覆盖范围内。 +- 需要在崩溃后分析时,把 `dump_path` 指向可写目录并在测试/CI 里收集该目录;不希望生成文件时用 `"-"`。 diff --git a/.agents/skills/bee-debugging/SKILL.md b/.agents/skills/bee-debugging/SKILL.md new file mode 100644 index 00000000..f23deb56 --- /dev/null +++ b/.agents/skills/bee-debugging/SKILL.md @@ -0,0 +1,39 @@ +--- +name: bee-debugging +description: 用 bee.debugging 触发断点与探测调试器(breakpoint、breakpoint_if_debugging、is_debugger_present)。当需要让调试器在指定位置中断、或按是否挂调试器切换行为时使用。 +--- + +# bee.debugging + +`require "bee.debugging"`,对应 `meta/debugging.lua`、`binding/lua_debugging.cpp`。底层是 `std::breakpoint()` / `std::is_debugger_present()`。 + +## API + +```lua +local debugging = require "bee.debugging" + +debugging.breakpoint() -- 无条件断点(无调试器时会走平台默认的 trap/SIGTRAP 语义) +debugging.is_debugger_present() --> boolean +debugging.breakpoint_if_debugging() -- 仅当有调试器附加时才中断,否则 no-op(安全版本) +``` + +## 用法 + +想在调试器里断下来,但不想让正常运行时崩溃,用 `breakpoint_if_debugging`: + +```lua +local debugging = require "bee.debugging" + +if debugging.is_debugger_present() then + -- 调试模式下走额外校验 +end + +debugging.breakpoint_if_debugging() -- 挂调试器则中断,否则什么都不发生 +``` + +## 注意事项 + +- 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 +- `breakpoint()` 在**没有**调试器附加时行为由平台决定(通常是触发异常/trap),生产代码里应优先用 `breakpoint_if_debugging()`。 +- `is_debugger_present()` 也可用于按环境切换日志级别。 +- 本模块目前没有独立测试文件。 diff --git a/.agents/skills/bee-epoll/SKILL.md b/.agents/skills/bee-epoll/SKILL.md new file mode 100644 index 00000000..78d4326e --- /dev/null +++ b/.agents/skills/bee-epoll/SKILL.md @@ -0,0 +1,77 @@ +--- +name: bee-epoll +description: 用 bee.epoll 做 epoll 风格 I/O 多路复用(create/event_add/event_mod/event_del/wait 迭代器、EPOLLIN/EPOLLOUT 等位标志、关联自定义 userdata,Windows 下由 IOCP 实现)。当需要监听多个 fd 或 channel 可读事件时使用。 +--- + +# bee.epoll + +`require "bee.epoll"`,对应 `meta/epoll.lua`、`test/test_epoll.lua`、`test/test_channel.lua`。 + +跨平台 epoll 风格 API,Windows 上由 IOCP 实现,因此返回错误的形式是 `nil, err`。 + +## API + +```lua +local epoll = require "bee.epoll" + +local epfd = assert(epoll.create(16)) -- max_events 必须 > 0,否则 error +epfd:event_add(fd, events [, userdata]) --> true | nil, err +epfd:event_mod(fd, events [, userdata]) --> true | nil, err +epfd:event_del(fd) --> true | nil, err +epfd:wait([timeout]) --> iterator | nil(已 close 时) +epfd:close() --> true | nil, err(重复 close 返回 nil) +``` + +- `fd` 可为 `bee.socket.fd` 或 `lightuserdata`(如 `channel:fd()`)。 +- `userdata` 是迭代回传的关联对象,默认 fd 自身。 +- `timeout` 毫秒,`-1`/省略为无限等待。 +- 重复 `event_add` 同一个 fd、或对未添加的 fd `event_mod`/`event_del` 返回 `nil`(不抛错)。 + +## 事件常量 + +按位定义,`test_epoll:test_enum` 锁定了取值: + +```lua +epoll.EPOLLIN -- 1 << 0 可读 +epoll.EPOLLPRI -- 1 << 1 +epoll.EPOLLOUT -- 1 << 2 可写 +epoll.EPOLLERR -- 1 << 3 +epoll.EPOLLHUP -- 1 << 4 +epoll.EPOLLRDNORM -- 1 << 6 +epoll.EPOLLRDBAND -- 1 << 7 +epoll.EPOLLWRNORM -- 1 << 8 +epoll.EPOLLWRBAND -- 1 << 9 +epoll.EPOLLMSG -- 1 << 10 +epoll.EPOLLRDHUP -- 1 << 13 对端关闭 +epoll.EPOLLONESHOT -- 1 << 30 一次性 +``` + +## 用法 + +```lua +local epfd = assert(epoll.create(16)) +epfd:event_add(res_chan:fd(), epoll.EPOLLIN, "res") + +for obj, event in epfd:wait() do + if event & (epoll.EPOLLERR | epoll.EPOLLHUP) ~= 0 then + error "unknown error" + end + if event & epoll.EPOLLIN ~= 0 then + -- 就绪通知,数据仍需自行消费 + while true do + local ok, v = res_chan:pop() + if not ok then break end + print(obj, v) + end + end +end +``` + +`test_channel:test_fd` 是完整范例:worker 线程里 `epfd:event_add(req:fd(), epoll.EPOLLIN)`,主线程监听 `res:fd()`,双方用通道收发。 + +## 注意事项 + +- `epoll.create(max_events)` 对 `<= 0` 的参数直接 `error`:`maxevents is less than or equal to zero.`(测试用 `lt.assertFailed` 断言)。 +- `wait` 返回空迭代表示超时;用作非阻塞轮询时传 `0`。 +- `epoll` 只做就绪通知(水平触发语义由底层决定),不消费数据;`channel:pop()` 到空为止是标准收尾方式。 +- 需要更简单的 `SELECT_READ/SELECT_WRITE` 语义用 `bee.select`;需要一次投递一次完成事件用 `bee.async`。 diff --git a/.agents/skills/bee-filesystem/SKILL.md b/.agents/skills/bee-filesystem/SKILL.md new file mode 100644 index 00000000..4b38b90f --- /dev/null +++ b/.agents/skills/bee-filesystem/SKILL.md @@ -0,0 +1,96 @@ +--- +name: bee-filesystem +description: 用 bee.filesystem 做路径与文件系统操作(fspath 对象、exists/copy/remove_all、pairs 目录遍历、时间与权限、符号链接)。当需要读写路径、遍历目录、批量复制删除文件时使用。 +--- + +# bee.filesystem + +`require "bee.filesystem"`,对应 `meta/filesystem.lua`、`test/test_filesystem.lua`。 + +## 路径对象 bee.fspath + +`fs.path(p)` 创建;所有接受路径的接口同时接受字符串。 + +```lua +local fs = require "bee.filesystem" +local p = fs.path "a/b/c.ext" +p:string() -- "a/b/c.ext"(Windows 下分隔符统一为 /) +p:filename() --> bee.fspath "c.ext" +p:parent_path() --> "a/b" +p:stem() --> "c" +p:extension() --> ".ext" +p:is_absolute() / p:is_relative() +p:remove_filename() / p:replace_filename(x) / p:replace_extension(".lua") +p:lexically_normal() +``` + +运算符:`a / b` 路径拼接(加分隔符),`a .. b` 直接拼接。 + +```lua +local root = fs.absolute("./temp/"):lexically_normal() +fs.create_directories(root / "dir") -- temp/dir +``` + +## 查询与操作 + +```lua +fs.status(p) / fs.symlink_status(p) --> bee.file_status(:type() / :exists() / :is_directory() / :is_regular_file()) +fs.exists / fs.is_directory / fs.is_regular_file / fs.file_size +fs.create_directory(p) -- 已存在返回 false +fs.create_directories(p) -- 递归创建 +fs.rename(from, to) / fs.remove(p) -- remove 对不存在的路径返回 false +fs.remove_all(p) -- 递归删除,返回删除数量 +fs.copy(from, to [, options]) / fs.copy_file(from, to [, options]) +fs.absolute(p) / fs.canonical(p) / fs.relative(p [, base]) +fs.current_path([p]) -- 无参返回当前 CWD(fspath),有参则切换 +fs.temp_directory_path() +fs.last_write_time(p [, t]) -- 秒级 Unix 时间戳,读写两用 +fs.permissions(p [, perms, options]) -- 读写两用,位标志 +fs.space(p) --> { capacity, free, available }(字节) +fs.create_symlink(target, link) / fs.create_directory_symlink / fs.create_hard_link +``` + +`file_status:type()` 取值:`"none"|"not_found"|"regular"|"directory"|"symlink"|"block"|"character"|"fifo"|"socket"|"junction"|"unknown"`。 + +## 目录遍历 + +`fs.pairs(dir)` 非递归、`fs.pairs_r(dir)` 递归。迭代产出 `(bee.fspath, bee.directory_entry)`;失败时**抛错**,目录不存在同样抛错。 + +```lua +for path, entry in fs.pairs(fs.path "temp") do + print(path:string(), entry:type(), entry:file_size(), entry:last_write_time()) +end +``` + +`directory_entry` 提供 `:path()`、`:refresh()`、`:status()`、`:symlink_status()`、`:type()`、`:exists()`、`:is_directory()`、`:is_regular_file()`、`:last_write_time()`、`:file_size()`。 + +递归累加(`test_fs:test_copy_dir` 模式): + +```lua +local function each_directory(dir, result) + result = result or {} + for path, status in fs.pairs(fs.path(dir)) do + if status:is_directory() then each_directory(path, result) end + result[path:string()] = true + end + return result +end +``` + +## 选项位标志 + +- `fs.copy_options.{none, skip_existing, overwrite_existing, update_existing, recursive, copy_symlinks, skip_symlinks, directories_only, create_symlinks, create_hard_links}` +- `fs.perm_options.{replace, add, remove, nofollow}` +- `fs.directory_options.{none, follow_directory_symlink, skip_permission_denied}` + +```lua +fs.copy(fs.path "temp", fs.path "temp1", + fs.copy_options.overwrite_existing | fs.copy_options.recursive) +``` + +## 注意事项 + +- 路径对象与字符串互转常见写法:`if type(filename) == "userdata" then filename = filename:string() end`。 +- 测试里所有文件操作都在 `fs.temp_directory_path() / "test_bee"` 下进行(见 `test/test.lua`),临时目录用完 `pcall(fs.remove_all, dir)` 清理。 +- 符号链接相关用例先 `if not supported "symlink" then return end`。 +- Windows 上符号链接/hardlink 需权限,`supported.lua` 的探测方式即 `pcall(fs.create_symlink, ...)`。 diff --git a/.agents/skills/bee-filewatch/SKILL.md b/.agents/skills/bee-filewatch/SKILL.md new file mode 100644 index 00000000..0fd4b141 --- /dev/null +++ b/.agents/skills/bee-filewatch/SKILL.md @@ -0,0 +1,75 @@ +--- +name: bee-filewatch +description: 用 bee.filewatch 监控文件系统变化(create、add 路径、set_recursive/set_follow_symlinks/set_filter、select 轮询 modify/rename 事件)。当需要实现热重载、构建监听或检测目录变更时使用。 +--- + +# bee.filewatch + +`require "bee.filewatch"`,对应 `meta/filewatch.lua`、`test/test_filewatch.lua`。底层:inotify / FSEvents / ReadDirectoryChangesW。 + +## API + +```lua +local filewatch = require "bee.filewatch" + +local fw = filewatch.create() +fw:add(path) -- 自动转绝对路径;可多次调用添加多个根 +fw:set_recursive(enable) --> boolean +fw:set_follow_symlinks(enable) --> boolean -- 某些平台可能不支持 +fw:set_filter(fn|nil) --> boolean -- fn 接收路径字符串,返回 true 表示接受该事件 +fw:select() --> type, path -- type: "modify" | "rename";无事件时 type == nil +``` + +`select()` 是非阻塞的:没有事件时立即返回 `nil`,需要自己轮询 + 睡眠。 + +## 用法 + +```lua +local filewatch = require "bee.filewatch" +local fs = require "bee.filesystem" +local thread = require "bee.thread" + +local root = fs.absolute("./temp"):lexically_normal() +local fw = filewatch.create() +fw:set_recursive(true) +fw:set_follow_symlinks(true) +fw:set_filter(function (path) return true end) +fw:add(root:string()) -- add 接收 string + +while true do + local kind, path = fw:select() + if kind then + print(kind, path) -- "modify"/"rename" + 变更路径 + else + thread.sleep(20) -- 空转等待,测试里用重试计数退出 + end +end +``` + +测试里的收事件循环(`test_filewatch:test_2`)值得参考——`select` 返回 `nil` 时重试若干次即认为事件收完: + +```lua +local retry = 5 +local n = retry +local list = {} +while true do + local w, v = fw:select() + if w then + n = retry + list[#list+1] = v + else + n = n - 1 + if n < 0 then break end + thread.sleep(20) + end +end +``` + +## 注意事项 + +- `add` 只接受字符串路径,`fs.path` 需要先 `:string()`;路径会自动转绝对路径。 +- 事件类型只有 `"modify"` 与 `"rename"` 两种(创建/删除/重命名都落在 `rename` 上)。 +- 事件可能重复或漏报(平台差异),测试里用 `has(list, v)` 去重并允许重试。 +- 目录符号链接、指向自身的符号链接是已知边界情况,`test_symlink` 只验证不崩溃。 +- FreeBSD/OpenBSD/NetBSD 上整个 filewatch 测试组被 `lt.skip "filewatch"` 跳过。 +- 需要“等事件”而不是“轮询”时,把 `select` 放进 `thread.sleep` 循环或与 `bee.epoll`/`bee.async` 的事件循环结合。 diff --git a/.agents/skills/bee-platform/SKILL.md b/.agents/skills/bee-platform/SKILL.md new file mode 100644 index 00000000..d1453eab --- /dev/null +++ b/.agents/skills/bee-platform/SKILL.md @@ -0,0 +1,48 @@ +--- +name: bee-platform +description: 用 bee.platform 读取当前运行平台信息(OS、架构、编译器、CRT、Debug 标志、OS 版本号)。当需要按平台分支代码、或在测试中检测平台差异时使用。 +--- + +# bee.platform + +平台信息模块。返回的是**普通表**(非类),无需 ``。 + +## API + +| 字段 | 类型 | 说明 | +|------|------|------| +| `os` | `"windows"|"android"|"linux"|"netbsd"|"freebsd"|"openbsd"|"ios"|"macos"|"unknown"` | 操作系统 | +| `Arch` | `"x86"|"x86_64"|"arm"|"arm64"|"riscv"|"wasm32"|"wasm64"|"mips64el"|"loongarch64"|"ppc"|"ppc64"|"unknown"` | 目标架构 | +| `Compiler` | `"clang"|"msvc"|"gcc"|"unknown"` | 编译器 | +| `CompilerVersion` | `string` | 编译器版本 | +| `CRT` | `"msvc"|"libstdc++"|"libc++"|"bionic"|"unknown"` | C 运行时库 | +| `CRTVersion` | `string` | CRT 版本 | +| `DEBUG` | `boolean` | 是否 Debug 构建 | +| `os_version` | `{ major: integer, minor: integer, revision: integer }` | 系统版本号 | + +## 用法 + +```lua +local platform = require "bee.platform" + +local isWindows = platform.os == "windows" +local isMinGW = isWindows and platform.CRT == "libstdc++" + +if platform.DEBUG then ... end +``` + +`test/test.lua` 在启动时打印环境信息,是标准用法: + +```lua +local v = platform.os_version +print(("OS: %s %d.%d.%d"):format(platform.os, v.major, v.minor, v.revision)) +print("Arch: ", platform.Arch) +print("Compiler: ", platform.CompilerVersion) +print("CRT: ", platform.CRTVersion) +print("DEBUG: ", platform.DEBUG) +``` + +## 注意事项 + +- 测试中按平台跳过用例请用 `lt.skip "module.test_name"`(`test/test_skip.lua`),按特性探测用 `supported "symlink"`(`test/supported.lua`)。 +- `supported "hardlink"` 的判定就是 `platform.os ~= "android"`。 diff --git a/.agents/skills/bee-select/SKILL.md b/.agents/skills/bee-select/SKILL.md new file mode 100644 index 00000000..50aaec68 --- /dev/null +++ b/.agents/skills/bee-select/SKILL.md @@ -0,0 +1,68 @@ +--- +name: bee-select +description: 用 bee.select 做 select 风格 I/O 多路复用(create/event_add/event_mod/event_del/wait 迭代器、SELECT_READ 与 SELECT_WRITE 位标志、关联自定义 userdata)。当需要同时等待多个 fd 可读可写时使用。 +--- + +# bee.select + +`require "bee.select"`,对应 `meta/select.lua`、`test/test_socket.lua`。 + +## API + +```lua +local select = require "bee.select" + +local ctx = select.create() -- 不会失败,返回值不是 nil,err 形式 +ctx:event_add(fd, events [, userdata]) --> boolean +ctx:event_mod(fd, events) --> boolean +ctx:event_del(fd) --> boolean +ctx:wait([timeout]) --> iterator +ctx:close() +``` + +- `events` 是位组合:`select.SELECT_READ` (读) | `select.SELECT_WRITE` (写)。 +- `fd` 可以是 `bee.socket.fd`,也可以是裸 `lightuserdata`(如 `channel:fd()`)。 +- `userdata` 为迭代时回传的关联对象,默认是 fd 自身。 +- `timeout` 单位毫秒,`-1`(或省略)无限等待。 + +## wait 的正确用法 + +`wait` 返回**迭代器**,迭代产出 `(userdata, event)`;返回空迭代表示超时。 + +```lua +for obj, event in ctx:wait() do + if event & select.SELECT_READ ~= 0 then ... end + if event & select.SELECT_WRITE ~= 0 then ... end +end +``` + +只要事件标志时可以直接累加(来自 `test_socket.lua` 的 `simple_select`): + +```lua +local function simple_select(fd, mode) + local s = select.create() + if mode == "r" then + s:event_add(fd, select.SELECT_READ) + s:wait() + elseif mode == "w" then + s:event_add(fd, select.SELECT_WRITE) + s:wait() + elseif mode == "rw" then + s:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) + local event = 0 + for _, e in s:wait() do + event = event | e + end + return event + else + assert(false) + end +end +``` + +## 注意事项 + +- 一次性等待建议用 `local s = select.create()`(to-be-closed),避免忘记 `close`。 +- 事件常量只有 `SELECT_READ` / `SELECT_WRITE`;需要 epoll 语义(`EPOLLRDHUP`、oneshot 等)请改用 `bee.epoll`。 +- `ctx:close()` 后再调用 `event_add` 等会失败;`bee.epoll` 对应接口返回 `nil, err`,`bee.select` 返回 `boolean`。 +- 与 `bee.async` 不同,select 只做就绪通知,收发仍需自己调用 `fd:recv`/`fd:send`。 diff --git a/.agents/skills/bee-serialization/SKILL.md b/.agents/skills/bee-serialization/SKILL.md new file mode 100644 index 00000000..11818800 --- /dev/null +++ b/.agents/skills/bee-serialization/SKILL.md @@ -0,0 +1,61 @@ +--- +name: bee-serialization +description: 用 bee.serialization 在线程/通道间传递数据(pack/packstring/unpack 的类型限制与报错文案、引用共享保留、lightuserdata 转换)。当需要跨线程传表、或在 channel:push 前预处理复杂结构时使用。 +--- + +# bee.serialization + +`require "bee.serialization"`,对应 `meta/serialization.lua`、`test/test_serialization.lua`。 + +## API + +```lua +local seri = require "bee.serialization" + +seri.pack(...) --> lightuserdata -- 需要 unpack 释放 +seri.packstring(...) --> string +seri.unpack(data) --> ... -- 接受 lightuserdata | string | userdata | function +seri.lightuserdata(ud) --> lightuserdata +``` + +```lua +local data = seri.packstring(1, { A = { B = "C" } }, true) +local a, t, b = seri.unpack(data) +``` + +## 支持的类型 + +`nil`、`boolean`、`number`、`string`、`table`、**light C function**。 + +**引用共享会保留**(`test_seri:test_ref`):同一张表被多处引用,反序列化后仍共享同一份: + +```lua +local N = 10 +local t = {} +for i = 1, N do t[i] = {} end +for i = 1, N do for j = 1, N do t[i][j] = t[j] end end +local newt = seri.unpack(seri.pack(t)) +assert(newt[i][j] == newt[j]) +``` + +## 不支持的类型与报错文案(固定字符串,测试逐字断言) + +| 输入 | 错误消息 | +|------|----------| +| 普通 Lua function | `Only light C function can be serialized` | +| coroutine(thread) | `Unsupport type thread to serialize` | +| userdata(如 `io.stdout`) | `Unsupport type userdata to serialize` | + +```lua +seri.pack(require) -- OK:require 是 light C function +seri.pack(os.clock) -- OK +seri.pack(function () end) -- error: Only light C function can be serialized +seri.pack(coroutine.create(f)) -- error: Unsupport type thread to serialize +seri.pack(io.stdout) -- error: Unsupport type userdata to serialize +``` + +## 注意事项 + +- 这是 `bee.thread` 参数传递和 `bee.channel` push/pop 的底层实现,限制完全一致。 +- `pack` 返回 lightuserdata,注意生命周期;只要跨线程传值用 `packstring` 更安全。 +- 不支持的类型在 `pack` 与 `packstring` 上行为一致(测试对两者都断言)。 diff --git a/.agents/skills/bee-socket/SKILL.md b/.agents/skills/bee-socket/SKILL.md new file mode 100644 index 00000000..d5a9491b --- /dev/null +++ b/.agents/skills/bee-socket/SKILL.md @@ -0,0 +1,142 @@ +--- +name: bee-socket +description: 用 bee.socket 创建 TCP/UDP/Unix 套接字(create/bind/listen/accept/connect/send/recv/sendto/recvfrom、端点对象、detach 与还原、非阻塞三态返回值)。当需要网络编程或实现回显服务时使用。 +--- + +# bee.socket + +`require "bee.socket"`,对应 `meta/socket.lua`、`test/test_socket.lua`。 + +## 非阻塞三态返回(本模块最重要的约定) + +| 返回值 | 含义 | +|--------|------| +| 值(`true`/数据/字节数/新 fd) | 成功 | +| `false` | 需等待,配合 `bee.select` / `bee.epoll` 重试 | +| `nil, errmsg` | 失败或对端关闭 | + +`fd:accept()` / `fd:recv()` 的 `nil` 表示对端关闭;`fd:send()` 返回**已发送字节数**,partial write 需自行切片重试。 + +## 创建与连接 + +```lua +local socket = require "bee.socket" +local select = require "bee.select" + +-- 协议:"tcp" | "udp" | "unix" | "tcp6" | "udp6" +local server = assert(socket.create "tcp") +assert(server:bind("127.0.0.1", 0)) -- 端口 0 = 系统分配 +assert(server:listen()) -- backlog 默认 5 +local address, port = server:info "socket":value() -- "socket" 本端 / "peer" 对端 + +local client = assert(socket.create "tcp") +client:connect("127.0.0.1", port) -- 非阻塞,之后等可写再 status() +-- 等可写后: +assert(client:status()) -- true 表示连接建立 + +local session = assert(server:accept()) -- false = 尚无连接 +session:close(); client:close(); server:close() +``` + +Unix socket:`socket.create "unix"` + `fd:bind(path)`,关闭后是否自动 unlink 依平台(测试中用 `detectAutoUnlink` 探测)。 + +## 读写 + +```lua +fd:recv([len]) --> string | false(等待) | nil(关闭), err +fd:send(data) --> n | false(等待) | nil, err +fd:sendv(s1, s2, ...) --> 一次系统调用向量化发送,返回总字节数 +fd:recvfrom([len]) --> data, bee.endpoint | false | nil, err +fd:sendto(data, ep_or_addr [, port]) --> n | false | nil, err +``` + +UDP 示例(`test_socket:test_udp`): + +```lua +local a, b = assert(socket.create "udp"), assert(socket.create "udp") +a:bind("127.0.0.1", 0); b:bind("127.0.0.1", 0) +local a_ep, b_ep = a:info "socket", b:info "socket" +assert(a:sendto("123", b_ep) == 3) +local data, from_ep = b:recvfrom() -- 需先等 b 可读 +assert(data == "123" and from_ep == a_ep) +``` + +## 端点与其它工具 + +```lua +socket.endpoint("inet", ip, port) -- 也有 "inet6" | "hostname" | "unix" +ep:value() -- inet/inet6 返回 ip, port;unix 返回 path, type +socket.pair() --> fd1, fd2(一对已连接的 socket,测试里用于 echo) +socket.gethostname() --> string +socket.fd(handle [, no_ownership]) -- 从裸句柄包装 +``` + +`fd:detach()` 交出裸句柄并放弃所有权,`socket.fd(h)` 可重新包装(`test_socket:test_dump`): + +```lua +local h = server:detach() +server = socket.fd(h) +``` + +## 其它 fd 方法 + +```lua +fd:option("reuseaddr"|"sndbuf"|"rcvbuf", value) +fd:shutdown("r"|"w") -- 省略则双向 +fd:handle() --> lightuserdata +``` + +## 常见用法模板 + +同步等待 + 收发(来自 `test_socket.lua` 的 `simple_select`): + +```lua +local function simple_select(fd, mode) + local s = select.create() + if mode == "r" then + s:event_add(fd, select.SELECT_READ) + elseif mode == "w" then + s:event_add(fd, select.SELECT_WRITE) + else + s:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) + end + s:wait() +end + +local function syncSend(fd, data) + while true do + simple_select(fd, "w") + local n = fd:send(data) + if not n then return n, data end + data = data:sub(n + 1) + if data == "" then return true end + end +end +``` + +回显服务端(`test_socket.lua` 的 echo 用例,客户端跑在 `thread.create` 里): + +```lua +while true do + local event = simple_select(client, "rw") + if event & select.SELECT_READ then + local data = client:recv() + if data == nil then break -- 对端关闭 + elseif data ~= false then queue = queue .. data end + end + if event & select.SELECT_WRITE then + if #queue > 0 then + local n = client:send(queue) + if n == nil then break + elseif n ~= false then queue = queue:sub(n + 1) end + end + end +end +``` + +## 注意事项 + +- 参数校验错误会 `error()`,文案如 `bad argument #1 to 'bee.socket.create' (invalid option 'icmp')`。 +- `socket.create` 失败返回 `nil, err`(用 `assert` 包装)。 +- 对端关闭后继续 `send` 不应崩溃(`test_SIGPIPE` 专门覆盖)。 +- 跨线程使用 socket 需要传句柄或让线程自己 `create`,不能共享 userdata。 diff --git a/.agents/skills/bee-subprocess/SKILL.md b/.agents/skills/bee-subprocess/SKILL.md new file mode 100644 index 00000000..74521bfc --- /dev/null +++ b/.agents/skills/bee-subprocess/SKILL.md @@ -0,0 +1,111 @@ +--- +name: bee-subprocess +description: 用 bee.subprocess 启动与管理子进程(spawn 配置表、stdin/stdout/stderr 管道或重定向、env/cwd、wait/kill/is_running/detach、select 批量等待、setenv/quotearg)。当需要调外部命令、跑测试子进程或用管道做进程间通信时使用。 +--- + +# bee.subprocess + +`require "bee.subprocess"`,对应 `meta/subprocess.lua`、`test/test_subprocess.lua`、`test/shell.lua`。 + +## spawn 配置表 + +```lua +local subprocess = require "bee.subprocess" +local p = assert(subprocess.spawn { + "lua", "-e", "io.write(io.read 'a')", -- [1] 程序路径,其后为参数(数组可嵌套,会被展平) + cwd = "some/dir", -- string | bee.fspath + stdin = true, -- true 建管道 | file* 直接接文件 + stdout = true, + stderr = "stdout", -- true | file* | "stdout"(共享标准输出) + env = { BEE_TEST = "ok", OTHER = false },-- false 表示删除该变量;缺省继承父进程环境 + suspended = false, -- 以挂起状态启动,后续 p:resume() + detached = false, + console = "new", -- Windows: "new"|"disable"|"inherit"|"detached" + hideWindow = false, -- Windows 隐藏窗口 + searchPath = false, -- Windows 是否搜索 PATH +}) +``` + +只有请求了对应管道的句柄才存在:`p.stdin` / `p.stdout` / `p.stderr`,都是标准 `file*`。 + +## 进程方法 + +```lua +p:wait() --> exitcode | nil, err -- 也用于收尸 +p:kill([signum=15]) --> boolean -- kill(0) 只探测存活,不真杀 +p:is_running() --> boolean +p:get_id() --> pid +p:resume() -- 恢复 suspended 启动的进程 +p:native_handle() --> lightuserdata +p:detach() -- 结束收尾,不再由本对象管理 +``` + +## 典型用法 + +```lua +local p = assert(subprocess.spawn { + "lua", "-e", "io.write 'ok'", + stdout = true, stderr = "stdout", +}) +local out = p.stdout:read "a" -- "ok" +assert(p:wait() == 0) +assert(p:detach() == true) -- 测试里每个进程用完都 detach +``` + +管道当 stdin(`test_subprocess:test_stdio_1`): + +```lua +local p = assert(subprocess.spawn { "lua", "-e", "io.write(io.read 'a')", stdin = true, stdout = true }) +assert(p:is_running()) +p.stdin:write "ok" +p.stdin:close() -- 关闭后子进程才 EOF +assert(p:wait() == 0) +assert(p.stdout:read(2) == "ok") +assert(p.stdout:read(2) == nil) -- 后续读到 nil +``` + +进程串联(把上一个的 stdout 当 stdin): + +```lua +local p1 = assert(subprocess.spawn { "lua", "-e", "io.write 'ok'", stdout = true }) +local p2 = assert(subprocess.spawn { "lua", "-e", "io.write(io.read 'a')", + stdin = p1.stdout, stdout = true }) +p1:wait(); p2:wait() +assert(p2.stdout:read "a" == "ok") +``` + +批量等待(`test_subprocess:test_select`): + +```lua +while #progs > 0 do + assert(subprocess.select(progs)) -- 等到任一进程结束 + local i = 1 + while i <= #progs do + if progs[i]:is_running() then + i = i + 1 + else + assert(progs[i]:wait() == 0) + progs[i]:detach() + table.remove(progs, i) + end + end +end +``` + +## 其它工具 + +```lua +subprocess.peek(file) --> 管道可读字节数 | nil, err +subprocess.get_id() --> 当前进程 pid +subprocess.setenv(name, value) -- 改**父进程**环境,后续 spawn 会继承(value 传 false 删除) +subprocess.quotearg(arg) -- 处理空格/引号的命令行转义 +``` + +## 注意事项 + +- `spawn` 失败返回 `nil, err`,用 `assert` 包装。 +- `wait()` 之后 `is_running()` 为 `false`;被 kill 的进程返回码是平台相关的(Windows 上 `0x0F00`)。 +- 用文件重定向时 `p.stdout` 就是传入的那个 `file*`(`assert(p.stdout == f)`),父进程 `close` 后子进程仍能写。 +- Windows 下 `windows.filemode(io.stdin, "b")` 可关闭 CRT 的 CRLF 转换(`test_subprocess` 有覆盖)。 +- 测试里统一通过 `shell:runlua(script, options)`(`test/shell.lua`)启动带正确 `package.cpath` 的 Lua 子进程,`options` 即上面的 spawn 表,`options[1]` 或 `"_"` 用于插入额外 argv。 +- `cwd` 会真正切换子进程工作目录(`test_cwd` 用 `fs.current_path()` 验证)。 diff --git a/.agents/skills/bee-sys/SKILL.md b/.agents/skills/bee-sys/SKILL.md new file mode 100644 index 00000000..35479532 --- /dev/null +++ b/.agents/skills/bee-sys/SKILL.md @@ -0,0 +1,54 @@ +--- +name: bee-sys +description: 用 bee.sys 获取可执行文件/动态库路径、解析文件完整路径、创建进程级文件锁。当需要定位自身可执行文件、防重入单实例锁或规范化路径时使用。 +--- + +# bee.sys + +`require "bee.sys"`,对应 `meta/sys.lua`、`test/test_sys.lua`。 + +## API + +```lua +local sys = require "bee.sys" + +sys.exe_path() --> bee.fspath | nil, err -- 当前可执行文件路径 +sys.dll_path() --> bee.fspath | nil, err -- 当前动态库(bee.dll/so)路径 +sys.fullpath(path) --> bee.fspath | nil, err -- 解析符号链接后的完整路径 +sys.filelock(path) --> file* | nil, err -- 独占文件锁;句柄即锁,close 即解锁 +``` + +## 文件锁 + +跨进程互斥(`test_sys:test_filelock_1`): + +```lua +local sys = require "bee.sys" +local fs = require "bee.filesystem" + +local f1 = assert(sys.filelock "temp.lock") -- 拿到锁 +assert(sys.filelock "temp.lock" == nil) -- 同进程/其他进程再取都是 nil(不是 error) +f1:close() -- 关闭句柄 = 释放锁 +local f2 = assert(sys.filelock "temp.lock") +f2:close() +fs.remove "temp.lock" +``` + +跨进程验证(`test_sys:test_filelock_2` 用 `shell:runlua` 起子进程):子进程拿锁后,父进程 `sys.filelock` 返回 `nil`;子进程退出(句柄关闭)后父进程即可获取。 + +## 用法:定位自身与路径规范化 + +```lua +local exe = sys.exe_path():string() +local dll = sys.dll_path() +local real = sys.fullpath("some/rel/path"):string() +``` + +`test/shell.lua` 里定位当前 Lua 解释器即用 `fs.absolute(fs.path(arg[i+1]))`(配合 `arg` 负数索引),可作为参考。 + +## 注意事项 + +- 三个路径函数返回的是 `bee.fspath`,需要字符串时 `:string()`。 +- 失败返回 `nil, err`,不要用 `assert` 之外的方式跳过错误。 +- 文件锁是**独占**语义,同进程重复加锁同样返回 `nil`(测试明确断言),别用它做可重入锁。 +- 锁文件本身会被创建,用完自行 `fs.remove`。 diff --git a/.agents/skills/bee-thread/SKILL.md b/.agents/skills/bee-thread/SKILL.md new file mode 100644 index 00000000..7cf88730 --- /dev/null +++ b/.agents/skills/bee-thread/SKILL.md @@ -0,0 +1,57 @@ +--- +name: bee-thread +description: 用 bee.thread 创建原生线程(create 传源码字符串与参数、wait、sleep、setname、errlog、线程 id 与 preload_module,线程间不共享全局变量)。当需要并行执行 Lua 代码或搭建多线程 worker 时使用。 +--- + +# bee.thread + +`require "bee.thread"`,对应 `meta/thread.lua`、`test/test_thread.lua`。 + +## API + +```lua +local thread = require "bee.thread" + +thread.create(source, ...) --> handle(lightuserdata) -- source 是 Lua 源码字符串 +thread.wait(handle) -- 等线程结束 +thread.sleep(msec) +thread.errlog() --> string | nil -- 取走并清空线程错误日志 +thread.setname(name) -- 给当前线程命名(调试用) +thread.id -- 主线程为 0,其他线程非 0 +thread.preload_module(L) -- 新线程内部用,注册 bee.* 模块到指定 lua_State +``` + +## 用法 + +```lua +local thread = require "bee.thread" + +GLOBAL = true +local thd = thread.create([[ + local thread = require "bee.thread" + local args = ... -- 传给 create 的额外参数会被序列化后传入 + assert(GLOBAL == nil) -- 线程不共享全局变量 + assert(thread.id ~= 0) + thread.setname "worker" +]], "hello") +thread.wait(thd) +assert(thread.errlog() == nil) +``` + +线程内错误不会中断主线程,集中记录在 `errlog`(`test_thread:test_thread_3`): + +```lua +local thd = thread.create [[ error "Test thread error." ]] +thread.wait(thd) +local msg = thread.errlog() +assert(string.find(msg, "Test thread error.", nil, true)) +``` + +## 注意事项 + +- `source` 必须是**字符串源码**,不能传函数;新线程只拿到自己的环境,只能通过 `require "bee.*"` 或参数传递数据。 +- 参数与返回数据要经过 `bee.serialization`,限制见 `bee-serialization` skill(不能传 userdata、`thread`、普通 Lua function)。 +- 线程内拿不到主线程的全局变量,测试里专门验证了 `GLOBAL == nil`。 +- 每个用例结束后应 `lt.assertEquals(thread.errlog(), nil)` 检查是否遗留线程错误(`test_*.lua` 的 `assertNotThreadError` 约定)。 +- macOS/BSD 上 `thread.sleep` 用例在 `test/test_skip.lua` 里被跳过,跨平台测试注意平台差异。 +- 线程间通信不要共享 userdata,用 `bee.channel`。 diff --git a/.agents/skills/bee-time/SKILL.md b/.agents/skills/bee-time/SKILL.md new file mode 100644 index 00000000..051aa363 --- /dev/null +++ b/.agents/skills/bee-time/SKILL.md @@ -0,0 +1,49 @@ +--- +name: bee-time +description: 用 bee.time 获取毫秒级时间(time 墙钟、monotonic 单调递增、thread 线程 CPU 时间)。当需要测量耗时、实现超时/退避、或在线程中计时时使用。 +--- + +# bee.time + +`require "bee.time"`,对应 `meta/time.lua`、`test/test_time.lua`。三个函数都返回**毫秒整数**。 + +## API + +```lua +local time = require "bee.time" + +time.time() -- 自 Unix 纪元(1970-01-01 UTC) 起的毫秒数(墙钟,会受系统时间调整影响) +time.monotonic() -- 单调递增毫秒数,测间隔用这个 +time.thread() -- 当前线程已消耗的 CPU 时间(毫秒) +``` + +## 用法 + +测量耗时(`test_thread:test_sleep`): + +```lua +local t1 = time.monotonic() +thread.sleep(1) +local t2 = time.monotonic() +assert(t2 - t1 >= 1) +``` + +与 `os.time()` 的关系(`test_time:test_now`):`os.time() * 1000` 与 `time.time()` 相差不超过 2 秒。 + +超时轮询(`test_async.lua` 的 `wait_completion`): + +```lua +local start = time.monotonic() +while time.monotonic() - start < timeout then + for op, token, st, data, errcode in as:wait(100) do + return op, token, st, data, errcode + end +end +error "wait_completion timeout" +``` + +## 注意事项 + +- 计时一律用 `monotonic()`,`time()` 可能被系统时间调整(NTP、手动改钟)拉回或跳过。 +- 单位是毫秒,不是秒;不要与 `os.time()`(秒)混用。 +- 精度/粒度依平台,`test_time:test_monotonic` 只断言 `> 0`。 diff --git a/.agents/skills/bee-windows/SKILL.md b/.agents/skills/bee-windows/SKILL.md new file mode 100644 index 00000000..fa4863b4 --- /dev/null +++ b/.agents/skills/bee-windows/SKILL.md @@ -0,0 +1,59 @@ +--- +name: bee-windows +description: 用 bee.windows 处理 Windows 专有事项(u2a/a2u 编码转换、filemode、isatty、write_console、is_ssd、find_file_holders、process_name)。当需要控制台/ANSI 编码、TTY 判断、文件占用排查时使用。 +--- + +# bee.windows + +`require "bee.windows"`,对应 `meta/windows.lua`、`test/test_windows.lua`。**仅 Windows 可用**,其他平台 `require` 会失败,需自行按 `platform.os` 分支或 pcall。 + +## API + +| 函数 | 说明 | +|------|------| +| `windows.u2a(str)` | UTF-8 → ANSI(GBK) | +| `windows.a2u(str)` | ANSI(GBK) → UTF-8 | +| `windows.filemode(file, mode)` | 设置文本/二进制模式,`"t"` 文本、`"b"` 二进制,返回 `boolean` | +| `windows.isatty(file)` | 句柄是否为终端,返回 `boolean` | +| `windows.write_console(file, msg)` | 用 `WriteConsoleW` 写控制台,正确输出 UTF-16,返回写入字符数 | +| `windows.is_ssd(drive)` | 驱动器是否 SSD,`drive` 形如 `"C:"` 或 `"C"` | +| `windows.find_file_holders(filepath)` | 通过 NT API 枚举句柄表,返回占用该文件的 PID 数组 | +| `windows.process_name(pid)` | PID → 进程名(如 `"notepad.exe"`),失败返回空字符串 | + +## 用法 + +编码转换与终端输出: + +```lua +local windows = require "bee.windows" + +local ansi = windows.u2a "中文" -- GBK 字节串,可交给只认 ANSI 的 API +local utf8 = windows.a2u(ansi) + +if windows.isatty(io.stdout) then + windows.write_console(io.stdout, "中文\n") -- 避免 CRT 编码问题 +end +``` + +管道/文件读写时的模式控制(`test_subprocess` 里用它关掉 CRLF 转换): + +```lua +windows.filemode(io.stdin, "b") +assert(io.read "a" == "\r\n") -- 二进制模式下读到原始换行 +``` + +排查文件占用: + +```lua +local pids = windows.find_file_holders "d:/build/out.exe" +for _, pid in ipairs(pids) do + print(pid, windows.process_name(pid)) +end +``` + +## 注意事项 + +- `windows.is_ssd` 的参数是驱动器名,`"C"` 与 `"C:"` 都接受。 +- `windows.find_file_holders` 需要相应权限,且只列出**当前进程可见**的句柄持有者。 +- 遇到含代理对/生僻字的路径(WTF-8 场景),Lua 层字符串是 UTF-8 编码,文件 API 由库内部转换,测试 `test_windows:test_wtf8` 覆盖了 `io.open` 写这种文件名。 +- 非 Windows 平台不要 `require` 本模块。 diff --git a/AGENT.md b/AGENT.md index fe604938..e8514e4a 100644 --- a/AGENT.md +++ b/AGENT.md @@ -2,6 +2,8 @@ 本文件为 AI 编码助手在此仓库中工作时提供指引。 +> Lua 侧 API 与用法:索引见 [`.agents/bee-lua-api.md`](.agents/bee-lua-api.md),每个 `bee.*` 模块一个 skill 文件位于 [`.agents/skills/`](.agents/skills/)(`bee-/SKILL.md`,示例多摘自 `test/`)。 + ## 项目简介 **bee.lua** 是一个跨平台 Lua 扩展库,为 Lua 5.4 和 5.5 提供系统级原生绑定,封装了异步 I/O、网络、子进程管理、多线程、文件监控等操作系统 API,以统一的 Lua 接口对外暴露。 From 1ada6da0bfdd56623cb9fc9b656820250d01dee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 22 Sep 2026 11:49:18 +0800 Subject: [PATCH 2/6] fix skill examples flagged by review - async.create/writebuf: use positional arguments instead of invalid table syntax --- .agents/skills/bee-async/SKILL.md | 8 +++++--- .agents/skills/bee-channel/SKILL.md | 10 +++++++--- .agents/skills/bee-debugging/SKILL.md | 15 +++++++++++++-- .agents/skills/bee-epoll/SKILL.md | 3 ++- .agents/skills/bee-select/SKILL.md | 2 +- .agents/skills/bee-serialization/SKILL.md | 6 +++++- .agents/skills/bee-time/SKILL.md | 3 +++ 7 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.agents/skills/bee-async/SKILL.md b/.agents/skills/bee-async/SKILL.md index 8d586e4d..baa0dcb7 100644 --- a/.agents/skills/bee-async/SKILL.md +++ b/.agents/skills/bee-async/SKILL.md @@ -14,7 +14,7 @@ description: 用 bee.async 做跨平台异步 I/O(create 实例、submit_read/ ```lua local async = require "bee.async" -local as = assert(async.create([max_completions = 64])) -- <=0 报 "max_completions is less than or equal to zero." +local as = assert(async.create(64)) -- create(max_completions),默认 64;<=0 报 "max_completions is less than or equal to zero." as:associate(fd) -- Windows/IOCP 必需,其他平台 no-op as:associate_file(file) -- 文件 I/O 前必须调用(io.open 得到的 file*) @@ -38,12 +38,12 @@ as:submit_poll(fd, udata) -- 只监听可读,不消 缓冲区: ```lua -local wb = assert(async.writebuf([hwm = 65536])) +local wb = assert(async.writebuf(64 * 1024)) -- writebuf(hwm),默认 65536 wb:write(data) --> true 表示缓冲 >= hwm,调用方应自行背压 wb:buffered() --> 当前排队字节数 wb:close() -- 流关闭时丢弃未发数据 -local rb = assert(async.readbuf(bufsize)) -- 向上取整到 2 的幂;<=0 报 "bufsize must be positive" +local rb = assert(async.readbuf(bufsize)) -- readbuf(bufsize),向上取整到 2 的幂;<=0 报 "bufsize must be positive" rb:read([n]) --> string | nil(数据不足);n 省略取全部可用 rb:readline([sep = "\r\n"]) --> string | nil(未找到分隔符) ``` @@ -63,6 +63,8 @@ end ## 完整示例(取自 `test_async.lua`) ```lua +local async = require "bee.async" +local socket = require "bee.socket" local as = assert(async.create(64)) -- 服务端/客户端都要先 associate diff --git a/.agents/skills/bee-channel/SKILL.md b/.agents/skills/bee-channel/SKILL.md index 4016b2d8..21cbeced 100644 --- a/.agents/skills/bee-channel/SKILL.md +++ b/.agents/skills/bee-channel/SKILL.md @@ -26,15 +26,18 @@ box:fd() --> lightuserdata -- 用于 epoll/select 等可读 ```lua local chan = channel.create "test" chan:push(1024); chan:push(1025) -assert(chan:pop() == 1024) -assert(chan:pop() == 1025) -local ok = chan:pop() -- false,通道已空 +local ok, v = chan:pop(); assert(ok == true and v == 1024) -- pop 第一个返回值是 ok,第二个才是数据 +ok, v = chan:pop(); assert(ok == true and v == 1025) +ok, v = chan:pop() -- ok == false,通道已空(v 为 nil) channel.destroy "test" ``` ## 用法:worker + 请求/响应 ```lua +local thread = require "bee.thread" +local channel = require "bee.channel" + local req = channel.create "testReq" local res = channel.create "testRes" @@ -65,6 +68,7 @@ channel.destroy "testReq"; channel.destroy "testRes" worker 端监听 `req:fd()`,取到 `EPOLLIN` 后循环 `pop` 直到取空(`test_channel:test_fd`): ```lua +local epoll = require "bee.epoll" local epfd = epoll.create(16) epfd:event_add(req:fd(), epoll.EPOLLIN) for _, event in epfd:wait() do diff --git a/.agents/skills/bee-debugging/SKILL.md b/.agents/skills/bee-debugging/SKILL.md index f23deb56..826f9e1f 100644 --- a/.agents/skills/bee-debugging/SKILL.md +++ b/.agents/skills/bee-debugging/SKILL.md @@ -12,11 +12,22 @@ description: 用 bee.debugging 触发断点与探测调试器(breakpoint、bre ```lua local debugging = require "bee.debugging" -debugging.breakpoint() -- 无条件断点(无调试器时会走平台默认的 trap/SIGTRAP 语义) +debugging.breakpoint() -- 无条件触发断点指令 debugging.is_debugger_present() --> boolean debugging.breakpoint_if_debugging() -- 仅当有调试器附加时才中断,否则 no-op(安全版本) ``` +`breakpoint()` 的底层实现依编译器而定(`bee/nonstd/debugging.h`): + +| 构建环境 | 实现 | +|----------|------| +| 有 ``(C++26 `__cpp_lib_debugging`) | `std::breakpoint()` | +| MSVC(无 ``) | `__debugbreak()` | +| clang(无 ``) | `__builtin_debugtrap()` | +| 其它(如 GCC,无 ``) | 函数体为空,**no-op** | + +`is_debugger_present()`:Windows 用 `IsDebuggerPresent()`,macOS 用 `sysctl` 的 `P_TRACED`,其他平台恒返回 `false`(此时 `breakpoint_if_debugging()` 也就恒为 no-op)。 + ## 用法 想在调试器里断下来,但不想让正常运行时崩溃,用 `breakpoint_if_debugging`: @@ -34,6 +45,6 @@ debugging.breakpoint_if_debugging() -- 挂调试器则中断,否则什么 ## 注意事项 - 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 -- `breakpoint()` 在**没有**调试器附加时行为由平台决定(通常是触发异常/trap),生产代码里应优先用 `breakpoint_if_debugging()`。 +- `breakpoint()` **不判断**是否有调试器:没有调试器附加时,断点异常交给系统的默认处理器(可能直接终止进程),因此生产代码里应优先用 `breakpoint_if_debugging()`。 - `is_debugger_present()` 也可用于按环境切换日志级别。 - 本模块目前没有独立测试文件。 diff --git a/.agents/skills/bee-epoll/SKILL.md b/.agents/skills/bee-epoll/SKILL.md index 78d4326e..13a656fc 100644 --- a/.agents/skills/bee-epoll/SKILL.md +++ b/.agents/skills/bee-epoll/SKILL.md @@ -18,7 +18,7 @@ local epfd = assert(epoll.create(16)) -- max_events 必须 > 0, epfd:event_add(fd, events [, userdata]) --> true | nil, err epfd:event_mod(fd, events [, userdata]) --> true | nil, err epfd:event_del(fd) --> true | nil, err -epfd:wait([timeout]) --> iterator | nil(已 close 时) +epfd:wait([timeout]) --> iterator | nil, err(实例已 close 时返回 nil, "bad file descriptor") epfd:close() --> true | nil, err(重复 close 返回 nil) ``` @@ -49,6 +49,7 @@ epoll.EPOLLONESHOT -- 1 << 30 一次性 ## 用法 ```lua +local epoll = require "bee.epoll" local epfd = assert(epoll.create(16)) epfd:event_add(res_chan:fd(), epoll.EPOLLIN, "res") diff --git a/.agents/skills/bee-select/SKILL.md b/.agents/skills/bee-select/SKILL.md index 50aaec68..0a832d14 100644 --- a/.agents/skills/bee-select/SKILL.md +++ b/.agents/skills/bee-select/SKILL.md @@ -36,7 +36,7 @@ for obj, event in ctx:wait() do end ``` -只要事件标志时可以直接累加(来自 `test_socket.lua` 的 `simple_select`): +`event` 是**位标志**:同一轮里可能既有读也有写就绪,需要按位或把多次迭代的 `event` 累加起来(来自 `test_socket.lua` 的 `simple_select`): ```lua local function simple_select(fd, mode) diff --git a/.agents/skills/bee-serialization/SKILL.md b/.agents/skills/bee-serialization/SKILL.md index 11818800..c54503ff 100644 --- a/.agents/skills/bee-serialization/SKILL.md +++ b/.agents/skills/bee-serialization/SKILL.md @@ -35,7 +35,11 @@ local t = {} for i = 1, N do t[i] = {} end for i = 1, N do for j = 1, N do t[i][j] = t[j] end end local newt = seri.unpack(seri.pack(t)) -assert(newt[i][j] == newt[j]) +for i = 1, N do + for j = 1, N do + assert(newt[i][j] == newt[j]) -- 解出来仍指向同一张表 + end +end ``` ## 不支持的类型与报错文案(固定字符串,测试逐字断言) diff --git a/.agents/skills/bee-time/SKILL.md b/.agents/skills/bee-time/SKILL.md index 051aa363..69058488 100644 --- a/.agents/skills/bee-time/SKILL.md +++ b/.agents/skills/bee-time/SKILL.md @@ -22,6 +22,9 @@ time.thread() -- 当前线程已消耗的 CPU 时间(毫秒) 测量耗时(`test_thread:test_sleep`): ```lua +local time = require "bee.time" +local thread = require "bee.thread" + local t1 = time.monotonic() thread.sleep(1) local t2 = time.monotonic() From e5dde55cf97720414c7953126bc7314529f84049 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:37:03 +0000 Subject: [PATCH 3/6] Clarify bee.debugging trap behavior Co-authored-by: sumneko <5213431+sumneko@users.noreply.github.com> --- .agents/skills/bee-debugging/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/bee-debugging/SKILL.md b/.agents/skills/bee-debugging/SKILL.md index 826f9e1f..310282cd 100644 --- a/.agents/skills/bee-debugging/SKILL.md +++ b/.agents/skills/bee-debugging/SKILL.md @@ -45,6 +45,6 @@ debugging.breakpoint_if_debugging() -- 挂调试器则中断,否则什么 ## 注意事项 - 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 -- `breakpoint()` **不判断**是否有调试器:没有调试器附加时,断点异常交给系统的默认处理器(可能直接终止进程),因此生产代码里应优先用 `breakpoint_if_debugging()`。 +- `breakpoint()` **不判断**是否有调试器:在实现了 trap 的构建环境里(C++26 `std::breakpoint()` / MSVC / clang),没有调试器附加时断点异常会交给系统默认处理器(可能直接终止进程);而 GCC 等无 trap 实现的分支里它只是 no-op。因此生产代码里应优先用 `breakpoint_if_debugging()`。 - `is_debugger_present()` 也可用于按环境切换日志级别。 - 本模块目前没有独立测试文件。 From cf6dbaf6ce26fb5eda7d3eaec4ebaae1632c7093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 22 Sep 2026 17:29:17 +0800 Subject: [PATCH 4/6] restructure skills docs to skills/ layout bee.lua is usually consumed as a 3rd party dependency, so the docs must not live in .agents/skills/ where agent clients would auto-load them from the host project. --- .agents/bee-lua-api.md | 80 -------------- AGENT.md | 2 +- README.md | 5 + skills/SKILL.md | 103 ++++++++++++++++++ .../references/concurrency/channel.md | 7 +- .../references/concurrency/thread.md | 7 +- .../references/core/filesystem.md | 5 - .../references/core/serialization.md | 5 - .../SKILL.md => skills/references/core/sys.md | 5 - .../references/core/time.md | 5 - .../SKILL.md => skills/references/io/async.md | 5 - .../SKILL.md => skills/references/io/epoll.md | 5 - .../references/io/filewatch.md | 5 - .../references/io/select.md | 5 - .../references/io/socket.md | 5 - .../references/platform/crash.md | 5 - .../references/platform/debugging.md | 5 - .../references/platform/platform.md | 5 - .../references/platform/windows.md | 5 - .../references/process/subprocess.md | 5 - 20 files changed, 111 insertions(+), 163 deletions(-) delete mode 100644 .agents/bee-lua-api.md create mode 100644 skills/SKILL.md rename .agents/skills/bee-channel/SKILL.md => skills/references/concurrency/channel.md (90%) rename .agents/skills/bee-thread/SKILL.md => skills/references/concurrency/thread.md (84%) rename .agents/skills/bee-filesystem/SKILL.md => skills/references/core/filesystem.md (93%) rename .agents/skills/bee-serialization/SKILL.md => skills/references/core/serialization.md (87%) rename .agents/skills/bee-sys/SKILL.md => skills/references/core/sys.md (88%) rename .agents/skills/bee-time/SKILL.md => skills/references/core/time.md (85%) rename .agents/skills/bee-async/SKILL.md => skills/references/io/async.md (91%) rename .agents/skills/bee-epoll/SKILL.md => skills/references/io/epoll.md (90%) rename .agents/skills/bee-filewatch/SKILL.md => skills/references/io/filewatch.md (90%) rename .agents/skills/bee-select/SKILL.md => skills/references/io/select.md (89%) rename .agents/skills/bee-socket/SKILL.md => skills/references/io/socket.md (94%) rename .agents/skills/bee-crash/SKILL.md => skills/references/platform/crash.md (82%) rename .agents/skills/bee-debugging/SKILL.md => skills/references/platform/debugging.md (88%) rename .agents/skills/bee-platform/SKILL.md => skills/references/platform/platform.md (87%) rename .agents/skills/bee-windows/SKILL.md => skills/references/platform/windows.md (88%) rename .agents/skills/bee-subprocess/SKILL.md => skills/references/process/subprocess.md (92%) diff --git a/.agents/bee-lua-api.md b/.agents/bee-lua-api.md deleted file mode 100644 index 57e34e17..00000000 --- a/.agents/bee-lua-api.md +++ /dev/null @@ -1,80 +0,0 @@ -# bee.lua Lua API 索引 - -各模块的详细 skill 见 `.agents/skills/bee-/SKILL.md`。权威签名在 `meta/*.lua`(LuaLS 注解),行为契约在 `test/test_*.lua`。 - -## 模块 → skill - -| 模块 | skill | 用途 | -|------|-------|------| -| `bee.platform` | [bee-platform](skills/bee-platform/SKILL.md) | 平台/编译器/架构信息(纯数据表) | -| `bee.filesystem` | [bee-filesystem](skills/bee-filesystem/SKILL.md) | 路径与文件系统操作 | -| `bee.socket` | [bee-socket](skills/bee-socket/SKILL.md) | TCP/UDP/Unix socket | -| `bee.select` | [bee-select](skills/bee-select/SKILL.md) | select 风格多路复用 | -| `bee.epoll` | [bee-epoll](skills/bee-epoll/SKILL.md) | epoll 风格多路复用(Windows 走 IOCP) | -| `bee.async` | [bee-async](skills/bee-async/SKILL.md) | 异步 I/O(IOCP / io_uring / GCD) | -| `bee.time` | [bee-time](skills/bee-time/SKILL.md) | 墙钟 / 单调 / 线程 CPU 时间 | -| `bee.thread` | [bee-thread](skills/bee-thread/SKILL.md) | 线程 | -| `bee.channel` | [bee-channel](skills/bee-channel/SKILL.md) | 线程间通信 | -| `bee.serialization` | [bee-serialization](skills/bee-serialization/SKILL.md) | 序列化(线程/通道的底层) | -| `bee.subprocess` | [bee-subprocess](skills/bee-subprocess/SKILL.md) | 子进程与管道 | -| `bee.filewatch` | [bee-filewatch](skills/bee-filewatch/SKILL.md) | 文件监控 | -| `bee.sys` | [bee-sys](skills/bee-sys/SKILL.md) | 可执行文件路径、文件锁 | -| `bee.crash` | [bee-crash](skills/bee-crash/SKILL.md) | 崩溃 dump | -| `bee.debugging` | [bee-debugging](skills/bee-debugging/SKILL.md) | 断点 / 调试器探测 | -| `bee.windows` | [bee-windows](skills/bee-windows/SKILL.md) | Windows 专有工具 | - -## 跨模块约定 - -- 模块都在 `bee.*` 命名空间:`local socket = require "bee.socket"`。 -- **三态返回值**(socket / epoll / select 等非阻塞接口):成功 → 值;`false` → 需等待(非错误);`nil, errmsg` → 失败/对端关闭。参数校验错误才 `error()`。 -- 句柄类对象用 to-be-closed 管理:`local fd = assert(socket.create "tcp")`。 -- 子进程管道是标准 `file*`,用 `:read "a"` / `:write` / `:close`。 -- 线程/通道传值经 `bee.serialization`,只支持 `nil/boolean/number/string/table/light C function`。 - -## 构建与测试 - -```bash -luamake # 编译 + 测试 -luamake -notest # 只编译 -luamake test -v # 只测试,详细输出 -luamake test -v # 只跑名称匹配的用例(如 socket.test_udp) -``` - -测试基于 ltest,文件在 `test/`: - -```lua -local lt = require "ltest" -local m = lt.test "module" - -function m:test_case() - lt.assertEquals(a, b) - lt.assertNil(x); lt.assertIsUserdata(fd); lt.assertTrue(cond) - lt.assertError(function () ... end) - lt.assertErrorMsgEquals("max_completions is less than or equal to zero.", async.create, 0) - lt.failure "msg" -end -``` - -常用辅助: - -- `test/shell.lua` — `shell:runlua(script, spawn_options)` 起带正确 `package.cpath` 的 Lua 子进程;`shell:add_readonly/del_readonly`;`shell:pwd()`;`shell.is_luamake`。 -- `test/supported.lua` — `supported "symlink"` / `supported "hardlink"` 特性探测(结果缓存)。 -- `test/test_skip.lua` — 按平台 `lt.skip "module.test_name"` 跳过用例。 -- `test/test.lua` — 入口:设置 `package.path/cpath`、按平台装载测试文件、`lt.run()` 后 `os.exit`。 - -## 可选链(编译期 patch) - -`?.` / `?:` / `?[...]` / `f?(...)` 是 vendored Lua 的补丁语法,仅当 `luamake -optchain` 构建时可用: - -```lua -local a = obj?.a?.b?.c -- 链上任一环节为 nil 即短路为 nil -local v = t?[1]?[2] -local r = obj?:method(args) -- 只保护接收者,方法本身不存在仍报错 -local x = f?(1, 2) -``` - -- 只有 `nil` 短路,`false` 会照常报错。 -- 短路时参数/键不会被求值;接收者只求值一次。 -- 短路只覆盖链本身:`(nothing?.b).c`、`nothing?.b + 1` 仍报错。 -- 不可作为赋值目标。 -- `test/test_optional_chain.lua` 还锁定了生成的字节码布局。 diff --git a/AGENT.md b/AGENT.md index e8514e4a..236abba1 100644 --- a/AGENT.md +++ b/AGENT.md @@ -2,7 +2,7 @@ 本文件为 AI 编码助手在此仓库中工作时提供指引。 -> Lua 侧 API 与用法:索引见 [`.agents/bee-lua-api.md`](.agents/bee-lua-api.md),每个 `bee.*` 模块一个 skill 文件位于 [`.agents/skills/`](.agents/skills/)(`bee-/SKILL.md`,示例多摘自 `test/`)。 +> Lua 侧 API 与用法见 [`skills/SKILL.md`](skills/SKILL.md)(模块细节在 `skills/references/`,按 core / io / concurrency / process / platform 分组,示例多摘自 `test/`)。 ## 项目简介 diff --git a/README.md b/README.md index c1a127ee..80ef46ed 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,8 @@ Lua runtime and toolset * [fmtlib/fmt](https://github.com/fmtlib/fmt) Compatible with `std::format`(c++20) and `std::print`(c++23). * [gulrak/filesystem](https://github.com/gulrak/filesystem) Compatible with `std::filesystem`(c++17). * [actboy168/ltest](https://github.com/actboy168/ltest) Test framework. + +## Documentation + +Lua API reference and usage guide: see [`skills/SKILL.md`](skills/SKILL.md). AI coding agents should read it before working on `require "bee.*"` code in this repo. + diff --git a/skills/SKILL.md b/skills/SKILL.md new file mode 100644 index 00000000..790758a2 --- /dev/null +++ b/skills/SKILL.md @@ -0,0 +1,103 @@ +--- +name: bee-lua +description: bee.lua 运行时库指南——Lua 5.4/5.5 上的跨平台系统级绑定(异步 I/O、网络、子进程、线程、文件系统、文件监控、崩溃 dump)。当需要在本项目里编写、修改或排查使用 `require "bee.*"` 的 Lua 代码,涉及 socket/select/epoll/async、thread/channel/serialization、subprocess、filesystem/filewatch、time/sys/crash/debugging/windows,或需要按平台差异处理行为时,使用本 skill。即使用户没有明确提到 bee.lua,只要上下文是在调用 `bee` 命名空间下的模块,也应使用。不要用于纯 Lua 语言/标准库问题、与 bee 无关的第三方 C 模块,或本项目 C++ 层 `bee/`、`binding/` 的实现细节(那些看源码)。 +--- + +# bee.lua + +Lua 扩展库,为 Lua 5.4 / 5.5 提供系统级原生绑定。权威签名在 `meta/*.lua`(LuaLS 注解),行为契约在 `test/test_*.lua`。 + +## 快速开始 + +```lua +local socket = require "bee.socket" +local select = require "bee.select" + +local server = assert(socket.create "tcp") +assert(server:bind("127.0.0.1", 0)) +assert(server:listen()) +local _, port = server:info "socket":value() + +local s = select.create() +s:event_add(server, select.SELECT_READ) +s:wait() +local conn = assert(server:accept()) +``` + +## 模块索引 + +| 模块 | 文档 | 用途 | +|------|------|------| +| `bee.platform` | [platform](references/platform/platform.md) | 平台/编译器/架构信息(纯数据表) | +| `bee.filesystem` | [filesystem](references/core/filesystem.md) | 路径与文件系统操作 | +| `bee.serialization` | [serialization](references/core/serialization.md) | 序列化(线程/通道的底层) | +| `bee.time` | [time](references/core/time.md) | 墙钟 / 单调 / 线程 CPU 时间 | +| `bee.sys` | [sys](references/core/sys.md) | 可执行文件路径、文件锁 | +| `bee.socket` | [socket](references/io/socket.md) | TCP/UDP/Unix socket | +| `bee.select` | [select](references/io/select.md) | select 风格多路复用 | +| `bee.epoll` | [epoll](references/io/epoll.md) | epoll 风格多路复用(Windows 走 IOCP) | +| `bee.async` | [async](references/io/async.md) | 异步 I/O(IOCP / io_uring / GCD) | +| `bee.filewatch` | [filewatch](references/io/filewatch.md) | 文件监控 | +| `bee.thread` | [thread](references/concurrency/thread.md) | 线程 | +| `bee.channel` | [channel](references/concurrency/channel.md) | 线程间通信 | +| `bee.subprocess` | [subprocess](references/process/subprocess.md) | 子进程与管道 | +| `bee.windows` | [windows](references/platform/windows.md) | Windows 专有工具 | +| `bee.crash` | [crash](references/platform/crash.md) | 崩溃 dump | +| `bee.debugging` | [debugging](references/platform/debugging.md) | 断点 / 调试器探测 | + +## 跨模块约定 + +- 模块都在 `bee.*` 命名空间:`local socket = require "bee.socket"`。 +- **三态返回值**(socket / epoll / select 等非阻塞接口):成功 → 值;`false` → 需等待(非错误);`nil, errmsg` → 失败/对端关闭。参数校验错误才 `error()`。 +- 句柄类对象用 to-be-closed 管理:`local fd = assert(socket.create "tcp")`。 +- 子进程管道是标准 `file*`,用 `:read "a"` / `:write` / `:close`。 +- 线程/通道传值经 `bee.serialization`,只支持 `nil/boolean/number/string/table/light C function`。 +- 平台上不可用的模块/接口在测试中跳过(`test/test_skip.lua`、`test/supported.lua`)。 + +## 构建与测试 + +```bash +luamake # 编译 + 测试 +luamake -notest # 只编译 +luamake test -v # 只测试,详细输出 +luamake test -v # 只跑名称匹配的用例(如 socket.test_udp) +``` + +测试基于 ltest,文件在 `test/`: + +```lua +local lt = require "ltest" +local m = lt.test "module" + +function m:test_case() + lt.assertEquals(a, b) + lt.assertNil(x); lt.assertIsUserdata(fd); lt.assertTrue(cond) + lt.assertError(function () ... end) + lt.assertErrorMsgEquals("max_completions is less than or equal to zero.", async.create, 0) + lt.failure "msg" +end +``` + +常用辅助: + +- `test/shell.lua` — `shell:runlua(script, spawn_options)` 起带正确 `package.cpath` 的 Lua 子进程;`shell:add_readonly/del_readonly`;`shell:pwd()`;`shell.is_luamake`。 +- `test/supported.lua` — `supported "symlink"` / `supported "hardlink"` 特性探测(结果缓存)。 +- `test/test_skip.lua` — 按平台 `lt.skip "module.test_name"` 跳过用例。 +- `test/test.lua` — 入口:设置 `package.path/cpath`、按平台装载测试文件、`lt.run()` 后 `os.exit`。 + +## 可选链(编译期 patch) + +`?.` / `?:` / `?[...]` / `f?(...)` 是 vendored Lua 的补丁语法,仅当 `luamake -optchain` 构建时可用: + +```lua +local a = obj?.a?.b?.c -- 链上任一环节为 nil 即短路为 nil +local v = t?[1]?[2] +local r = obj?:method(args) -- 只保护接收者,方法本身不存在仍报错 +local x = f?(1, 2) +``` + +- 只有 `nil` 短路,`false` 会照常报错。 +- 短路时参数/键不会被求值;接收者只求值一次。 +- 短路只覆盖链本身:`(nothing?.b).c`、`nothing?.b + 1` 仍报错。 +- 不可作为赋值目标。 +- `test/test_optional_chain.lua` 还锁定了生成的字节码布局。 diff --git a/.agents/skills/bee-channel/SKILL.md b/skills/references/concurrency/channel.md similarity index 90% rename from .agents/skills/bee-channel/SKILL.md rename to skills/references/concurrency/channel.md index 21cbeced..af3741b1 100644 --- a/.agents/skills/bee-channel/SKILL.md +++ b/skills/references/concurrency/channel.md @@ -1,8 +1,3 @@ ---- -name: bee-channel -description: 用 bee.channel 做线程间通信(create/query/destroy 命名通道、box:push/pop 序列化传递、box:fd 接入 epoll/select 等待可读)。当需要多线程收发消息、或搭建 worker 请求-响应模型时使用。 ---- - # bee.channel `require "bee.channel"`,对应 `meta/channel.lua`、`test/test_channel.lua`。 @@ -89,6 +84,6 @@ end - 通道是**全局命名**的:`channel.query` 在别的线程里靠名字找回同一个通道,因此名字要唯一且双方约定一致。 - `create` 一个已存在的名字会 `error`;`test_reset_1` 说明 `destroy` 后可以重新 `create` 同名通道。 -- 传的数据经序列化,不能传 userdata / `thread` / 普通 Lua function(报错文案见 `bee-serialization`)。 +- 传的数据经序列化,不能传 userdata / `thread` / 普通 Lua function(报错文案见 [serialization](../core/serialization.md))。 - 通道内数据在 `destroy` 时被清空,不要依赖销毁后还能 `pop`。 - 双向通信要建两个通道(req/res),单个通道是单向队列。 diff --git a/.agents/skills/bee-thread/SKILL.md b/skills/references/concurrency/thread.md similarity index 84% rename from .agents/skills/bee-thread/SKILL.md rename to skills/references/concurrency/thread.md index 7cf88730..be58be74 100644 --- a/.agents/skills/bee-thread/SKILL.md +++ b/skills/references/concurrency/thread.md @@ -1,8 +1,3 @@ ---- -name: bee-thread -description: 用 bee.thread 创建原生线程(create 传源码字符串与参数、wait、sleep、setname、errlog、线程 id 与 preload_module,线程间不共享全局变量)。当需要并行执行 Lua 代码或搭建多线程 worker 时使用。 ---- - # bee.thread `require "bee.thread"`,对应 `meta/thread.lua`、`test/test_thread.lua`。 @@ -50,7 +45,7 @@ assert(string.find(msg, "Test thread error.", nil, true)) ## 注意事项 - `source` 必须是**字符串源码**,不能传函数;新线程只拿到自己的环境,只能通过 `require "bee.*"` 或参数传递数据。 -- 参数与返回数据要经过 `bee.serialization`,限制见 `bee-serialization` skill(不能传 userdata、`thread`、普通 Lua function)。 +- 参数与返回数据要经过 `bee.serialization`,限制见 [serialization](../core/serialization.md)(不能传 userdata、`thread`、普通 Lua function)。 - 线程内拿不到主线程的全局变量,测试里专门验证了 `GLOBAL == nil`。 - 每个用例结束后应 `lt.assertEquals(thread.errlog(), nil)` 检查是否遗留线程错误(`test_*.lua` 的 `assertNotThreadError` 约定)。 - macOS/BSD 上 `thread.sleep` 用例在 `test/test_skip.lua` 里被跳过,跨平台测试注意平台差异。 diff --git a/.agents/skills/bee-filesystem/SKILL.md b/skills/references/core/filesystem.md similarity index 93% rename from .agents/skills/bee-filesystem/SKILL.md rename to skills/references/core/filesystem.md index 4b38b90f..2a36a1cc 100644 --- a/.agents/skills/bee-filesystem/SKILL.md +++ b/skills/references/core/filesystem.md @@ -1,8 +1,3 @@ ---- -name: bee-filesystem -description: 用 bee.filesystem 做路径与文件系统操作(fspath 对象、exists/copy/remove_all、pairs 目录遍历、时间与权限、符号链接)。当需要读写路径、遍历目录、批量复制删除文件时使用。 ---- - # bee.filesystem `require "bee.filesystem"`,对应 `meta/filesystem.lua`、`test/test_filesystem.lua`。 diff --git a/.agents/skills/bee-serialization/SKILL.md b/skills/references/core/serialization.md similarity index 87% rename from .agents/skills/bee-serialization/SKILL.md rename to skills/references/core/serialization.md index c54503ff..2d2e8e7a 100644 --- a/.agents/skills/bee-serialization/SKILL.md +++ b/skills/references/core/serialization.md @@ -1,8 +1,3 @@ ---- -name: bee-serialization -description: 用 bee.serialization 在线程/通道间传递数据(pack/packstring/unpack 的类型限制与报错文案、引用共享保留、lightuserdata 转换)。当需要跨线程传表、或在 channel:push 前预处理复杂结构时使用。 ---- - # bee.serialization `require "bee.serialization"`,对应 `meta/serialization.lua`、`test/test_serialization.lua`。 diff --git a/.agents/skills/bee-sys/SKILL.md b/skills/references/core/sys.md similarity index 88% rename from .agents/skills/bee-sys/SKILL.md rename to skills/references/core/sys.md index 35479532..e33b18c3 100644 --- a/.agents/skills/bee-sys/SKILL.md +++ b/skills/references/core/sys.md @@ -1,8 +1,3 @@ ---- -name: bee-sys -description: 用 bee.sys 获取可执行文件/动态库路径、解析文件完整路径、创建进程级文件锁。当需要定位自身可执行文件、防重入单实例锁或规范化路径时使用。 ---- - # bee.sys `require "bee.sys"`,对应 `meta/sys.lua`、`test/test_sys.lua`。 diff --git a/.agents/skills/bee-time/SKILL.md b/skills/references/core/time.md similarity index 85% rename from .agents/skills/bee-time/SKILL.md rename to skills/references/core/time.md index 69058488..21a1b180 100644 --- a/.agents/skills/bee-time/SKILL.md +++ b/skills/references/core/time.md @@ -1,8 +1,3 @@ ---- -name: bee-time -description: 用 bee.time 获取毫秒级时间(time 墙钟、monotonic 单调递增、thread 线程 CPU 时间)。当需要测量耗时、实现超时/退避、或在线程中计时时使用。 ---- - # bee.time `require "bee.time"`,对应 `meta/time.lua`、`test/test_time.lua`。三个函数都返回**毫秒整数**。 diff --git a/.agents/skills/bee-async/SKILL.md b/skills/references/io/async.md similarity index 91% rename from .agents/skills/bee-async/SKILL.md rename to skills/references/io/async.md index baa0dcb7..2d8c485b 100644 --- a/.agents/skills/bee-async/SKILL.md +++ b/skills/references/io/async.md @@ -1,8 +1,3 @@ ---- -name: bee-async -description: 用 bee.async 做跨平台异步 I/O(create 实例、submit_read/submit_write/submit_accept/submit_connect/submit_file_read/submit_file_write/submit_poll 投递、readbuf/writebuf 缓冲区、poll/wait 返回 completion 迭代器、SUCCESS/CLOSE/ERROR/CANCEL 与 OP_* 常量,Windows 需 associate)。当需要高吞吐异步收发、非阻塞文件 I/O 或基于完成事件的事件循环时使用。 ---- - # bee.async `require "bee.async"`,对应 `meta/async.lua`、`test/test_async.lua`。macOS 用 GCD,Windows 用 IOCP,Linux 用 io_uring/epoll。 diff --git a/.agents/skills/bee-epoll/SKILL.md b/skills/references/io/epoll.md similarity index 90% rename from .agents/skills/bee-epoll/SKILL.md rename to skills/references/io/epoll.md index 13a656fc..f8d12a7e 100644 --- a/.agents/skills/bee-epoll/SKILL.md +++ b/skills/references/io/epoll.md @@ -1,8 +1,3 @@ ---- -name: bee-epoll -description: 用 bee.epoll 做 epoll 风格 I/O 多路复用(create/event_add/event_mod/event_del/wait 迭代器、EPOLLIN/EPOLLOUT 等位标志、关联自定义 userdata,Windows 下由 IOCP 实现)。当需要监听多个 fd 或 channel 可读事件时使用。 ---- - # bee.epoll `require "bee.epoll"`,对应 `meta/epoll.lua`、`test/test_epoll.lua`、`test/test_channel.lua`。 diff --git a/.agents/skills/bee-filewatch/SKILL.md b/skills/references/io/filewatch.md similarity index 90% rename from .agents/skills/bee-filewatch/SKILL.md rename to skills/references/io/filewatch.md index 0fd4b141..bfed6c5e 100644 --- a/.agents/skills/bee-filewatch/SKILL.md +++ b/skills/references/io/filewatch.md @@ -1,8 +1,3 @@ ---- -name: bee-filewatch -description: 用 bee.filewatch 监控文件系统变化(create、add 路径、set_recursive/set_follow_symlinks/set_filter、select 轮询 modify/rename 事件)。当需要实现热重载、构建监听或检测目录变更时使用。 ---- - # bee.filewatch `require "bee.filewatch"`,对应 `meta/filewatch.lua`、`test/test_filewatch.lua`。底层:inotify / FSEvents / ReadDirectoryChangesW。 diff --git a/.agents/skills/bee-select/SKILL.md b/skills/references/io/select.md similarity index 89% rename from .agents/skills/bee-select/SKILL.md rename to skills/references/io/select.md index 0a832d14..88bae43b 100644 --- a/.agents/skills/bee-select/SKILL.md +++ b/skills/references/io/select.md @@ -1,8 +1,3 @@ ---- -name: bee-select -description: 用 bee.select 做 select 风格 I/O 多路复用(create/event_add/event_mod/event_del/wait 迭代器、SELECT_READ 与 SELECT_WRITE 位标志、关联自定义 userdata)。当需要同时等待多个 fd 可读可写时使用。 ---- - # bee.select `require "bee.select"`,对应 `meta/select.lua`、`test/test_socket.lua`。 diff --git a/.agents/skills/bee-socket/SKILL.md b/skills/references/io/socket.md similarity index 94% rename from .agents/skills/bee-socket/SKILL.md rename to skills/references/io/socket.md index d5a9491b..9fc42b98 100644 --- a/.agents/skills/bee-socket/SKILL.md +++ b/skills/references/io/socket.md @@ -1,8 +1,3 @@ ---- -name: bee-socket -description: 用 bee.socket 创建 TCP/UDP/Unix 套接字(create/bind/listen/accept/connect/send/recv/sendto/recvfrom、端点对象、detach 与还原、非阻塞三态返回值)。当需要网络编程或实现回显服务时使用。 ---- - # bee.socket `require "bee.socket"`,对应 `meta/socket.lua`、`test/test_socket.lua`。 diff --git a/.agents/skills/bee-crash/SKILL.md b/skills/references/platform/crash.md similarity index 82% rename from .agents/skills/bee-crash/SKILL.md rename to skills/references/platform/crash.md index af52863d..20e7bab1 100644 --- a/.agents/skills/bee-crash/SKILL.md +++ b/skills/references/platform/crash.md @@ -1,8 +1,3 @@ ---- -name: bee-crash -description: 用 bee.crash 安装崩溃处理器、在进程崩溃时落 dump 文件(create_handler、dump 路径与 "-" 关闭落盘)。当需要捕获 native crash 现场、生成崩溃报告或想显式关闭 dump 写入时使用。 ---- - # bee.crash `require "bee.crash"`,对应 `meta/crash.lua`、`binding/lua_crash.cpp`、`test/test.lua`。 diff --git a/.agents/skills/bee-debugging/SKILL.md b/skills/references/platform/debugging.md similarity index 88% rename from .agents/skills/bee-debugging/SKILL.md rename to skills/references/platform/debugging.md index 310282cd..72a613bb 100644 --- a/.agents/skills/bee-debugging/SKILL.md +++ b/skills/references/platform/debugging.md @@ -1,8 +1,3 @@ ---- -name: bee-debugging -description: 用 bee.debugging 触发断点与探测调试器(breakpoint、breakpoint_if_debugging、is_debugger_present)。当需要让调试器在指定位置中断、或按是否挂调试器切换行为时使用。 ---- - # bee.debugging `require "bee.debugging"`,对应 `meta/debugging.lua`、`binding/lua_debugging.cpp`。底层是 `std::breakpoint()` / `std::is_debugger_present()`。 diff --git a/.agents/skills/bee-platform/SKILL.md b/skills/references/platform/platform.md similarity index 87% rename from .agents/skills/bee-platform/SKILL.md rename to skills/references/platform/platform.md index d1453eab..c50a395b 100644 --- a/.agents/skills/bee-platform/SKILL.md +++ b/skills/references/platform/platform.md @@ -1,8 +1,3 @@ ---- -name: bee-platform -description: 用 bee.platform 读取当前运行平台信息(OS、架构、编译器、CRT、Debug 标志、OS 版本号)。当需要按平台分支代码、或在测试中检测平台差异时使用。 ---- - # bee.platform 平台信息模块。返回的是**普通表**(非类),无需 ``。 diff --git a/.agents/skills/bee-windows/SKILL.md b/skills/references/platform/windows.md similarity index 88% rename from .agents/skills/bee-windows/SKILL.md rename to skills/references/platform/windows.md index fa4863b4..3b98dfde 100644 --- a/.agents/skills/bee-windows/SKILL.md +++ b/skills/references/platform/windows.md @@ -1,8 +1,3 @@ ---- -name: bee-windows -description: 用 bee.windows 处理 Windows 专有事项(u2a/a2u 编码转换、filemode、isatty、write_console、is_ssd、find_file_holders、process_name)。当需要控制台/ANSI 编码、TTY 判断、文件占用排查时使用。 ---- - # bee.windows `require "bee.windows"`,对应 `meta/windows.lua`、`test/test_windows.lua`。**仅 Windows 可用**,其他平台 `require` 会失败,需自行按 `platform.os` 分支或 pcall。 diff --git a/.agents/skills/bee-subprocess/SKILL.md b/skills/references/process/subprocess.md similarity index 92% rename from .agents/skills/bee-subprocess/SKILL.md rename to skills/references/process/subprocess.md index 74521bfc..40555c74 100644 --- a/.agents/skills/bee-subprocess/SKILL.md +++ b/skills/references/process/subprocess.md @@ -1,8 +1,3 @@ ---- -name: bee-subprocess -description: 用 bee.subprocess 启动与管理子进程(spawn 配置表、stdin/stdout/stderr 管道或重定向、env/cwd、wait/kill/is_running/detach、select 批量等待、setenv/quotearg)。当需要调外部命令、跑测试子进程或用管道做进程间通信时使用。 ---- - # bee.subprocess `require "bee.subprocess"`,对应 `meta/subprocess.lua`、`test/test_subprocess.lua`、`test/shell.lua`。 From 46c65bcf2d49ac2ed9601aaaaafa45ba441e1868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 23 Sep 2026 10:46:47 +0800 Subject: [PATCH 5/6] point skills at meta/, drop duplicated API signatures The meta/*.lua annotations are the authoritative API source and ship with the repo, so duplicating them in the skill docs only creates drift (this PR already had wrong signatures caught in review). --- AGENT.md | 2 +- README.md | 3 +- skills/SKILL.md | 50 ++++++----- skills/references/concurrency/channel.md | 46 +++++----- skills/references/concurrency/thread.md | 34 +++----- skills/references/core/filesystem.md | 77 ++++------------- skills/references/core/serialization.md | 25 ++---- skills/references/core/sys.md | 31 +++---- skills/references/core/time.md | 19 ++--- skills/references/io/async.md | 104 ++++++----------------- skills/references/io/epoll.md | 55 +++--------- skills/references/io/filewatch.md | 34 +++----- skills/references/io/select.md | 38 ++++----- skills/references/io/socket.md | 60 ++++--------- skills/references/platform/crash.md | 24 ++---- skills/references/platform/debugging.md | 26 ++---- skills/references/platform/platform.md | 20 ++--- skills/references/platform/windows.md | 43 +++------- skills/references/process/subprocess.md | 73 +++++----------- 19 files changed, 247 insertions(+), 517 deletions(-) diff --git a/AGENT.md b/AGENT.md index 236abba1..b42b5141 100644 --- a/AGENT.md +++ b/AGENT.md @@ -2,7 +2,7 @@ 本文件为 AI 编码助手在此仓库中工作时提供指引。 -> Lua 侧 API 与用法见 [`skills/SKILL.md`](skills/SKILL.md)(模块细节在 `skills/references/`,按 core / io / concurrency / process / platform 分组,示例多摘自 `test/`)。 +> Lua 侧 API 与用法见 [`skills/SKILL.md`](skills/SKILL.md)(模块细节在 `skills/references/`,按 core / io / concurrency / process / platform 分组,示例多摘自 `test/`)。查签名一律以 `meta/*.lua` 为准,`skills/` 只写 meta 表达不了的约定与陷阱。 ## 项目简介 diff --git a/README.md b/README.md index 80ef46ed..67699095 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,6 @@ Lua runtime and toolset ## Documentation -Lua API reference and usage guide: see [`skills/SKILL.md`](skills/SKILL.md). AI coding agents should read it before working on `require "bee.*"` code in this repo. +Lua API: see [`meta/`](meta) (LuaLS annotations, authoritative for signatures) and [`test/`](test) (behavior contract). +Usage guide and conventions: see [`skills/SKILL.md`](skills/SKILL.md). AI coding agents should read it before working on `require "bee.*"` code in this repo. diff --git a/skills/SKILL.md b/skills/SKILL.md index 790758a2..f95dc92f 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -5,7 +5,19 @@ description: bee.lua 运行时库指南——Lua 5.4/5.5 上的跨平台系统 # bee.lua -Lua 扩展库,为 Lua 5.4 / 5.5 提供系统级原生绑定。权威签名在 `meta/*.lua`(LuaLS 注解),行为契约在 `test/test_*.lua`。 +Lua 扩展库,为 Lua 5.4 / 5.5 提供系统级原生绑定。 + +## API 参考来源 + +**本 skill 不维护 bee 的 API 签名表**,请直接读仓库里的权威来源: + +| 目的 | 来源 | +|------|------| +| 函数签名、参数/返回值类型、字段与常量 | `meta/.lua`(LuaLS/EmmyLua 注解,带中文说明) | +| 行为契约、错误文案、边界情况 | `test/test_.lua` | +| C++ 层实现细节 | `bee/`、`binding/lua_.cpp` | + +`skills/references/` 下的文档只写**从 meta 里读不出来的东西**:跨模块约定、非显然语义与陷阱、可运行片段、平台差异。读 meta 拿到签名后发现行为不明确时,回来查对应文档或直接看测试。 ## 快速开始 @@ -26,24 +38,24 @@ local conn = assert(server:accept()) ## 模块索引 -| 模块 | 文档 | 用途 | -|------|------|------| -| `bee.platform` | [platform](references/platform/platform.md) | 平台/编译器/架构信息(纯数据表) | -| `bee.filesystem` | [filesystem](references/core/filesystem.md) | 路径与文件系统操作 | -| `bee.serialization` | [serialization](references/core/serialization.md) | 序列化(线程/通道的底层) | -| `bee.time` | [time](references/core/time.md) | 墙钟 / 单调 / 线程 CPU 时间 | -| `bee.sys` | [sys](references/core/sys.md) | 可执行文件路径、文件锁 | -| `bee.socket` | [socket](references/io/socket.md) | TCP/UDP/Unix socket | -| `bee.select` | [select](references/io/select.md) | select 风格多路复用 | -| `bee.epoll` | [epoll](references/io/epoll.md) | epoll 风格多路复用(Windows 走 IOCP) | -| `bee.async` | [async](references/io/async.md) | 异步 I/O(IOCP / io_uring / GCD) | -| `bee.filewatch` | [filewatch](references/io/filewatch.md) | 文件监控 | -| `bee.thread` | [thread](references/concurrency/thread.md) | 线程 | -| `bee.channel` | [channel](references/concurrency/channel.md) | 线程间通信 | -| `bee.subprocess` | [subprocess](references/process/subprocess.md) | 子进程与管道 | -| `bee.windows` | [windows](references/platform/windows.md) | Windows 专有工具 | -| `bee.crash` | [crash](references/platform/crash.md) | 崩溃 dump | -| `bee.debugging` | [debugging](references/platform/debugging.md) | 断点 / 调试器探测 | +| 模块 | meta | 说明文档 | 用途 | +|------|------|----------|------| +| `bee.platform` | `meta/platform.lua` | [platform](references/platform/platform.md) | 平台/编译器/架构信息(纯数据表) | +| `bee.filesystem` | `meta/filesystem.lua` | [filesystem](references/core/filesystem.md) | 路径与文件系统操作 | +| `bee.serialization` | `meta/serialization.lua` | [serialization](references/core/serialization.md) | 序列化(线程/通道的底层) | +| `bee.time` | `meta/time.lua` | [time](references/core/time.md) | 墙钟 / 单调 / 线程 CPU 时间 | +| `bee.sys` | `meta/sys.lua` | [sys](references/core/sys.md) | 可执行文件路径、文件锁 | +| `bee.socket` | `meta/socket.lua` | [socket](references/io/socket.md) | TCP/UDP/Unix socket | +| `bee.select` | `meta/select.lua` | [select](references/io/select.md) | select 风格多路复用 | +| `bee.epoll` | `meta/epoll.lua` | [epoll](references/io/epoll.md) | epoll 风格多路复用(Windows 走 IOCP) | +| `bee.async` | `meta/async.lua` | [async](references/io/async.md) | 异步 I/O(IOCP / io_uring / GCD) | +| `bee.filewatch` | `meta/filewatch.lua` | [filewatch](references/io/filewatch.md) | 文件监控 | +| `bee.thread` | `meta/thread.lua` | [thread](references/concurrency/thread.md) | 线程 | +| `bee.channel` | `meta/channel.lua` | [channel](references/concurrency/channel.md) | 线程间通信 | +| `bee.subprocess` | `meta/subprocess.lua` | [subprocess](references/process/subprocess.md) | 子进程与管道 | +| `bee.windows` | `meta/windows.lua` | [windows](references/platform/windows.md) | Windows 专有工具 | +| `bee.crash` | `meta/crash.lua` | [crash](references/platform/crash.md) | 崩溃 dump | +| `bee.debugging` | `meta/debugging.lua` | [debugging](references/platform/debugging.md) | 断点 / 调试器探测 | ## 跨模块约定 diff --git a/skills/references/concurrency/channel.md b/skills/references/concurrency/channel.md index af3741b1..a2f288be 100644 --- a/skills/references/concurrency/channel.md +++ b/skills/references/concurrency/channel.md @@ -1,33 +1,28 @@ # bee.channel -`require "bee.channel"`,对应 `meta/channel.lua`、`test/test_channel.lua`。 +线程间通信(命名通道)。签名见 `meta/channel.lua`,行为契约见 `test/test_channel.lua`。 -## API +## 要点 -```lua -local channel = require "bee.channel" - -local box = channel.create(name) -- 名称必须唯一,重复则 error: "Duplicate channel 'test'" -local box = channel.query(name) --> box | nil, err -channel.destroy(name) -- 清空数据并销毁 - -box:push(...) -- 序列化后入队(类型限制同 bee.serialization) -local ok, ... = box:pop() -- ok == false 表示通道为空(此时第二个返回值为 nil) -box:fd() --> lightuserdata -- 用于 epoll/select 等可读 -``` +- 通道是**全局命名**的:`channel.query(name)` 在别的线程里靠名字找回同一个通道,所以名字要唯一且双方约定一致;`create` 重名会 `error`(`destroy` 后可以重新 `create`)。 +- `push(...)` 内部序列化,类型限制同 [serialization](../core/serialization.md)。 +- `pop()` 是 **FIFO 逐条出队**,返回 `(ok, ...)`:`ok == false` 表示通道为空。 +- `fd()` 给 epoll/select 用,能等可读,避免空转。 -`pop` 逐条出队,FIFO: +## 用法 ```lua +local channel = require "bee.channel" + local chan = channel.create "test" chan:push(1024); chan:push(1025) -local ok, v = chan:pop(); assert(ok == true and v == 1024) -- pop 第一个返回值是 ok,第二个才是数据 +local ok, v = chan:pop(); assert(ok == true and v == 1024) -- 第一个返回值是 ok ok, v = chan:pop(); assert(ok == true and v == 1025) -ok, v = chan:pop() -- ok == false,通道已空(v 为 nil) +ok, v = chan:pop() -- ok == false,通道已空 channel.destroy "test" ``` -## 用法:worker + 请求/响应 +worker + 请求/响应(双向要建两个通道): ```lua local thread = require "bee.thread" @@ -52,24 +47,23 @@ local thd = thread.create([[ ]]) req:push("echo", 1, { A = { B = "C" } }) -local ok, what, arg = res:pop() -- 阻塞式轮询 +local ok, what, arg = res:pop() req:push "exit" thread.wait(thd) channel.destroy "testReq"; channel.destroy "testRes" ``` -## 用法:用 fd 参与多路复用(避免空转) - -worker 端监听 `req:fd()`,取到 `EPOLLIN` 后循环 `pop` 直到取空(`test_channel:test_fd`): +用 `fd()` 接多路复用,替掉空转(`test_channel:test_fd`): ```lua local epoll = require "bee.epoll" + local epfd = epoll.create(16) epfd:event_add(req:fd(), epoll.EPOLLIN) for _, event in epfd:wait() do if event & (epoll.EPOLLERR | epoll.EPOLLHUP) ~= 0 then error "unknown error" end if event & epoll.EPOLLIN ~= 0 then - while true do + while true do -- 取空为止 local ok, what, ... = req:pop() if not ok then break end -- 分发;收到 "exit" 则 return @@ -82,8 +76,6 @@ end ## 注意事项 -- 通道是**全局命名**的:`channel.query` 在别的线程里靠名字找回同一个通道,因此名字要唯一且双方约定一致。 -- `create` 一个已存在的名字会 `error`;`test_reset_1` 说明 `destroy` 后可以重新 `create` 同名通道。 -- 传的数据经序列化,不能传 userdata / `thread` / 普通 Lua function(报错文案见 [serialization](../core/serialization.md))。 -- 通道内数据在 `destroy` 时被清空,不要依赖销毁后还能 `pop`。 -- 双向通信要建两个通道(req/res),单个通道是单向队列。 +- 单个通道是**单向队列**,双向通信要建两个。 +- `destroy` 会清空通道内数据,不要依赖销毁后还能 `pop`。 +- 用 `fd()` 等可读时,收到通知后必须 `pop` 到 `ok == false`,否则会一直就绪。 diff --git a/skills/references/concurrency/thread.md b/skills/references/concurrency/thread.md index be58be74..4aa6d483 100644 --- a/skills/references/concurrency/thread.md +++ b/skills/references/concurrency/thread.md @@ -1,20 +1,15 @@ # bee.thread -`require "bee.thread"`,对应 `meta/thread.lua`、`test/test_thread.lua`。 +原生线程。签名见 `meta/thread.lua`,行为契约见 `test/test_thread.lua`。 -## API +## 要点 -```lua -local thread = require "bee.thread" - -thread.create(source, ...) --> handle(lightuserdata) -- source 是 Lua 源码字符串 -thread.wait(handle) -- 等线程结束 -thread.sleep(msec) -thread.errlog() --> string | nil -- 取走并清空线程错误日志 -thread.setname(name) -- 给当前线程命名(调试用) -thread.id -- 主线程为 0,其他线程非 0 -thread.preload_module(L) -- 新线程内部用,注册 bee.* 模块到指定 lua_State -``` +- `create(source, ...)` 的 `source` 是**Lua 源码字符串**,不能传函数;额外参数序列化后在新线程里以 `...` 取得。 +- 新线程**不共享全局变量**,只能靠 `require "bee.*"` 或参数拿数据。 +- `thread.id` 主线程为 0,其他线程非 0。 +- 线程里的错误不会中断主线程,累积在 `errlog()`(读取即取走并清空)。 +- 跨线程传值走 `bee.serialization`,类型限制见 [serialization](../core/serialization.md)。 +- 线程间通信不要共享 userdata,用 `bee.channel`。 ## 用法 @@ -24,7 +19,7 @@ local thread = require "bee.thread" GLOBAL = true local thd = thread.create([[ local thread = require "bee.thread" - local args = ... -- 传给 create 的额外参数会被序列化后传入 + local args = ... -- 传给 create 的额外参数 assert(GLOBAL == nil) -- 线程不共享全局变量 assert(thread.id ~= 0) thread.setname "worker" @@ -33,7 +28,7 @@ thread.wait(thd) assert(thread.errlog() == nil) ``` -线程内错误不会中断主线程,集中记录在 `errlog`(`test_thread:test_thread_3`): +线程内的错误(`test_thread:test_thread_3`): ```lua local thd = thread.create [[ error "Test thread error." ]] @@ -44,9 +39,6 @@ assert(string.find(msg, "Test thread error.", nil, true)) ## 注意事项 -- `source` 必须是**字符串源码**,不能传函数;新线程只拿到自己的环境,只能通过 `require "bee.*"` 或参数传递数据。 -- 参数与返回数据要经过 `bee.serialization`,限制见 [serialization](../core/serialization.md)(不能传 userdata、`thread`、普通 Lua function)。 -- 线程内拿不到主线程的全局变量,测试里专门验证了 `GLOBAL == nil`。 -- 每个用例结束后应 `lt.assertEquals(thread.errlog(), nil)` 检查是否遗留线程错误(`test_*.lua` 的 `assertNotThreadError` 约定)。 -- macOS/BSD 上 `thread.sleep` 用例在 `test/test_skip.lua` 里被跳过,跨平台测试注意平台差异。 -- 线程间通信不要共享 userdata,用 `bee.channel`。 +- 每个用例结束都应检查 `thread.errlog() == nil`(`test_*.lua` 里的 `assertNotThreadError` 约定),避免错误被静默吞掉。 +- macOS / BSD 上 `thread.sleep` 用例在 `test/test_skip.lua` 里被跳过,跨平台测试注意这一点。 +- `thread.sleep(0)` 是合法的「让出」写法,`test_channel` 的 worker 空转循环就靠它。 diff --git a/skills/references/core/filesystem.md b/skills/references/core/filesystem.md index 2a36a1cc..b40e6708 100644 --- a/skills/references/core/filesystem.md +++ b/skills/references/core/filesystem.md @@ -1,65 +1,29 @@ # bee.filesystem -`require "bee.filesystem"`,对应 `meta/filesystem.lua`、`test/test_filesystem.lua`。 +文件系统与路径操作。签名见 `meta/filesystem.lua`,行为契约见 `test/test_filesystem.lua`。 -## 路径对象 bee.fspath +## 要点 -`fs.path(p)` 创建;所有接受路径的接口同时接受字符串。 +- 路径对象是 `bee.fspath`,`fs.path(p)` 创建;所有接口同时接受字符串。 +- `a / b` 是路径拼接(会补分隔符),`a .. b` 是直接拼接;取字符串用 `:string()`。 +- `fs.pairs` 非递归 / `fs.pairs_r` 递归,迭代产出 `(bee.fspath, bee.directory_entry)`;**目录不可遍历时抛错**,不是返回 `nil, err`。 +- 选项是位标志:`fs.copy_options` / `fs.perm_options` / `fs.directory_options`,用 `|` 组合。 +- `fs.current_path()` 无参读 CWD、有参切换;`fs.last_write_time` 与 `fs.permissions` 都是读写两用。 ```lua local fs = require "bee.filesystem" -local p = fs.path "a/b/c.ext" -p:string() -- "a/b/c.ext"(Windows 下分隔符统一为 /) -p:filename() --> bee.fspath "c.ext" -p:parent_path() --> "a/b" -p:stem() --> "c" -p:extension() --> ".ext" -p:is_absolute() / p:is_relative() -p:remove_filename() / p:replace_filename(x) / p:replace_extension(".lua") -p:lexically_normal() -``` - -运算符:`a / b` 路径拼接(加分隔符),`a .. b` 直接拼接。 - -```lua local root = fs.absolute("./temp/"):lexically_normal() -fs.create_directories(root / "dir") -- temp/dir -``` - -## 查询与操作 - -```lua -fs.status(p) / fs.symlink_status(p) --> bee.file_status(:type() / :exists() / :is_directory() / :is_regular_file()) -fs.exists / fs.is_directory / fs.is_regular_file / fs.file_size -fs.create_directory(p) -- 已存在返回 false -fs.create_directories(p) -- 递归创建 -fs.rename(from, to) / fs.remove(p) -- remove 对不存在的路径返回 false -fs.remove_all(p) -- 递归删除,返回删除数量 -fs.copy(from, to [, options]) / fs.copy_file(from, to [, options]) -fs.absolute(p) / fs.canonical(p) / fs.relative(p [, base]) -fs.current_path([p]) -- 无参返回当前 CWD(fspath),有参则切换 -fs.temp_directory_path() -fs.last_write_time(p [, t]) -- 秒级 Unix 时间戳,读写两用 -fs.permissions(p [, perms, options]) -- 读写两用,位标志 -fs.space(p) --> { capacity, free, available }(字节) -fs.create_symlink(target, link) / fs.create_directory_symlink / fs.create_hard_link -``` - -`file_status:type()` 取值:`"none"|"not_found"|"regular"|"directory"|"symlink"|"block"|"character"|"fifo"|"socket"|"junction"|"unknown"`。 - -## 目录遍历 +fs.create_directories(root / "dir") -`fs.pairs(dir)` 非递归、`fs.pairs_r(dir)` 递归。迭代产出 `(bee.fspath, bee.directory_entry)`;失败时**抛错**,目录不存在同样抛错。 +fs.copy(fs.path "temp", fs.path "temp1", + fs.copy_options.overwrite_existing | fs.copy_options.recursive) -```lua for path, entry in fs.pairs(fs.path "temp") do print(path:string(), entry:type(), entry:file_size(), entry:last_write_time()) end ``` -`directory_entry` 提供 `:path()`、`:refresh()`、`:status()`、`:symlink_status()`、`:type()`、`:exists()`、`:is_directory()`、`:is_regular_file()`、`:last_write_time()`、`:file_size()`。 - -递归累加(`test_fs:test_copy_dir` 模式): +递归累加(`test_fs:test_copy_dir` 的骨架): ```lua local function each_directory(dir, result) @@ -72,20 +36,9 @@ local function each_directory(dir, result) end ``` -## 选项位标志 - -- `fs.copy_options.{none, skip_existing, overwrite_existing, update_existing, recursive, copy_symlinks, skip_symlinks, directories_only, create_symlinks, create_hard_links}` -- `fs.perm_options.{replace, add, remove, nofollow}` -- `fs.directory_options.{none, follow_directory_symlink, skip_permission_denied}` - -```lua -fs.copy(fs.path "temp", fs.path "temp1", - fs.copy_options.overwrite_existing | fs.copy_options.recursive) -``` - ## 注意事项 -- 路径对象与字符串互转常见写法:`if type(filename) == "userdata" then filename = filename:string() end`。 -- 测试里所有文件操作都在 `fs.temp_directory_path() / "test_bee"` 下进行(见 `test/test.lua`),临时目录用完 `pcall(fs.remove_all, dir)` 清理。 -- 符号链接相关用例先 `if not supported "symlink" then return end`。 -- Windows 上符号链接/hardlink 需权限,`supported.lua` 的探测方式即 `pcall(fs.create_symlink, ...)`。 +- 路径对象与字符串互转的常见写法:`if type(filename) == "userdata" then filename = filename:string() end`。 +- `fs.remove` 对不存在的路径返回 `false`,递归删除要用 `fs.remove_all`。 +- 测试里所有文件操作都在 `fs.temp_directory_path() / "test_bee"` 下进行(`test/test.lua`),临时目录用完 `pcall(fs.remove_all, dir)` 清理。 +- 符号链接相关用例先 `if not supported "symlink" then return end`;Windows 上 symlink / hardlink 需要权限,`supported.lua` 的探测方式就是 `pcall(fs.create_symlink, ...)`。 diff --git a/skills/references/core/serialization.md b/skills/references/core/serialization.md index 2d2e8e7a..66450f04 100644 --- a/skills/references/core/serialization.md +++ b/skills/references/core/serialization.md @@ -1,28 +1,21 @@ # bee.serialization -`require "bee.serialization"`,对应 `meta/serialization.lua`、`test/test_serialization.lua`。 +跨线程/跨通道传值的序列化。签名见 `meta/serialization.lua`,行为契约见 `test/test_serialization.lua`。 -## API +## 要点 + +- 支持的类型:`nil`、`boolean`、`number`、`string`、`table`、**light C function**(如 `require`、`os.clock`)。 +- **引用共享会保留**:同一张表被多处引用,反序列化后仍指向同一份。 +- `pack` 返回 lightuserdata(需自行管理生命周期);跨线程传值用 `packstring` 更安全。 ```lua local seri = require "bee.serialization" -seri.pack(...) --> lightuserdata -- 需要 unpack 释放 -seri.packstring(...) --> string -seri.unpack(data) --> ... -- 接受 lightuserdata | string | userdata | function -seri.lightuserdata(ud) --> lightuserdata -``` - -```lua local data = seri.packstring(1, { A = { B = "C" } }, true) local a, t, b = seri.unpack(data) ``` -## 支持的类型 - -`nil`、`boolean`、`number`、`string`、`table`、**light C function**。 - -**引用共享会保留**(`test_seri:test_ref`):同一张表被多处引用,反序列化后仍共享同一份: +引用共享(`test_seri:test_ref`): ```lua local N = 10 @@ -47,7 +40,6 @@ end ```lua seri.pack(require) -- OK:require 是 light C function -seri.pack(os.clock) -- OK seri.pack(function () end) -- error: Only light C function can be serialized seri.pack(coroutine.create(f)) -- error: Unsupport type thread to serialize seri.pack(io.stdout) -- error: Unsupport type userdata to serialize @@ -56,5 +48,4 @@ seri.pack(io.stdout) -- error: Unsupport type userdata to serializ ## 注意事项 - 这是 `bee.thread` 参数传递和 `bee.channel` push/pop 的底层实现,限制完全一致。 -- `pack` 返回 lightuserdata,注意生命周期;只要跨线程传值用 `packstring` 更安全。 -- 不支持的类型在 `pack` 与 `packstring` 上行为一致(测试对两者都断言)。 +- `pack` 与 `packstring` 对不支持类型的报错行为一致(测试对两者都断言)。 diff --git a/skills/references/core/sys.md b/skills/references/core/sys.md index e33b18c3..999a7fd7 100644 --- a/skills/references/core/sys.md +++ b/skills/references/core/sys.md @@ -1,19 +1,13 @@ # bee.sys -`require "bee.sys"`,对应 `meta/sys.lua`、`test/test_sys.lua`。 +系统工具:自身路径与文件锁。签名见 `meta/sys.lua`,行为契约见 `test/test_sys.lua`。 -## API +## 要点 -```lua -local sys = require "bee.sys" +- `exe_path` / `dll_path` / `fullpath` 返回的是 **`bee.fspath`**,要字符串时 `:string()`;失败返回 `nil, err`。 +- `filelock(path)` 是**独占**语义:拿不到锁返回 `nil`(不是 `error`);返回的 `file*` 既是句柄也是锁,`close()` 即解锁。 -sys.exe_path() --> bee.fspath | nil, err -- 当前可执行文件路径 -sys.dll_path() --> bee.fspath | nil, err -- 当前动态库(bee.dll/so)路径 -sys.fullpath(path) --> bee.fspath | nil, err -- 解析符号链接后的完整路径 -sys.filelock(path) --> file* | nil, err -- 独占文件锁;句柄即锁,close 即解锁 -``` - -## 文件锁 +## 用法 跨进程互斥(`test_sys:test_filelock_1`): @@ -22,16 +16,16 @@ local sys = require "bee.sys" local fs = require "bee.filesystem" local f1 = assert(sys.filelock "temp.lock") -- 拿到锁 -assert(sys.filelock "temp.lock" == nil) -- 同进程/其他进程再取都是 nil(不是 error) +assert(sys.filelock "temp.lock" == nil) -- 同进程再取也是 nil f1:close() -- 关闭句柄 = 释放锁 local f2 = assert(sys.filelock "temp.lock") f2:close() fs.remove "temp.lock" ``` -跨进程验证(`test_sys:test_filelock_2` 用 `shell:runlua` 起子进程):子进程拿锁后,父进程 `sys.filelock` 返回 `nil`;子进程退出(句柄关闭)后父进程即可获取。 +跨进程验证见 `test_sys:test_filelock_2`:用 `shell:runlua` 起子进程拿锁,父进程随即返回 `nil`;子进程退出后父进程即可获取。 -## 用法:定位自身与路径规范化 +定位自身与路径规范化: ```lua local exe = sys.exe_path():string() @@ -39,11 +33,8 @@ local dll = sys.dll_path() local real = sys.fullpath("some/rel/path"):string() ``` -`test/shell.lua` 里定位当前 Lua 解释器即用 `fs.absolute(fs.path(arg[i+1]))`(配合 `arg` 负数索引),可作为参考。 - ## 注意事项 -- 三个路径函数返回的是 `bee.fspath`,需要字符串时 `:string()`。 -- 失败返回 `nil, err`,不要用 `assert` 之外的方式跳过错误。 -- 文件锁是**独占**语义,同进程重复加锁同样返回 `nil`(测试明确断言),别用它做可重入锁。 -- 锁文件本身会被创建,用完自行 `fs.remove`。 +- 文件锁是**独占**的,同进程重复加锁同样返回 `nil`,别拿它当可重入锁。 +- 锁文件用完自行 `fs.remove`。 +- `test/shell.lua` 里定位当前 Lua 解释器用的是 `fs.absolute(fs.path(arg[i+1]))`(配合 `arg` 负数索引),需要类似逻辑时可以参考。 diff --git a/skills/references/core/time.md b/skills/references/core/time.md index 21a1b180..1388e21f 100644 --- a/skills/references/core/time.md +++ b/skills/references/core/time.md @@ -1,16 +1,12 @@ # bee.time -`require "bee.time"`,对应 `meta/time.lua`、`test/test_time.lua`。三个函数都返回**毫秒整数**。 +毫秒级时间。签名见 `meta/time.lua`,行为契约见 `test/test_time.lua`。三个函数都返回**毫秒整数**。 -## API +## 要点 -```lua -local time = require "bee.time" - -time.time() -- 自 Unix 纪元(1970-01-01 UTC) 起的毫秒数(墙钟,会受系统时间调整影响) -time.monotonic() -- 单调递增毫秒数,测间隔用这个 -time.thread() -- 当前线程已消耗的 CPU 时间(毫秒) -``` +- `time.time()` 是墙钟,会被 NTP / 手动改钟影响;**测间隔一律用 `time.monotonic()`**。 +- `time.thread()` 是当前线程已消耗的 CPU 时间。 +- `time.time()` 与 `os.time() * 1000` 相差不超过 2 秒(`test_time:test_now`),但单位不同,别混用。 ## 用法 @@ -26,8 +22,6 @@ local t2 = time.monotonic() assert(t2 - t1 >= 1) ``` -与 `os.time()` 的关系(`test_time:test_now`):`os.time() * 1000` 与 `time.time()` 相差不超过 2 秒。 - 超时轮询(`test_async.lua` 的 `wait_completion`): ```lua @@ -42,6 +36,5 @@ error "wait_completion timeout" ## 注意事项 -- 计时一律用 `monotonic()`,`time()` 可能被系统时间调整(NTP、手动改钟)拉回或跳过。 -- 单位是毫秒,不是秒;不要与 `os.time()`(秒)混用。 - 精度/粒度依平台,`test_time:test_monotonic` 只断言 `> 0`。 +- 需要「等一段时间」用 `thread.sleep`(毫秒),不要忙等 `monotonic`。 diff --git a/skills/references/io/async.md b/skills/references/io/async.md index 2d8c485b..7715cca3 100644 --- a/skills/references/io/async.md +++ b/skills/references/io/async.md @@ -1,115 +1,63 @@ # bee.async -`require "bee.async"`,对应 `meta/async.lua`、`test/test_async.lua`。macOS 用 GCD,Windows 用 IOCP,Linux 用 io_uring/epoll。 +跨平台异步 I/O:macOS 用 GCD,Windows 用 IOCP,Linux 用 io_uring/epoll。签名见 `meta/async.lua`,行为契约见 `test/test_async.lua`。 -模型:**一次投递 → 一次 completion**,投递时传入的 `udata`(token)原样回传。 +模型:**一次投递 → 一次 completion**,投递时传入的 token 原样回传。 -## API +## 要点 -```lua -local async = require "bee.async" - -local as = assert(async.create(64)) -- create(max_completions),默认 64;<=0 报 "max_completions is less than or equal to zero." - -as:associate(fd) -- Windows/IOCP 必需,其他平台 no-op -as:associate_file(file) -- 文件 I/O 前必须调用(io.open 得到的 file*) -as:cancel(fd) -- 取消该 fd 上所有未完成操作 -as:poll() / as:wait([timeout_ms]) -- 非阻塞 / 阻塞,返回完成事件迭代器 -as:stop() -``` - -投递接口: +- `associate(fd)` / `associate_file(file)` 必须在**首次提交 I/O 之前**完成;重复 `associate` 同一 socket 允许。 +- `wait(timeout_ms)` 阻塞、`poll()` 非阻塞,都返回**迭代器**,产出 `(op, token, status, data, errcode)`: + - `op`:`OP_READ` / `OP_WRITE` / `OP_ACCEPT` / `OP_CONNECT` / `OP_FILE_READ` / `OP_FILE_WRITE` / `OP_POLL`。 + - `status`:`SUCCESS` / `CLOSE` / `ERROR` / `CANCEL`;**对端关闭是 `CLOSE`,不是 `ERROR`**。 + - `data`:`OP_ACCEPT` 是新 socket userdata;`OP_FILE_READ` 是读到的字符串;其余是字节数。 +- 读写缓冲区独立于 socket:`OP_READ` 的数据在 `readbuf` 里,要自己 `rb:read()` 取。 +- **写入的 completion `bytes` 恒为 `0`**(C 层已 drain 完,含 partial write 重试)。 +- `submit_read` 有背压:ring buffer 空闲不足返回 `false`(不是错误),重试前先 `rb:read()` 腾空间。 +- `writebuf:write(data)` 返回 `true` 表示缓冲已达 hwm,调用方要自己背压。 +- 关闭 fd 前建议 `as:cancel(fd)`,回收未完成操作(Windows 上尤其重要)。 -```lua -as:submit_read(rb, fd, udata) --> true | false(背压) | nil, err -as:submit_write(wb, fd, udata) --> true | nil, err -as:submit_accept(listen_fd, udata) -as:submit_connect(fd, host, port, udata) -- 也接受 bee.endpoint 重载 -as:submit_file_read(file, len [, offset = 0], udata) -as:submit_file_write(file, data [, offset = 0], udata) -as:submit_poll(fd, udata) -- 只监听可读,不消费数据 -``` - -缓冲区: - -```lua -local wb = assert(async.writebuf(64 * 1024)) -- writebuf(hwm),默认 65536 -wb:write(data) --> true 表示缓冲 >= hwm,调用方应自行背压 -wb:buffered() --> 当前排队字节数 -wb:close() -- 流关闭时丢弃未发数据 - -local rb = assert(async.readbuf(bufsize)) -- readbuf(bufsize),向上取整到 2 的幂;<=0 报 "bufsize must be positive" -rb:read([n]) --> string | nil(数据不足);n 省略取全部可用 -rb:readline([sep = "\r\n"]) --> string | nil(未找到分隔符) -``` - -## completion 迭代器 - -```lua -for op, udata, status, data, errcode in as:wait(timeout_ms) do - -- op : OP_READ / OP_WRITE / OP_ACCEPT / OP_CONNECT / OP_FILE_READ / OP_FILE_WRITE / OP_POLL - -- status : SUCCESS / CLOSE / ERROR / CANCEL - -- data : accept -> 新 socket userdata;file_read -> 读到的字符串;其余 -> 传输字节数 -end -``` - -写入的完成事件 `bytes` 恒为 `0`(数据已由 C 层 drain 完,包括 partial write 重试)。 - -## 完整示例(取自 `test_async.lua`) +## 用法(取自 `test_async.lua`) ```lua local async = require "bee.async" local socket = require "bee.socket" -local as = assert(async.create(64)) --- 服务端/客户端都要先 associate +local as = assert(async.create(64)) -- create(max_completions) local sfd = assert(socket.create "tcp") assert(as:associate(sfd)) assert(sfd:bind("127.0.0.1", 0)); assert(sfd:listen()) - local _, port = sfd:info "socket":value() local cfd = assert(socket.create "tcp") assert(as:associate(cfd)) -local ok, err = cfd:connect("127.0.0.1", port) -- 可能返回 false(等待中),用 submit_connect 更常见 +local ok, err = cfd:connect("127.0.0.1", port) assert(ok ~= nil, err) --- 接受连接(测试里用 select 等可读,再 sfd:accept 并 associate) -assert(as:submit_accept(sfd, "accept_token")) -- completion 的 data 即新 socket userdata - --- 写 -local wb = assert(async.writebuf(64 * 1024)) -wb:write "hello" -assert(as:submit_write(wb, cfd, "write_token")) +assert(as:submit_accept(sfd, "accept_token")) -- completion 的 data 即新 socket +assert(as:submit_read(rb, newfd, { id = 42 })) -- token 可以是任意 local op, token, status, bytes -for _op, _tok, _st, _data in as:wait(1000) do -- wait/poll 返回的是迭代器 +for _op, _tok, _st, _data in as:wait(1000) do op, token, status, bytes = _op, _tok, _st, _data break end --- op == async.OP_WRITE, token == "write_token", status == async.SUCCESS, bytes == 0 - --- 读(数据在 ring buffer 里自取) -local rb = assert(async.readbuf(64)) -assert(as:submit_read(rb, newfd, { id = 42 })) --- 收到 OP_READ + SUCCESS 后: +-- op == async.OP_READ;收到 SUCCESS 后从 ring buffer 取数据: local data = rb:read(5) -- 精确字节数;不足返回 nil -local line = rb:readline() -- 或按行取 +local line = rb:readline() -- 或按行取,默认分隔符 "\r\n" ``` -文件 I/O: +文件 I/O 要额外的 `associate_file`: ```lua local rf = assert(io.open(path, "rb")) assert(as:associate_file(rf)) assert(as:submit_file_read(rf, 128, 0, "fread")) --- completion: op == OP_FILE_READ, data == 读到的字符串(不是字节数) +-- completion: op == OP_FILE_READ,data 是读到的字符串(不是字节数) ``` ## 注意事项 -- `associate` / `associate_file` 必须在**首次提交 I/O 之前**完成;重复 `associate` 同一 socket 是允许的。 -- `submit_read` 有背压:ring buffer 空闲不足返回 `false`,重试前需先 `rb:read()` 腾出空间。 -- 对端关闭时读操作产生 `status == CLOSE`,不是 `ERROR`。 -- 关闭 fd 前建议 `as:cancel(fd)`,确保未完成操作及时回收(Windows 上尤其重要)。 -- `submit_poll` 只通知可读,典型用途是监听 `channel:fd()` 后自行 `channel:pop()`。 +- `submit_poll(fd, udata)` 只通知可读、不消费数据,典型用途是监听 `channel:fd()` 后自行 `channel:pop()`。 +- `create` 的 `max_completions <= 0` 与 `readbuf` 的 `bufsize <= 0` 都是 `error`,文案见测试断言。 +- 事件循环里别在 completion 回调内阻塞等待同一 fd 的下一个事件,会死锁;测试里的 `wait_completion` 是超时轮询写法,可参考。 diff --git a/skills/references/io/epoll.md b/skills/references/io/epoll.md index f8d12a7e..eae53cff 100644 --- a/skills/references/io/epoll.md +++ b/skills/references/io/epoll.md @@ -1,50 +1,20 @@ # bee.epoll -`require "bee.epoll"`,对应 `meta/epoll.lua`、`test/test_epoll.lua`、`test/test_channel.lua`。 +epoll 风格 I/O 多路复用(Windows 由 IOCP 实现)。签名见 `meta/epoll.lua`,行为契约见 `test/test_epoll.lua`、`test/test_channel.lua`。 -跨平台 epoll 风格 API,Windows 上由 IOCP 实现,因此返回错误的形式是 `nil, err`。 +## 要点 -## API - -```lua -local epoll = require "bee.epoll" - -local epfd = assert(epoll.create(16)) -- max_events 必须 > 0,否则 error -epfd:event_add(fd, events [, userdata]) --> true | nil, err -epfd:event_mod(fd, events [, userdata]) --> true | nil, err -epfd:event_del(fd) --> true | nil, err -epfd:wait([timeout]) --> iterator | nil, err(实例已 close 时返回 nil, "bad file descriptor") -epfd:close() --> true | nil, err(重复 close 返回 nil) -``` - -- `fd` 可为 `bee.socket.fd` 或 `lightuserdata`(如 `channel:fd()`)。 -- `userdata` 是迭代回传的关联对象,默认 fd 自身。 -- `timeout` 毫秒,`-1`/省略为无限等待。 -- 重复 `event_add` 同一个 fd、或对未添加的 fd `event_mod`/`event_del` 返回 `nil`(不抛错)。 - -## 事件常量 - -按位定义,`test_epoll:test_enum` 锁定了取值: - -```lua -epoll.EPOLLIN -- 1 << 0 可读 -epoll.EPOLLPRI -- 1 << 1 -epoll.EPOLLOUT -- 1 << 2 可写 -epoll.EPOLLERR -- 1 << 3 -epoll.EPOLLHUP -- 1 << 4 -epoll.EPOLLRDNORM -- 1 << 6 -epoll.EPOLLRDBAND -- 1 << 7 -epoll.EPOLLWRNORM -- 1 << 8 -epoll.EPOLLWRBAND -- 1 << 9 -epoll.EPOLLMSG -- 1 << 10 -epoll.EPOLLRDHUP -- 1 << 13 对端关闭 -epoll.EPOLLONESHOT -- 1 << 30 一次性 -``` +- 常量是位标志,取值被 `test_epoll:test_enum` 锁定;`EPOLLRDHUP`(对端关闭)与 `EPOLLONESHOT`(一次性)是 select 没有的。 +- `fd` 可为 `bee.socket.fd` 或 `lightuserdata`(如 `channel:fd()`);`event_add` 的第三个参数是迭代回传的关联对象,默认 fd 自身。 +- `wait([timeout])` 返回**迭代器**,空迭代表示超时;`timeout` 毫秒、`-1`/省略为无限等待,传 `0` 即非阻塞轮询。 +- 只做就绪通知,**不消费数据**;`event_add` 到已存在的 fd、或对未注册的 fd `event_mod`/`event_del` 返回 `nil`(不抛错)。 +- `epoll.create(max_events)` 对 `<= 0` 的参数直接 `error`:`maxevents is less than or equal to zero.`。 ## 用法 ```lua local epoll = require "bee.epoll" + local epfd = assert(epoll.create(16)) epfd:event_add(res_chan:fd(), epoll.EPOLLIN, "res") @@ -53,7 +23,7 @@ for obj, event in epfd:wait() do error "unknown error" end if event & epoll.EPOLLIN ~= 0 then - -- 就绪通知,数据仍需自行消费 + -- 就绪通知:数据要自己取空 while true do local ok, v = res_chan:pop() if not ok then break end @@ -67,7 +37,6 @@ end ## 注意事项 -- `epoll.create(max_events)` 对 `<= 0` 的参数直接 `error`:`maxevents is less than or equal to zero.`(测试用 `lt.assertFailed` 断言)。 -- `wait` 返回空迭代表示超时;用作非阻塞轮询时传 `0`。 -- `epoll` 只做就绪通知(水平触发语义由底层决定),不消费数据;`channel:pop()` 到空为止是标准收尾方式。 -- 需要更简单的 `SELECT_READ/SELECT_WRITE` 语义用 `bee.select`;需要一次投递一次完成事件用 `bee.async`。 +- 取到 `EPOLLIN` 后**必须把数据取空**(`channel:pop()` 到 `ok == false`),否则下一次还会立刻就绪。 +- 关闭实例前先 `event_del`,避免残留注册;实例 `close` 后 `wait` 会返回 `nil, "bad file descriptor"`。 +- 需要更简单的读/写语义用 `bee.select`;需要「一次投递一次完成事件」用 `bee.async`。 diff --git a/skills/references/io/filewatch.md b/skills/references/io/filewatch.md index bfed6c5e..4c4facf6 100644 --- a/skills/references/io/filewatch.md +++ b/skills/references/io/filewatch.md @@ -1,21 +1,13 @@ # bee.filewatch -`require "bee.filewatch"`,对应 `meta/filewatch.lua`、`test/test_filewatch.lua`。底层:inotify / FSEvents / ReadDirectoryChangesW。 +文件系统监控,底层 inotify / FSEvents / ReadDirectoryChangesW。签名见 `meta/filewatch.lua`,行为契约见 `test/test_filewatch.lua`。 -## API +## 要点 -```lua -local filewatch = require "bee.filewatch" - -local fw = filewatch.create() -fw:add(path) -- 自动转绝对路径;可多次调用添加多个根 -fw:set_recursive(enable) --> boolean -fw:set_follow_symlinks(enable) --> boolean -- 某些平台可能不支持 -fw:set_filter(fn|nil) --> boolean -- fn 接收路径字符串,返回 true 表示接受该事件 -fw:select() --> type, path -- type: "modify" | "rename";无事件时 type == nil -``` - -`select()` 是非阻塞的:没有事件时立即返回 `nil`,需要自己轮询 + 睡眠。 +- `select()` 是**非阻塞轮询**:没有事件时立即返回 `nil`,需要自己 `thread.sleep` 再试。 +- 事件类型只有 `"modify"` 和 `"rename"` 两种(创建、删除、重命名都落在 `rename` 上)。 +- `add(path)` 只接受字符串(`fs.path` 要先 `:string()`),内部会转绝对路径;可多次调用添加多个根。 +- `set_filter(fn)` 的 `fn` 收到路径字符串、返回 `true` 表示接受该事件;传 `nil` 清除过滤器。 ## 用法 @@ -29,19 +21,19 @@ local fw = filewatch.create() fw:set_recursive(true) fw:set_follow_symlinks(true) fw:set_filter(function (path) return true end) -fw:add(root:string()) -- add 接收 string +fw:add(root:string()) while true do local kind, path = fw:select() if kind then print(kind, path) -- "modify"/"rename" + 变更路径 else - thread.sleep(20) -- 空转等待,测试里用重试计数退出 + thread.sleep(20) -- 空转等待 end end ``` -测试里的收事件循环(`test_filewatch:test_2`)值得参考——`select` 返回 `nil` 时重试若干次即认为事件收完: +「收到一批事件就停」的写法(`test_filewatch:test_2`)——`select` 连续返回若干次 `nil` 即认为这一轮事件收完: ```lua local retry = 5 @@ -62,9 +54,7 @@ end ## 注意事项 -- `add` 只接受字符串路径,`fs.path` 需要先 `:string()`;路径会自动转绝对路径。 -- 事件类型只有 `"modify"` 与 `"rename"` 两种(创建/删除/重命名都落在 `rename` 上)。 -- 事件可能重复或漏报(平台差异),测试里用 `has(list, v)` 去重并允许重试。 +- 事件可能重复或漏报(平台差异),消费端要去重并允许重试;测试里用 `has(list, v)` 去重。 - 目录符号链接、指向自身的符号链接是已知边界情况,`test_symlink` 只验证不崩溃。 -- FreeBSD/OpenBSD/NetBSD 上整个 filewatch 测试组被 `lt.skip "filewatch"` 跳过。 -- 需要“等事件”而不是“轮询”时,把 `select` 放进 `thread.sleep` 循环或与 `bee.epoll`/`bee.async` 的事件循环结合。 +- FreeBSD / OpenBSD / NetBSD 上整个 filewatch 测试组被 `lt.skip "filewatch"` 跳过。 +- 要「等事件」而不是轮询时,把它接到 `bee.epoll` / `bee.async` 的事件循环上,避免忙等。 diff --git a/skills/references/io/select.md b/skills/references/io/select.md index 88bae43b..666c3712 100644 --- a/skills/references/io/select.md +++ b/skills/references/io/select.md @@ -1,37 +1,30 @@ # bee.select -`require "bee.select"`,对应 `meta/select.lua`、`test/test_socket.lua`。 +select 风格 I/O 多路复用。签名见 `meta/select.lua`,行为契约见 `test/test_socket.lua`。 -## API +## 要点 -```lua -local select = require "bee.select" - -local ctx = select.create() -- 不会失败,返回值不是 nil,err 形式 -ctx:event_add(fd, events [, userdata]) --> boolean -ctx:event_mod(fd, events) --> boolean -ctx:event_del(fd) --> boolean -ctx:wait([timeout]) --> iterator -ctx:close() -``` - -- `events` 是位组合:`select.SELECT_READ` (读) | `select.SELECT_WRITE` (写)。 +- 事件是**位标志**:`select.SELECT_READ` | `select.SELECT_WRITE`,只有这两个。 - `fd` 可以是 `bee.socket.fd`,也可以是裸 `lightuserdata`(如 `channel:fd()`)。 -- `userdata` 为迭代时回传的关联对象,默认是 fd 自身。 -- `timeout` 单位毫秒,`-1`(或省略)无限等待。 - -## wait 的正确用法 +- `event_add` 的第三个参数是迭代时回传的关联对象,默认是 fd 自身。 +- `wait([timeout])` 返回**迭代器**,迭代产出 `(userdata, event)`;空迭代表示超时,`timeout` 单位毫秒、`-1`/省略为无限等待。 +- 同一轮可能读、写同时就绪,所以判断标志要**按位与**,多次迭代要**按位或**累加。 +- `select` 只做就绪通知,不消费数据,也不报错误事件;收发仍需自己 `fd:recv` / `fd:send`。 -`wait` 返回**迭代器**,迭代产出 `(userdata, event)`;返回空迭代表示超时。 +## 用法 ```lua +local select = require "bee.select" + +local ctx = select.create() +ctx:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) for obj, event in ctx:wait() do if event & select.SELECT_READ ~= 0 then ... end if event & select.SELECT_WRITE ~= 0 then ... end end ``` -`event` 是**位标志**:同一轮里可能既有读也有写就绪,需要按位或把多次迭代的 `event` 累加起来(来自 `test_socket.lua` 的 `simple_select`): +只关心「有没有就绪」时可以把事件累加(来自 `test_socket.lua` 的 `simple_select`): ```lua local function simple_select(fd, mode) @@ -58,6 +51,5 @@ end ## 注意事项 - 一次性等待建议用 `local s = select.create()`(to-be-closed),避免忘记 `close`。 -- 事件常量只有 `SELECT_READ` / `SELECT_WRITE`;需要 epoll 语义(`EPOLLRDHUP`、oneshot 等)请改用 `bee.epoll`。 -- `ctx:close()` 后再调用 `event_add` 等会失败;`bee.epoll` 对应接口返回 `nil, err`,`bee.select` 返回 `boolean`。 -- 与 `bee.async` 不同,select 只做就绪通知,收发仍需自己调用 `fd:recv`/`fd:send`。 +- 与 `bee.epoll` 的差异:epoll 的 `event_*` 返回 `nil, err` 且支持 `EPOLLRDHUP` / oneshot 等语义,`bee.select` 的返回 `boolean`、只有读/写两种标志。 +- 需要「一次投递一次完成事件」用 `bee.async`,不要在 select 上自己拼状态机。 diff --git a/skills/references/io/socket.md b/skills/references/io/socket.md index 9fc42b98..99140d93 100644 --- a/skills/references/io/socket.md +++ b/skills/references/io/socket.md @@ -1,6 +1,6 @@ # bee.socket -`require "bee.socket"`,对应 `meta/socket.lua`、`test/test_socket.lua`。 +TCP / UDP / Unix 套接字。签名见 `meta/socket.lua`,行为契约见 `test/test_socket.lua`。 ## 非阻塞三态返回(本模块最重要的约定) @@ -10,42 +10,30 @@ | `false` | 需等待,配合 `bee.select` / `bee.epoll` 重试 | | `nil, errmsg` | 失败或对端关闭 | -`fd:accept()` / `fd:recv()` 的 `nil` 表示对端关闭;`fd:send()` 返回**已发送字节数**,partial write 需自行切片重试。 +- `accept()` / `recv()` 的 `nil` 表示**对端关闭**,不是错误。 +- `send()` 返回**已发送字节数**,partial write 要自己切片重试。 +- `connect()` 是非阻塞的:之后等可写再 `status()` 判断是否真的连上。 -## 创建与连接 +## 用法 ```lua local socket = require "bee.socket" local select = require "bee.select" --- 协议:"tcp" | "udp" | "unix" | "tcp6" | "udp6" -local server = assert(socket.create "tcp") +local server = assert(socket.create "tcp") -- "tcp"|"udp"|"unix"|"tcp6"|"udp6" assert(server:bind("127.0.0.1", 0)) -- 端口 0 = 系统分配 assert(server:listen()) -- backlog 默认 5 local address, port = server:info "socket":value() -- "socket" 本端 / "peer" 对端 local client = assert(socket.create "tcp") -client:connect("127.0.0.1", port) -- 非阻塞,之后等可写再 status() --- 等可写后: -assert(client:status()) -- true 表示连接建立 +client:connect("127.0.0.1", port) +-- 等可写后:assert(client:status()) local session = assert(server:accept()) -- false = 尚无连接 session:close(); client:close(); server:close() ``` -Unix socket:`socket.create "unix"` + `fd:bind(path)`,关闭后是否自动 unlink 依平台(测试中用 `detectAutoUnlink` 探测)。 - -## 读写 - -```lua -fd:recv([len]) --> string | false(等待) | nil(关闭), err -fd:send(data) --> n | false(等待) | nil, err -fd:sendv(s1, s2, ...) --> 一次系统调用向量化发送,返回总字节数 -fd:recvfrom([len]) --> data, bee.endpoint | false | nil, err -fd:sendto(data, ep_or_addr [, port]) --> n | false | nil, err -``` - -UDP 示例(`test_socket:test_udp`): +UDP(`test_socket:test_udp`): ```lua local a, b = assert(socket.create "udp"), assert(socket.create "udp") @@ -56,34 +44,16 @@ local data, from_ep = b:recvfrom() -- 需先等 b 可读 assert(data == "123" and from_ep == a_ep) ``` -## 端点与其它工具 - -```lua -socket.endpoint("inet", ip, port) -- 也有 "inet6" | "hostname" | "unix" -ep:value() -- inet/inet6 返回 ip, port;unix 返回 path, type -socket.pair() --> fd1, fd2(一对已连接的 socket,测试里用于 echo) -socket.gethostname() --> string -socket.fd(handle [, no_ownership]) -- 从裸句柄包装 -``` - -`fd:detach()` 交出裸句柄并放弃所有权,`socket.fd(h)` 可重新包装(`test_socket:test_dump`): - -```lua -local h = server:detach() -server = socket.fd(h) -``` - -## 其它 fd 方法 +句柄移交(`test_socket:test_dump`): ```lua -fd:option("reuseaddr"|"sndbuf"|"rcvbuf", value) -fd:shutdown("r"|"w") -- 省略则双向 -fd:handle() --> lightuserdata +local h = server:detach() -- 交出裸句柄并放弃所有权 +server = socket.fd(h) -- 再包装回来 ``` ## 常见用法模板 -同步等待 + 收发(来自 `test_socket.lua` 的 `simple_select`): +同步等待 + 收发(`test_socket.lua` 的 `simple_select`): ```lua local function simple_select(fd, mode) @@ -132,6 +102,6 @@ end ## 注意事项 - 参数校验错误会 `error()`,文案如 `bad argument #1 to 'bee.socket.create' (invalid option 'icmp')`。 -- `socket.create` 失败返回 `nil, err`(用 `assert` 包装)。 +- Unix socket 关闭后是否自动 unlink 依平台,测试里用 `detectAutoUnlink` 探测。 - 对端关闭后继续 `send` 不应崩溃(`test_SIGPIPE` 专门覆盖)。 -- 跨线程使用 socket 需要传句柄或让线程自己 `create`,不能共享 userdata。 +- 跨线程使用 socket 要把句柄传过去或让线程自己 `create`,不能共享 userdata。 diff --git a/skills/references/platform/crash.md b/skills/references/platform/crash.md index 20e7bab1..0f8756b9 100644 --- a/skills/references/platform/crash.md +++ b/skills/references/platform/crash.md @@ -1,20 +1,14 @@ # bee.crash -`require "bee.crash"`,对应 `meta/crash.lua`、`binding/lua_crash.cpp`、`test/test.lua`。 +崩溃处理器:进程崩溃时落 dump。签名见 `meta/crash.lua`,行为契约见 `test/test.lua`(入口就在用),实现在 `bee/crash/`。 -## API +## 要点 -```lua -local crash = require "bee.crash" - -local handler = crash.create_handler(dump_path) --> handler userdata -``` - -- `dump_path` 是**目录**:崩溃日志写成 `/crash_.log`。 -- `dump_path` 传 `"-"` 时**关闭落盘**(崩溃时只把日志打印到控制台),测试入口就是这么用的: +- `create_handler(dump_path)` 的 `dump_path` 是**目录**,崩溃日志写成 `/crash_.log`。 +- `dump_path` 传 `"-"` 时**关闭落盘**,只在崩溃时把日志打到控制台: ```lua --- test/test.lua +-- test/test.lua 的用法 local crash = require "bee.crash" local _ = crash.create_handler "-" ``` @@ -23,7 +17,7 @@ local _ = crash.create_handler "-" ## 注意事项 -- 只在 Windows + MSVC(且非 address sanitizer)构建下真正生效,其他平台是 `empty_handler`,构造调用是 **no-op**(见 `bee/crash/handler.h`)。因此跨平台代码可以无条件调用。 -- 路径是 `luaL_checkstring`,必须传字符串;非 Windows 平台不会校验路径是否存在。 -- 用途是捕获 native 层崩溃(段错误、未处理异常),Lua 的 `pcall` 错误栈不在其覆盖范围内。 -- 需要在崩溃后分析时,把 `dump_path` 指向可写目录并在测试/CI 里收集该目录;不希望生成文件时用 `"-"`。 +- 只在 **Windows + MSVC**(且非 address sanitizer)构建下真正生效,其他平台是 `empty_handler`,构造调用是 **no-op**(`bee/crash/handler.h`)。因此跨平台代码可以无条件调用,但别指望在 Linux/macOS 上拿到 dump。 +- 参数是 `luaL_checkstring`,必须传字符串;非 Windows 平台不会校验路径是否存在。 +- 捕获的是 native 层崩溃(段错误、未处理异常),Lua 层的 `pcall` 错误栈不在覆盖范围。 +- 需要在崩溃后分析时,把 `dump_path` 指向可写目录并在 CI 里收集;不想生成文件就用 `"-"`。 diff --git a/skills/references/platform/debugging.md b/skills/references/platform/debugging.md index 72a613bb..59b4b4d6 100644 --- a/skills/references/platform/debugging.md +++ b/skills/references/platform/debugging.md @@ -1,18 +1,14 @@ # bee.debugging -`require "bee.debugging"`,对应 `meta/debugging.lua`、`binding/lua_debugging.cpp`。底层是 `std::breakpoint()` / `std::is_debugger_present()`。 +断点与调试器探测。签名见 `meta/debugging.lua`,实现在 `binding/lua_debugging.cpp` + `bee/nonstd/debugging.h`。本模块目前没有独立测试文件。 -## API +## 要点 -```lua -local debugging = require "bee.debugging" - -debugging.breakpoint() -- 无条件触发断点指令 -debugging.is_debugger_present() --> boolean -debugging.breakpoint_if_debugging() -- 仅当有调试器附加时才中断,否则 no-op(安全版本) -``` +- 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 +- `breakpoint()` **不判断**是否有调试器;`breakpoint_if_debugging()` 才是「有调试器才断」的安全版本。 +- `is_debugger_present()`:Windows 用 `IsDebuggerPresent()`,macOS 用 `sysctl` 的 `P_TRACED`,其他平台恒返回 `false`(于是 `breakpoint_if_debugging()` 也恒为 no-op)。 -`breakpoint()` 的底层实现依编译器而定(`bee/nonstd/debugging.h`): +`breakpoint()` 的底层实现依编译器而定: | 构建环境 | 实现 | |----------|------| @@ -21,12 +17,10 @@ debugging.breakpoint_if_debugging() -- 仅当有调试器附加时才中断 | clang(无 ``) | `__builtin_debugtrap()` | | 其它(如 GCC,无 ``) | 函数体为空,**no-op** | -`is_debugger_present()`:Windows 用 `IsDebuggerPresent()`,macOS 用 `sysctl` 的 `P_TRACED`,其他平台恒返回 `false`(此时 `breakpoint_if_debugging()` 也就恒为 no-op)。 +在实现了 trap 的构建环境里,没有调试器附加时断点异常会交给系统默认处理器(可能直接终止进程);没有 trap 实现的分支里则什么都不发生。所以生产代码里优先用 `breakpoint_if_debugging()`。 ## 用法 -想在调试器里断下来,但不想让正常运行时崩溃,用 `breakpoint_if_debugging`: - ```lua local debugging = require "bee.debugging" @@ -39,7 +33,5 @@ debugging.breakpoint_if_debugging() -- 挂调试器则中断,否则什么 ## 注意事项 -- 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 -- `breakpoint()` **不判断**是否有调试器:在实现了 trap 的构建环境里(C++26 `std::breakpoint()` / MSVC / clang),没有调试器附加时断点异常会交给系统默认处理器(可能直接终止进程);而 GCC 等无 trap 实现的分支里它只是 no-op。因此生产代码里应优先用 `breakpoint_if_debugging()`。 -- `is_debugger_present()` 也可用于按环境切换日志级别。 -- 本模块目前没有独立测试文件。 +- `is_debugger_present()` 也可以用来按环境切换日志级别——比自定义开关更可靠。 +- 想让 Lua 层停下来看调用栈,请用 `debug.sethook` / 调试器;本模块只处理 native 断点。 diff --git a/skills/references/platform/platform.md b/skills/references/platform/platform.md index c50a395b..b241b6f6 100644 --- a/skills/references/platform/platform.md +++ b/skills/references/platform/platform.md @@ -1,19 +1,11 @@ # bee.platform -平台信息模块。返回的是**普通表**(非类),无需 ``。 +平台信息。签名见 `meta/platform.lua`。模块返回的是**普通表**(非类),无需 ``。 -## API +## 要点 -| 字段 | 类型 | 说明 | -|------|------|------| -| `os` | `"windows"|"android"|"linux"|"netbsd"|"freebsd"|"openbsd"|"ios"|"macos"|"unknown"` | 操作系统 | -| `Arch` | `"x86"|"x86_64"|"arm"|"arm64"|"riscv"|"wasm32"|"wasm64"|"mips64el"|"loongarch64"|"ppc"|"ppc64"|"unknown"` | 目标架构 | -| `Compiler` | `"clang"|"msvc"|"gcc"|"unknown"` | 编译器 | -| `CompilerVersion` | `string` | 编译器版本 | -| `CRT` | `"msvc"|"libstdc++"|"libc++"|"bionic"|"unknown"` | C 运行时库 | -| `CRTVersion` | `string` | CRT 版本 | -| `DEBUG` | `boolean` | 是否 Debug 构建 | -| `os_version` | `{ major: integer, minor: integer, revision: integer }` | 系统版本号 | +- 常用来分支的字段:`os`、`Arch`、`Compiler`、`CRT`、`DEBUG`。 +- 版本号在 `os_version` 里(`{ major, minor, revision }`),另有 `CompilerVersion` / `CRTVersion` 字符串。 ## 用法 @@ -26,7 +18,7 @@ local isMinGW = isWindows and platform.CRT == "libstdc++" if platform.DEBUG then ... end ``` -`test/test.lua` 在启动时打印环境信息,是标准用法: +`test/test.lua` 启动时打印环境信息,是标准用法: ```lua local v = platform.os_version @@ -39,5 +31,5 @@ print("DEBUG: ", platform.DEBUG) ## 注意事项 -- 测试中按平台跳过用例请用 `lt.skip "module.test_name"`(`test/test_skip.lua`),按特性探测用 `supported "symlink"`(`test/supported.lua`)。 +- 测试里按平台跳过用例用 `lt.skip "module.test_name"`(`test/test_skip.lua`),按**特性**探测用 `supported "symlink"`(`test/supported.lua`,结果会缓存)——能力探测优先于平台判断。 - `supported "hardlink"` 的判定就是 `platform.os ~= "android"`。 diff --git a/skills/references/platform/windows.md b/skills/references/platform/windows.md index 3b98dfde..47a9ae80 100644 --- a/skills/references/platform/windows.md +++ b/skills/references/platform/windows.md @@ -1,45 +1,31 @@ # bee.windows -`require "bee.windows"`,对应 `meta/windows.lua`、`test/test_windows.lua`。**仅 Windows 可用**,其他平台 `require` 会失败,需自行按 `platform.os` 分支或 pcall。 +Windows 专有工具。签名见 `meta/windows.lua`,行为契约见 `test/test_windows.lua`。 -## API +**仅 Windows 可用**:其他平台 `require` 会失败,按 `platform.os` 分支或 `pcall` 包裹。 -| 函数 | 说明 | -|------|------| -| `windows.u2a(str)` | UTF-8 → ANSI(GBK) | -| `windows.a2u(str)` | ANSI(GBK) → UTF-8 | -| `windows.filemode(file, mode)` | 设置文本/二进制模式,`"t"` 文本、`"b"` 二进制,返回 `boolean` | -| `windows.isatty(file)` | 句柄是否为终端,返回 `boolean` | -| `windows.write_console(file, msg)` | 用 `WriteConsoleW` 写控制台,正确输出 UTF-16,返回写入字符数 | -| `windows.is_ssd(drive)` | 驱动器是否 SSD,`drive` 形如 `"C:"` 或 `"C"` | -| `windows.find_file_holders(filepath)` | 通过 NT API 枚举句柄表,返回占用该文件的 PID 数组 | -| `windows.process_name(pid)` | PID → 进程名(如 `"notepad.exe"`),失败返回空字符串 | +## 要点 -## 用法 +- 编码:`u2a` / `a2u` 在 UTF-8 与 ANSI(GBK) 之间转换,只认 ANSI 的老 API 用得上。 +- 控制台:`isatty(file)` 判断句柄是不是终端;`write_console(file, msg)` 走 `WriteConsoleW`,能正确输出 UTF-16,避开 CRT 编码问题。 +- 文本模式:`filemode(file, "t"|"b")` 切 CRT 的换行转换,二进制模式下 `io.read "a"` 才会拿到原始 `\r\n`。 +- 排查占用:`find_file_holders(filepath)` 通过 NT API 枚举句柄表返回 PID 数组,`process_name(pid)` 把 PID 换成进程名(失败返回空串)。 +- `is_ssd(drive)` 判断驱动器是否 SSD,`"C"` 与 `"C:"` 都接受。 -编码转换与终端输出: +## 用法 ```lua local windows = require "bee.windows" -local ansi = windows.u2a "中文" -- GBK 字节串,可交给只认 ANSI 的 API +local ansi = windows.u2a "中文" -- GBK 字节串 local utf8 = windows.a2u(ansi) if windows.isatty(io.stdout) then - windows.write_console(io.stdout, "中文\n") -- 避免 CRT 编码问题 + windows.write_console(io.stdout, "中文\n") end -``` - -管道/文件读写时的模式控制(`test_subprocess` 里用它关掉 CRLF 转换): -```lua -windows.filemode(io.stdin, "b") -assert(io.read "a" == "\r\n") -- 二进制模式下读到原始换行 -``` +windows.filemode(io.stdin, "b") -- 关掉 CRLF 转换(test_subprocess 里这么用) -排查文件占用: - -```lua local pids = windows.find_file_holders "d:/build/out.exe" for _, pid in ipairs(pids) do print(pid, windows.process_name(pid)) @@ -48,7 +34,6 @@ end ## 注意事项 -- `windows.is_ssd` 的参数是驱动器名,`"C"` 与 `"C:"` 都接受。 -- `windows.find_file_holders` 需要相应权限,且只列出**当前进程可见**的句柄持有者。 -- 遇到含代理对/生僻字的路径(WTF-8 场景),Lua 层字符串是 UTF-8 编码,文件 API 由库内部转换,测试 `test_windows:test_wtf8` 覆盖了 `io.open` 写这种文件名。 +- `find_file_holders` 需要相应权限,且只列出**当前进程可见**的句柄持有者。 +- 含代理对/生僻字的路径(WTF-8 场景):Lua 层字符串仍是 UTF-8,转换由库内部处理,`test_windows:test_wtf8` 覆盖了用这种文件名 `io.open`。 - 非 Windows 平台不要 `require` 本模块。 diff --git a/skills/references/process/subprocess.md b/skills/references/process/subprocess.md index 40555c74..f6bb28db 100644 --- a/skills/references/process/subprocess.md +++ b/skills/references/process/subprocess.md @@ -1,50 +1,34 @@ # bee.subprocess -`require "bee.subprocess"`,对应 `meta/subprocess.lua`、`test/test_subprocess.lua`、`test/shell.lua`。 +子进程与管道。签名见 `meta/subprocess.lua`,行为契约见 `test/test_subprocess.lua`,测试脚手架见 `test/shell.lua`。 -## spawn 配置表 +## 要点 -```lua -local subprocess = require "bee.subprocess" -local p = assert(subprocess.spawn { - "lua", "-e", "io.write(io.read 'a')", -- [1] 程序路径,其后为参数(数组可嵌套,会被展平) - cwd = "some/dir", -- string | bee.fspath - stdin = true, -- true 建管道 | file* 直接接文件 - stdout = true, - stderr = "stdout", -- true | file* | "stdout"(共享标准输出) - env = { BEE_TEST = "ok", OTHER = false },-- false 表示删除该变量;缺省继承父进程环境 - suspended = false, -- 以挂起状态启动,后续 p:resume() - detached = false, - console = "new", -- Windows: "new"|"disable"|"inherit"|"detached" - hideWindow = false, -- Windows 隐藏窗口 - searchPath = false, -- Windows 是否搜索 PATH -}) -``` - -只有请求了对应管道的句柄才存在:`p.stdin` / `p.stdout` / `p.stderr`,都是标准 `file*`。 +- `spawn(args)` 的 `args[1]` 是程序路径,其后为参数(数组可嵌套,会展平);`spawn` 失败返回 `nil, err`,用 `assert` 包装。 +- 只有请求了对应管道的句柄才存在:`p.stdin` / `p.stdout` / `p.stderr`,都是标准 `file*`。 +- `stdin`/`stdout`/`stderr` 传 `true` 建管道、传 `file*` 直接接文件;`stderr = "stdout"` 表示共享标准输出。 +- `env` 是**覆盖**语义:缺省继承父进程,键值为 `false` 表示删除该变量。 +- `wait()` 之后 `is_running()` 为 `false`;用完记得 `detach()` 收尾。 +- `kill(0)` 只探测存活、不真杀;被 kill 的返回码平台相关(Windows 上是 `0x0F00`)。 +- 切换 `cwd` 会真正改子进程工作目录(`test_cwd` 用 `fs.current_path()` 验证)。 +- `setenv` 改的是**父进程**环境,后续 `spawn` 会继承。 -## 进程方法 +## 用法 ```lua -p:wait() --> exitcode | nil, err -- 也用于收尸 -p:kill([signum=15]) --> boolean -- kill(0) 只探测存活,不真杀 -p:is_running() --> boolean -p:get_id() --> pid -p:resume() -- 恢复 suspended 启动的进程 -p:native_handle() --> lightuserdata -p:detach() -- 结束收尾,不再由本对象管理 -``` - -## 典型用法 +local subprocess = require "bee.subprocess" -```lua local p = assert(subprocess.spawn { - "lua", "-e", "io.write 'ok'", - stdout = true, stderr = "stdout", + "lua", "-e", "io.write(io.read 'a')", -- 后续元素是参数 + cwd = "some/dir", + stdin = true, stdout = true, stderr = "stdout", + env = { BEE_TEST = "ok", OTHER = false }, + suspended = false, detached = false, + console = "new", hideWindow = false, searchPath = false, -- 后三个是 Windows 专有 }) -local out = p.stdout:read "a" -- "ok" +local out = p.stdout:read "a" assert(p:wait() == 0) -assert(p:detach() == true) -- 测试里每个进程用完都 detach +assert(p:detach() == true) -- 测试里每个进程用完都 detach ``` 管道当 stdin(`test_subprocess:test_stdio_1`): @@ -87,20 +71,9 @@ while #progs > 0 do end ``` -## 其它工具 - -```lua -subprocess.peek(file) --> 管道可读字节数 | nil, err -subprocess.get_id() --> 当前进程 pid -subprocess.setenv(name, value) -- 改**父进程**环境,后续 spawn 会继承(value 传 false 删除) -subprocess.quotearg(arg) -- 处理空格/引号的命令行转义 -``` - ## 注意事项 -- `spawn` 失败返回 `nil, err`,用 `assert` 包装。 -- `wait()` 之后 `is_running()` 为 `false`;被 kill 的进程返回码是平台相关的(Windows 上 `0x0F00`)。 - 用文件重定向时 `p.stdout` 就是传入的那个 `file*`(`assert(p.stdout == f)`),父进程 `close` 后子进程仍能写。 -- Windows 下 `windows.filemode(io.stdin, "b")` 可关闭 CRT 的 CRLF 转换(`test_subprocess` 有覆盖)。 -- 测试里统一通过 `shell:runlua(script, options)`(`test/shell.lua`)启动带正确 `package.cpath` 的 Lua 子进程,`options` 即上面的 spawn 表,`options[1]` 或 `"_"` 用于插入额外 argv。 -- `cwd` 会真正切换子进程工作目录(`test_cwd` 用 `fs.current_path()` 验证)。 +- `subprocess.peek(file)` 探测管道可读字节数;`subprocess.quotearg(arg)` 处理空格/引号转义;`subprocess.get_id()` 取当前 pid。 +- Windows 下 `windows.filemode(io.stdin, "b")` 可关掉 CRT 的 CRLF 转换。 +- 测试统一用 `shell:runlua(script, options)`(`test/shell.lua`)起带正确 `package.cpath` 的 Lua 子进程,`options` 就是上面的 spawn 表,`options[1]` 或 `"_"` 用于插入额外 argv。 From 0d2523300aafc6820c3b576eee04dff007774ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 24 Sep 2026 18:19:01 +0800 Subject: [PATCH 6/6] trim skills to an index, drop references/ --- AGENT.md | 2 +- skills/SKILL.md | 105 +++++----------------- skills/references/concurrency/channel.md | 81 ----------------- skills/references/concurrency/thread.md | 44 ---------- skills/references/core/filesystem.md | 44 ---------- skills/references/core/serialization.md | 51 ----------- skills/references/core/sys.md | 40 --------- skills/references/core/time.md | 40 --------- skills/references/io/async.md | 63 ------------- skills/references/io/epoll.md | 42 --------- skills/references/io/filewatch.md | 60 ------------- skills/references/io/select.md | 55 ------------ skills/references/io/socket.md | 107 ----------------------- skills/references/platform/crash.md | 23 ----- skills/references/platform/debugging.md | 37 -------- skills/references/platform/platform.md | 35 -------- skills/references/platform/windows.md | 39 --------- skills/references/process/subprocess.md | 79 ----------------- 18 files changed, 25 insertions(+), 922 deletions(-) delete mode 100644 skills/references/concurrency/channel.md delete mode 100644 skills/references/concurrency/thread.md delete mode 100644 skills/references/core/filesystem.md delete mode 100644 skills/references/core/serialization.md delete mode 100644 skills/references/core/sys.md delete mode 100644 skills/references/core/time.md delete mode 100644 skills/references/io/async.md delete mode 100644 skills/references/io/epoll.md delete mode 100644 skills/references/io/filewatch.md delete mode 100644 skills/references/io/select.md delete mode 100644 skills/references/io/socket.md delete mode 100644 skills/references/platform/crash.md delete mode 100644 skills/references/platform/debugging.md delete mode 100644 skills/references/platform/platform.md delete mode 100644 skills/references/platform/windows.md delete mode 100644 skills/references/process/subprocess.md diff --git a/AGENT.md b/AGENT.md index b42b5141..aeb9ea34 100644 --- a/AGENT.md +++ b/AGENT.md @@ -2,7 +2,7 @@ 本文件为 AI 编码助手在此仓库中工作时提供指引。 -> Lua 侧 API 与用法见 [`skills/SKILL.md`](skills/SKILL.md)(模块细节在 `skills/references/`,按 core / io / concurrency / process / platform 分组,示例多摘自 `test/`)。查签名一律以 `meta/*.lua` 为准,`skills/` 只写 meta 表达不了的约定与陷阱。 +> Lua 侧 API 见 [`skills/SKILL.md`](skills/SKILL.md)——只有模块索引与跨模块约定,签名一律以 `meta/*.lua` 为准,行为与示例看 `test/`。 ## 项目简介 diff --git a/skills/SKILL.md b/skills/SKILL.md index f95dc92f..4a39a4d9 100644 --- a/skills/SKILL.md +++ b/skills/SKILL.md @@ -9,53 +9,35 @@ Lua 扩展库,为 Lua 5.4 / 5.5 提供系统级原生绑定。 ## API 参考来源 -**本 skill 不维护 bee 的 API 签名表**,请直接读仓库里的权威来源: +**本 skill 不维护 bee 的 API 签名表**,以仓库里的文件为唯一权威来源: | 目的 | 来源 | |------|------| | 函数签名、参数/返回值类型、字段与常量 | `meta/.lua`(LuaLS/EmmyLua 注解,带中文说明) | -| 行为契约、错误文案、边界情况 | `test/test_.lua` | +| 行为契约、错误文案、边界情况、可运行示例 | `test/test_.lua` | +| 构建与测试命令、测试规范 | `AGENT.md` | | C++ 层实现细节 | `bee/`、`binding/lua_.cpp` | -`skills/references/` 下的文档只写**从 meta 里读不出来的东西**:跨模块约定、非显然语义与陷阱、可运行片段、平台差异。读 meta 拿到签名后发现行为不明确时,回来查对应文档或直接看测试。 - -## 快速开始 - -```lua -local socket = require "bee.socket" -local select = require "bee.select" - -local server = assert(socket.create "tcp") -assert(server:bind("127.0.0.1", 0)) -assert(server:listen()) -local _, port = server:info "socket":value() - -local s = select.create() -s:event_add(server, select.SELECT_READ) -s:wait() -local conn = assert(server:accept()) -``` - ## 模块索引 -| 模块 | meta | 说明文档 | 用途 | -|------|------|----------|------| -| `bee.platform` | `meta/platform.lua` | [platform](references/platform/platform.md) | 平台/编译器/架构信息(纯数据表) | -| `bee.filesystem` | `meta/filesystem.lua` | [filesystem](references/core/filesystem.md) | 路径与文件系统操作 | -| `bee.serialization` | `meta/serialization.lua` | [serialization](references/core/serialization.md) | 序列化(线程/通道的底层) | -| `bee.time` | `meta/time.lua` | [time](references/core/time.md) | 墙钟 / 单调 / 线程 CPU 时间 | -| `bee.sys` | `meta/sys.lua` | [sys](references/core/sys.md) | 可执行文件路径、文件锁 | -| `bee.socket` | `meta/socket.lua` | [socket](references/io/socket.md) | TCP/UDP/Unix socket | -| `bee.select` | `meta/select.lua` | [select](references/io/select.md) | select 风格多路复用 | -| `bee.epoll` | `meta/epoll.lua` | [epoll](references/io/epoll.md) | epoll 风格多路复用(Windows 走 IOCP) | -| `bee.async` | `meta/async.lua` | [async](references/io/async.md) | 异步 I/O(IOCP / io_uring / GCD) | -| `bee.filewatch` | `meta/filewatch.lua` | [filewatch](references/io/filewatch.md) | 文件监控 | -| `bee.thread` | `meta/thread.lua` | [thread](references/concurrency/thread.md) | 线程 | -| `bee.channel` | `meta/channel.lua` | [channel](references/concurrency/channel.md) | 线程间通信 | -| `bee.subprocess` | `meta/subprocess.lua` | [subprocess](references/process/subprocess.md) | 子进程与管道 | -| `bee.windows` | `meta/windows.lua` | [windows](references/platform/windows.md) | Windows 专有工具 | -| `bee.crash` | `meta/crash.lua` | [crash](references/platform/crash.md) | 崩溃 dump | -| `bee.debugging` | `meta/debugging.lua` | [debugging](references/platform/debugging.md) | 断点 / 调试器探测 | +| 模块 | meta | 用途 | +|------|------|------| +| `bee.platform` | `meta/platform.lua` | 平台/编译器/架构信息(纯数据表) | +| `bee.filesystem` | `meta/filesystem.lua` | 路径与文件系统操作 | +| `bee.serialization` | `meta/serialization.lua` | 序列化(线程/通道的底层) | +| `bee.time` | `meta/time.lua` | 墙钟 / 单调 / 线程 CPU 时间 | +| `bee.sys` | `meta/sys.lua` | 可执行文件路径、文件锁 | +| `bee.socket` | `meta/socket.lua` | TCP/UDP/Unix socket | +| `bee.select` | `meta/select.lua` | select 风格多路复用 | +| `bee.epoll` | `meta/epoll.lua` | epoll 风格多路复用(Windows 走 IOCP) | +| `bee.async` | `meta/async.lua` | 异步 I/O(IOCP / io_uring / GCD) | +| `bee.filewatch` | `meta/filewatch.lua` | 文件监控 | +| `bee.thread` | `meta/thread.lua` | 线程 | +| `bee.channel` | `meta/channel.lua` | 线程间通信 | +| `bee.subprocess` | `meta/subprocess.lua` | 子进程与管道 | +| `bee.windows` | `meta/windows.lua` | Windows 专有工具 | +| `bee.crash` | `meta/crash.lua` | 崩溃 dump | +| `bee.debugging` | `meta/debugging.lua` | 断点 / 调试器探测 | ## 跨模块约定 @@ -66,50 +48,11 @@ local conn = assert(server:accept()) - 线程/通道传值经 `bee.serialization`,只支持 `nil/boolean/number/string/table/light C function`。 - 平台上不可用的模块/接口在测试中跳过(`test/test_skip.lua`、`test/supported.lua`)。 -## 构建与测试 - -```bash -luamake # 编译 + 测试 -luamake -notest # 只编译 -luamake test -v # 只测试,详细输出 -luamake test -v # 只跑名称匹配的用例(如 socket.test_udp) -``` - -测试基于 ltest,文件在 `test/`: - -```lua -local lt = require "ltest" -local m = lt.test "module" - -function m:test_case() - lt.assertEquals(a, b) - lt.assertNil(x); lt.assertIsUserdata(fd); lt.assertTrue(cond) - lt.assertError(function () ... end) - lt.assertErrorMsgEquals("max_completions is less than or equal to zero.", async.create, 0) - lt.failure "msg" -end -``` - -常用辅助: - -- `test/shell.lua` — `shell:runlua(script, spawn_options)` 起带正确 `package.cpath` 的 Lua 子进程;`shell:add_readonly/del_readonly`;`shell:pwd()`;`shell.is_luamake`。 -- `test/supported.lua` — `supported "symlink"` / `supported "hardlink"` 特性探测(结果缓存)。 -- `test/test_skip.lua` — 按平台 `lt.skip "module.test_name"` 跳过用例。 -- `test/test.lua` — 入口:设置 `package.path/cpath`、按平台装载测试文件、`lt.run()` 后 `os.exit`。 - ## 可选链(编译期 patch) -`?.` / `?:` / `?[...]` / `f?(...)` 是 vendored Lua 的补丁语法,仅当 `luamake -optchain` 构建时可用: - -```lua -local a = obj?.a?.b?.c -- 链上任一环节为 nil 即短路为 nil -local v = t?[1]?[2] -local r = obj?:method(args) -- 只保护接收者,方法本身不存在仍报错 -local x = f?(1, 2) -``` +`?.` / `?:` / `?[...]` / `f?(...)` 是 vendored Lua 的补丁语法,仅当 `luamake -optchain` 构建时可用(补丁机制见 `AGENT.md`): -- 只有 `nil` 短路,`false` 会照常报错。 -- 短路时参数/键不会被求值;接收者只求值一次。 +- 只有 `nil` 短路,`false` 会照常报错;短路时参数/键不会被求值,接收者只求值一次。 - 短路只覆盖链本身:`(nothing?.b).c`、`nothing?.b + 1` 仍报错。 - 不可作为赋值目标。 -- `test/test_optional_chain.lua` 还锁定了生成的字节码布局。 +- 完整行为(含字节码布局)见 `test/test_optional_chain.lua`。 diff --git a/skills/references/concurrency/channel.md b/skills/references/concurrency/channel.md deleted file mode 100644 index a2f288be..00000000 --- a/skills/references/concurrency/channel.md +++ /dev/null @@ -1,81 +0,0 @@ -# bee.channel - -线程间通信(命名通道)。签名见 `meta/channel.lua`,行为契约见 `test/test_channel.lua`。 - -## 要点 - -- 通道是**全局命名**的:`channel.query(name)` 在别的线程里靠名字找回同一个通道,所以名字要唯一且双方约定一致;`create` 重名会 `error`(`destroy` 后可以重新 `create`)。 -- `push(...)` 内部序列化,类型限制同 [serialization](../core/serialization.md)。 -- `pop()` 是 **FIFO 逐条出队**,返回 `(ok, ...)`:`ok == false` 表示通道为空。 -- `fd()` 给 epoll/select 用,能等可读,避免空转。 - -## 用法 - -```lua -local channel = require "bee.channel" - -local chan = channel.create "test" -chan:push(1024); chan:push(1025) -local ok, v = chan:pop(); assert(ok == true and v == 1024) -- 第一个返回值是 ok -ok, v = chan:pop(); assert(ok == true and v == 1025) -ok, v = chan:pop() -- ok == false,通道已空 -channel.destroy "test" -``` - -worker + 请求/响应(双向要建两个通道): - -```lua -local thread = require "bee.thread" -local channel = require "bee.channel" - -local req = channel.create "testReq" -local res = channel.create "testRes" - -local thd = thread.create([[ - local thread = require "bee.thread" - local channel = require "bee.channel" - local req = channel.query "testReq" - local res = channel.query "testRes" - local function dispatch(ok, what, ...) - if not ok then return end - if what == "exit" then return true end - res:push(what, ...) - end - while not dispatch(req:pop()) do - thread.sleep(0) -- 空转等待 - end -]]) - -req:push("echo", 1, { A = { B = "C" } }) -local ok, what, arg = res:pop() -req:push "exit" -thread.wait(thd) -channel.destroy "testReq"; channel.destroy "testRes" -``` - -用 `fd()` 接多路复用,替掉空转(`test_channel:test_fd`): - -```lua -local epoll = require "bee.epoll" - -local epfd = epoll.create(16) -epfd:event_add(req:fd(), epoll.EPOLLIN) -for _, event in epfd:wait() do - if event & (epoll.EPOLLERR | epoll.EPOLLHUP) ~= 0 then error "unknown error" end - if event & epoll.EPOLLIN ~= 0 then - while true do -- 取空为止 - local ok, what, ... = req:pop() - if not ok then break end - -- 分发;收到 "exit" 则 return - end - end -end -``` - -主线程侧同理监听 `res:fd()`;`bee.async` 里可用 `as:submit_poll(chan:fd(), udata)`。 - -## 注意事项 - -- 单个通道是**单向队列**,双向通信要建两个。 -- `destroy` 会清空通道内数据,不要依赖销毁后还能 `pop`。 -- 用 `fd()` 等可读时,收到通知后必须 `pop` 到 `ok == false`,否则会一直就绪。 diff --git a/skills/references/concurrency/thread.md b/skills/references/concurrency/thread.md deleted file mode 100644 index 4aa6d483..00000000 --- a/skills/references/concurrency/thread.md +++ /dev/null @@ -1,44 +0,0 @@ -# bee.thread - -原生线程。签名见 `meta/thread.lua`,行为契约见 `test/test_thread.lua`。 - -## 要点 - -- `create(source, ...)` 的 `source` 是**Lua 源码字符串**,不能传函数;额外参数序列化后在新线程里以 `...` 取得。 -- 新线程**不共享全局变量**,只能靠 `require "bee.*"` 或参数拿数据。 -- `thread.id` 主线程为 0,其他线程非 0。 -- 线程里的错误不会中断主线程,累积在 `errlog()`(读取即取走并清空)。 -- 跨线程传值走 `bee.serialization`,类型限制见 [serialization](../core/serialization.md)。 -- 线程间通信不要共享 userdata,用 `bee.channel`。 - -## 用法 - -```lua -local thread = require "bee.thread" - -GLOBAL = true -local thd = thread.create([[ - local thread = require "bee.thread" - local args = ... -- 传给 create 的额外参数 - assert(GLOBAL == nil) -- 线程不共享全局变量 - assert(thread.id ~= 0) - thread.setname "worker" -]], "hello") -thread.wait(thd) -assert(thread.errlog() == nil) -``` - -线程内的错误(`test_thread:test_thread_3`): - -```lua -local thd = thread.create [[ error "Test thread error." ]] -thread.wait(thd) -local msg = thread.errlog() -assert(string.find(msg, "Test thread error.", nil, true)) -``` - -## 注意事项 - -- 每个用例结束都应检查 `thread.errlog() == nil`(`test_*.lua` 里的 `assertNotThreadError` 约定),避免错误被静默吞掉。 -- macOS / BSD 上 `thread.sleep` 用例在 `test/test_skip.lua` 里被跳过,跨平台测试注意这一点。 -- `thread.sleep(0)` 是合法的「让出」写法,`test_channel` 的 worker 空转循环就靠它。 diff --git a/skills/references/core/filesystem.md b/skills/references/core/filesystem.md deleted file mode 100644 index b40e6708..00000000 --- a/skills/references/core/filesystem.md +++ /dev/null @@ -1,44 +0,0 @@ -# bee.filesystem - -文件系统与路径操作。签名见 `meta/filesystem.lua`,行为契约见 `test/test_filesystem.lua`。 - -## 要点 - -- 路径对象是 `bee.fspath`,`fs.path(p)` 创建;所有接口同时接受字符串。 -- `a / b` 是路径拼接(会补分隔符),`a .. b` 是直接拼接;取字符串用 `:string()`。 -- `fs.pairs` 非递归 / `fs.pairs_r` 递归,迭代产出 `(bee.fspath, bee.directory_entry)`;**目录不可遍历时抛错**,不是返回 `nil, err`。 -- 选项是位标志:`fs.copy_options` / `fs.perm_options` / `fs.directory_options`,用 `|` 组合。 -- `fs.current_path()` 无参读 CWD、有参切换;`fs.last_write_time` 与 `fs.permissions` 都是读写两用。 - -```lua -local fs = require "bee.filesystem" -local root = fs.absolute("./temp/"):lexically_normal() -fs.create_directories(root / "dir") - -fs.copy(fs.path "temp", fs.path "temp1", - fs.copy_options.overwrite_existing | fs.copy_options.recursive) - -for path, entry in fs.pairs(fs.path "temp") do - print(path:string(), entry:type(), entry:file_size(), entry:last_write_time()) -end -``` - -递归累加(`test_fs:test_copy_dir` 的骨架): - -```lua -local function each_directory(dir, result) - result = result or {} - for path, status in fs.pairs(fs.path(dir)) do - if status:is_directory() then each_directory(path, result) end - result[path:string()] = true - end - return result -end -``` - -## 注意事项 - -- 路径对象与字符串互转的常见写法:`if type(filename) == "userdata" then filename = filename:string() end`。 -- `fs.remove` 对不存在的路径返回 `false`,递归删除要用 `fs.remove_all`。 -- 测试里所有文件操作都在 `fs.temp_directory_path() / "test_bee"` 下进行(`test/test.lua`),临时目录用完 `pcall(fs.remove_all, dir)` 清理。 -- 符号链接相关用例先 `if not supported "symlink" then return end`;Windows 上 symlink / hardlink 需要权限,`supported.lua` 的探测方式就是 `pcall(fs.create_symlink, ...)`。 diff --git a/skills/references/core/serialization.md b/skills/references/core/serialization.md deleted file mode 100644 index 66450f04..00000000 --- a/skills/references/core/serialization.md +++ /dev/null @@ -1,51 +0,0 @@ -# bee.serialization - -跨线程/跨通道传值的序列化。签名见 `meta/serialization.lua`,行为契约见 `test/test_serialization.lua`。 - -## 要点 - -- 支持的类型:`nil`、`boolean`、`number`、`string`、`table`、**light C function**(如 `require`、`os.clock`)。 -- **引用共享会保留**:同一张表被多处引用,反序列化后仍指向同一份。 -- `pack` 返回 lightuserdata(需自行管理生命周期);跨线程传值用 `packstring` 更安全。 - -```lua -local seri = require "bee.serialization" - -local data = seri.packstring(1, { A = { B = "C" } }, true) -local a, t, b = seri.unpack(data) -``` - -引用共享(`test_seri:test_ref`): - -```lua -local N = 10 -local t = {} -for i = 1, N do t[i] = {} end -for i = 1, N do for j = 1, N do t[i][j] = t[j] end end -local newt = seri.unpack(seri.pack(t)) -for i = 1, N do - for j = 1, N do - assert(newt[i][j] == newt[j]) -- 解出来仍指向同一张表 - end -end -``` - -## 不支持的类型与报错文案(固定字符串,测试逐字断言) - -| 输入 | 错误消息 | -|------|----------| -| 普通 Lua function | `Only light C function can be serialized` | -| coroutine(thread) | `Unsupport type thread to serialize` | -| userdata(如 `io.stdout`) | `Unsupport type userdata to serialize` | - -```lua -seri.pack(require) -- OK:require 是 light C function -seri.pack(function () end) -- error: Only light C function can be serialized -seri.pack(coroutine.create(f)) -- error: Unsupport type thread to serialize -seri.pack(io.stdout) -- error: Unsupport type userdata to serialize -``` - -## 注意事项 - -- 这是 `bee.thread` 参数传递和 `bee.channel` push/pop 的底层实现,限制完全一致。 -- `pack` 与 `packstring` 对不支持类型的报错行为一致(测试对两者都断言)。 diff --git a/skills/references/core/sys.md b/skills/references/core/sys.md deleted file mode 100644 index 999a7fd7..00000000 --- a/skills/references/core/sys.md +++ /dev/null @@ -1,40 +0,0 @@ -# bee.sys - -系统工具:自身路径与文件锁。签名见 `meta/sys.lua`,行为契约见 `test/test_sys.lua`。 - -## 要点 - -- `exe_path` / `dll_path` / `fullpath` 返回的是 **`bee.fspath`**,要字符串时 `:string()`;失败返回 `nil, err`。 -- `filelock(path)` 是**独占**语义:拿不到锁返回 `nil`(不是 `error`);返回的 `file*` 既是句柄也是锁,`close()` 即解锁。 - -## 用法 - -跨进程互斥(`test_sys:test_filelock_1`): - -```lua -local sys = require "bee.sys" -local fs = require "bee.filesystem" - -local f1 = assert(sys.filelock "temp.lock") -- 拿到锁 -assert(sys.filelock "temp.lock" == nil) -- 同进程再取也是 nil -f1:close() -- 关闭句柄 = 释放锁 -local f2 = assert(sys.filelock "temp.lock") -f2:close() -fs.remove "temp.lock" -``` - -跨进程验证见 `test_sys:test_filelock_2`:用 `shell:runlua` 起子进程拿锁,父进程随即返回 `nil`;子进程退出后父进程即可获取。 - -定位自身与路径规范化: - -```lua -local exe = sys.exe_path():string() -local dll = sys.dll_path() -local real = sys.fullpath("some/rel/path"):string() -``` - -## 注意事项 - -- 文件锁是**独占**的,同进程重复加锁同样返回 `nil`,别拿它当可重入锁。 -- 锁文件用完自行 `fs.remove`。 -- `test/shell.lua` 里定位当前 Lua 解释器用的是 `fs.absolute(fs.path(arg[i+1]))`(配合 `arg` 负数索引),需要类似逻辑时可以参考。 diff --git a/skills/references/core/time.md b/skills/references/core/time.md deleted file mode 100644 index 1388e21f..00000000 --- a/skills/references/core/time.md +++ /dev/null @@ -1,40 +0,0 @@ -# bee.time - -毫秒级时间。签名见 `meta/time.lua`,行为契约见 `test/test_time.lua`。三个函数都返回**毫秒整数**。 - -## 要点 - -- `time.time()` 是墙钟,会被 NTP / 手动改钟影响;**测间隔一律用 `time.monotonic()`**。 -- `time.thread()` 是当前线程已消耗的 CPU 时间。 -- `time.time()` 与 `os.time() * 1000` 相差不超过 2 秒(`test_time:test_now`),但单位不同,别混用。 - -## 用法 - -测量耗时(`test_thread:test_sleep`): - -```lua -local time = require "bee.time" -local thread = require "bee.thread" - -local t1 = time.monotonic() -thread.sleep(1) -local t2 = time.monotonic() -assert(t2 - t1 >= 1) -``` - -超时轮询(`test_async.lua` 的 `wait_completion`): - -```lua -local start = time.monotonic() -while time.monotonic() - start < timeout then - for op, token, st, data, errcode in as:wait(100) do - return op, token, st, data, errcode - end -end -error "wait_completion timeout" -``` - -## 注意事项 - -- 精度/粒度依平台,`test_time:test_monotonic` 只断言 `> 0`。 -- 需要「等一段时间」用 `thread.sleep`(毫秒),不要忙等 `monotonic`。 diff --git a/skills/references/io/async.md b/skills/references/io/async.md deleted file mode 100644 index 7715cca3..00000000 --- a/skills/references/io/async.md +++ /dev/null @@ -1,63 +0,0 @@ -# bee.async - -跨平台异步 I/O:macOS 用 GCD,Windows 用 IOCP,Linux 用 io_uring/epoll。签名见 `meta/async.lua`,行为契约见 `test/test_async.lua`。 - -模型:**一次投递 → 一次 completion**,投递时传入的 token 原样回传。 - -## 要点 - -- `associate(fd)` / `associate_file(file)` 必须在**首次提交 I/O 之前**完成;重复 `associate` 同一 socket 允许。 -- `wait(timeout_ms)` 阻塞、`poll()` 非阻塞,都返回**迭代器**,产出 `(op, token, status, data, errcode)`: - - `op`:`OP_READ` / `OP_WRITE` / `OP_ACCEPT` / `OP_CONNECT` / `OP_FILE_READ` / `OP_FILE_WRITE` / `OP_POLL`。 - - `status`:`SUCCESS` / `CLOSE` / `ERROR` / `CANCEL`;**对端关闭是 `CLOSE`,不是 `ERROR`**。 - - `data`:`OP_ACCEPT` 是新 socket userdata;`OP_FILE_READ` 是读到的字符串;其余是字节数。 -- 读写缓冲区独立于 socket:`OP_READ` 的数据在 `readbuf` 里,要自己 `rb:read()` 取。 -- **写入的 completion `bytes` 恒为 `0`**(C 层已 drain 完,含 partial write 重试)。 -- `submit_read` 有背压:ring buffer 空闲不足返回 `false`(不是错误),重试前先 `rb:read()` 腾空间。 -- `writebuf:write(data)` 返回 `true` 表示缓冲已达 hwm,调用方要自己背压。 -- 关闭 fd 前建议 `as:cancel(fd)`,回收未完成操作(Windows 上尤其重要)。 - -## 用法(取自 `test_async.lua`) - -```lua -local async = require "bee.async" -local socket = require "bee.socket" - -local as = assert(async.create(64)) -- create(max_completions) -local sfd = assert(socket.create "tcp") -assert(as:associate(sfd)) -assert(sfd:bind("127.0.0.1", 0)); assert(sfd:listen()) -local _, port = sfd:info "socket":value() - -local cfd = assert(socket.create "tcp") -assert(as:associate(cfd)) -local ok, err = cfd:connect("127.0.0.1", port) -assert(ok ~= nil, err) - -assert(as:submit_accept(sfd, "accept_token")) -- completion 的 data 即新 socket -assert(as:submit_read(rb, newfd, { id = 42 })) -- token 可以是任意 - -local op, token, status, bytes -for _op, _tok, _st, _data in as:wait(1000) do - op, token, status, bytes = _op, _tok, _st, _data - break -end --- op == async.OP_READ;收到 SUCCESS 后从 ring buffer 取数据: -local data = rb:read(5) -- 精确字节数;不足返回 nil -local line = rb:readline() -- 或按行取,默认分隔符 "\r\n" -``` - -文件 I/O 要额外的 `associate_file`: - -```lua -local rf = assert(io.open(path, "rb")) -assert(as:associate_file(rf)) -assert(as:submit_file_read(rf, 128, 0, "fread")) --- completion: op == OP_FILE_READ,data 是读到的字符串(不是字节数) -``` - -## 注意事项 - -- `submit_poll(fd, udata)` 只通知可读、不消费数据,典型用途是监听 `channel:fd()` 后自行 `channel:pop()`。 -- `create` 的 `max_completions <= 0` 与 `readbuf` 的 `bufsize <= 0` 都是 `error`,文案见测试断言。 -- 事件循环里别在 completion 回调内阻塞等待同一 fd 的下一个事件,会死锁;测试里的 `wait_completion` 是超时轮询写法,可参考。 diff --git a/skills/references/io/epoll.md b/skills/references/io/epoll.md deleted file mode 100644 index eae53cff..00000000 --- a/skills/references/io/epoll.md +++ /dev/null @@ -1,42 +0,0 @@ -# bee.epoll - -epoll 风格 I/O 多路复用(Windows 由 IOCP 实现)。签名见 `meta/epoll.lua`,行为契约见 `test/test_epoll.lua`、`test/test_channel.lua`。 - -## 要点 - -- 常量是位标志,取值被 `test_epoll:test_enum` 锁定;`EPOLLRDHUP`(对端关闭)与 `EPOLLONESHOT`(一次性)是 select 没有的。 -- `fd` 可为 `bee.socket.fd` 或 `lightuserdata`(如 `channel:fd()`);`event_add` 的第三个参数是迭代回传的关联对象,默认 fd 自身。 -- `wait([timeout])` 返回**迭代器**,空迭代表示超时;`timeout` 毫秒、`-1`/省略为无限等待,传 `0` 即非阻塞轮询。 -- 只做就绪通知,**不消费数据**;`event_add` 到已存在的 fd、或对未注册的 fd `event_mod`/`event_del` 返回 `nil`(不抛错)。 -- `epoll.create(max_events)` 对 `<= 0` 的参数直接 `error`:`maxevents is less than or equal to zero.`。 - -## 用法 - -```lua -local epoll = require "bee.epoll" - -local epfd = assert(epoll.create(16)) -epfd:event_add(res_chan:fd(), epoll.EPOLLIN, "res") - -for obj, event in epfd:wait() do - if event & (epoll.EPOLLERR | epoll.EPOLLHUP) ~= 0 then - error "unknown error" - end - if event & epoll.EPOLLIN ~= 0 then - -- 就绪通知:数据要自己取空 - while true do - local ok, v = res_chan:pop() - if not ok then break end - print(obj, v) - end - end -end -``` - -`test_channel:test_fd` 是完整范例:worker 线程里 `epfd:event_add(req:fd(), epoll.EPOLLIN)`,主线程监听 `res:fd()`,双方用通道收发。 - -## 注意事项 - -- 取到 `EPOLLIN` 后**必须把数据取空**(`channel:pop()` 到 `ok == false`),否则下一次还会立刻就绪。 -- 关闭实例前先 `event_del`,避免残留注册;实例 `close` 后 `wait` 会返回 `nil, "bad file descriptor"`。 -- 需要更简单的读/写语义用 `bee.select`;需要「一次投递一次完成事件」用 `bee.async`。 diff --git a/skills/references/io/filewatch.md b/skills/references/io/filewatch.md deleted file mode 100644 index 4c4facf6..00000000 --- a/skills/references/io/filewatch.md +++ /dev/null @@ -1,60 +0,0 @@ -# bee.filewatch - -文件系统监控,底层 inotify / FSEvents / ReadDirectoryChangesW。签名见 `meta/filewatch.lua`,行为契约见 `test/test_filewatch.lua`。 - -## 要点 - -- `select()` 是**非阻塞轮询**:没有事件时立即返回 `nil`,需要自己 `thread.sleep` 再试。 -- 事件类型只有 `"modify"` 和 `"rename"` 两种(创建、删除、重命名都落在 `rename` 上)。 -- `add(path)` 只接受字符串(`fs.path` 要先 `:string()`),内部会转绝对路径;可多次调用添加多个根。 -- `set_filter(fn)` 的 `fn` 收到路径字符串、返回 `true` 表示接受该事件;传 `nil` 清除过滤器。 - -## 用法 - -```lua -local filewatch = require "bee.filewatch" -local fs = require "bee.filesystem" -local thread = require "bee.thread" - -local root = fs.absolute("./temp"):lexically_normal() -local fw = filewatch.create() -fw:set_recursive(true) -fw:set_follow_symlinks(true) -fw:set_filter(function (path) return true end) -fw:add(root:string()) - -while true do - local kind, path = fw:select() - if kind then - print(kind, path) -- "modify"/"rename" + 变更路径 - else - thread.sleep(20) -- 空转等待 - end -end -``` - -「收到一批事件就停」的写法(`test_filewatch:test_2`)——`select` 连续返回若干次 `nil` 即认为这一轮事件收完: - -```lua -local retry = 5 -local n = retry -local list = {} -while true do - local w, v = fw:select() - if w then - n = retry - list[#list+1] = v - else - n = n - 1 - if n < 0 then break end - thread.sleep(20) - end -end -``` - -## 注意事项 - -- 事件可能重复或漏报(平台差异),消费端要去重并允许重试;测试里用 `has(list, v)` 去重。 -- 目录符号链接、指向自身的符号链接是已知边界情况,`test_symlink` 只验证不崩溃。 -- FreeBSD / OpenBSD / NetBSD 上整个 filewatch 测试组被 `lt.skip "filewatch"` 跳过。 -- 要「等事件」而不是轮询时,把它接到 `bee.epoll` / `bee.async` 的事件循环上,避免忙等。 diff --git a/skills/references/io/select.md b/skills/references/io/select.md deleted file mode 100644 index 666c3712..00000000 --- a/skills/references/io/select.md +++ /dev/null @@ -1,55 +0,0 @@ -# bee.select - -select 风格 I/O 多路复用。签名见 `meta/select.lua`,行为契约见 `test/test_socket.lua`。 - -## 要点 - -- 事件是**位标志**:`select.SELECT_READ` | `select.SELECT_WRITE`,只有这两个。 -- `fd` 可以是 `bee.socket.fd`,也可以是裸 `lightuserdata`(如 `channel:fd()`)。 -- `event_add` 的第三个参数是迭代时回传的关联对象,默认是 fd 自身。 -- `wait([timeout])` 返回**迭代器**,迭代产出 `(userdata, event)`;空迭代表示超时,`timeout` 单位毫秒、`-1`/省略为无限等待。 -- 同一轮可能读、写同时就绪,所以判断标志要**按位与**,多次迭代要**按位或**累加。 -- `select` 只做就绪通知,不消费数据,也不报错误事件;收发仍需自己 `fd:recv` / `fd:send`。 - -## 用法 - -```lua -local select = require "bee.select" - -local ctx = select.create() -ctx:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) -for obj, event in ctx:wait() do - if event & select.SELECT_READ ~= 0 then ... end - if event & select.SELECT_WRITE ~= 0 then ... end -end -``` - -只关心「有没有就绪」时可以把事件累加(来自 `test_socket.lua` 的 `simple_select`): - -```lua -local function simple_select(fd, mode) - local s = select.create() - if mode == "r" then - s:event_add(fd, select.SELECT_READ) - s:wait() - elseif mode == "w" then - s:event_add(fd, select.SELECT_WRITE) - s:wait() - elseif mode == "rw" then - s:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) - local event = 0 - for _, e in s:wait() do - event = event | e - end - return event - else - assert(false) - end -end -``` - -## 注意事项 - -- 一次性等待建议用 `local s = select.create()`(to-be-closed),避免忘记 `close`。 -- 与 `bee.epoll` 的差异:epoll 的 `event_*` 返回 `nil, err` 且支持 `EPOLLRDHUP` / oneshot 等语义,`bee.select` 的返回 `boolean`、只有读/写两种标志。 -- 需要「一次投递一次完成事件」用 `bee.async`,不要在 select 上自己拼状态机。 diff --git a/skills/references/io/socket.md b/skills/references/io/socket.md deleted file mode 100644 index 99140d93..00000000 --- a/skills/references/io/socket.md +++ /dev/null @@ -1,107 +0,0 @@ -# bee.socket - -TCP / UDP / Unix 套接字。签名见 `meta/socket.lua`,行为契约见 `test/test_socket.lua`。 - -## 非阻塞三态返回(本模块最重要的约定) - -| 返回值 | 含义 | -|--------|------| -| 值(`true`/数据/字节数/新 fd) | 成功 | -| `false` | 需等待,配合 `bee.select` / `bee.epoll` 重试 | -| `nil, errmsg` | 失败或对端关闭 | - -- `accept()` / `recv()` 的 `nil` 表示**对端关闭**,不是错误。 -- `send()` 返回**已发送字节数**,partial write 要自己切片重试。 -- `connect()` 是非阻塞的:之后等可写再 `status()` 判断是否真的连上。 - -## 用法 - -```lua -local socket = require "bee.socket" -local select = require "bee.select" - -local server = assert(socket.create "tcp") -- "tcp"|"udp"|"unix"|"tcp6"|"udp6" -assert(server:bind("127.0.0.1", 0)) -- 端口 0 = 系统分配 -assert(server:listen()) -- backlog 默认 5 -local address, port = server:info "socket":value() -- "socket" 本端 / "peer" 对端 - -local client = assert(socket.create "tcp") -client:connect("127.0.0.1", port) --- 等可写后:assert(client:status()) - -local session = assert(server:accept()) -- false = 尚无连接 -session:close(); client:close(); server:close() -``` - -UDP(`test_socket:test_udp`): - -```lua -local a, b = assert(socket.create "udp"), assert(socket.create "udp") -a:bind("127.0.0.1", 0); b:bind("127.0.0.1", 0) -local a_ep, b_ep = a:info "socket", b:info "socket" -assert(a:sendto("123", b_ep) == 3) -local data, from_ep = b:recvfrom() -- 需先等 b 可读 -assert(data == "123" and from_ep == a_ep) -``` - -句柄移交(`test_socket:test_dump`): - -```lua -local h = server:detach() -- 交出裸句柄并放弃所有权 -server = socket.fd(h) -- 再包装回来 -``` - -## 常见用法模板 - -同步等待 + 收发(`test_socket.lua` 的 `simple_select`): - -```lua -local function simple_select(fd, mode) - local s = select.create() - if mode == "r" then - s:event_add(fd, select.SELECT_READ) - elseif mode == "w" then - s:event_add(fd, select.SELECT_WRITE) - else - s:event_add(fd, select.SELECT_READ | select.SELECT_WRITE) - end - s:wait() -end - -local function syncSend(fd, data) - while true do - simple_select(fd, "w") - local n = fd:send(data) - if not n then return n, data end - data = data:sub(n + 1) - if data == "" then return true end - end -end -``` - -回显服务端(`test_socket.lua` 的 echo 用例,客户端跑在 `thread.create` 里): - -```lua -while true do - local event = simple_select(client, "rw") - if event & select.SELECT_READ then - local data = client:recv() - if data == nil then break -- 对端关闭 - elseif data ~= false then queue = queue .. data end - end - if event & select.SELECT_WRITE then - if #queue > 0 then - local n = client:send(queue) - if n == nil then break - elseif n ~= false then queue = queue:sub(n + 1) end - end - end -end -``` - -## 注意事项 - -- 参数校验错误会 `error()`,文案如 `bad argument #1 to 'bee.socket.create' (invalid option 'icmp')`。 -- Unix socket 关闭后是否自动 unlink 依平台,测试里用 `detectAutoUnlink` 探测。 -- 对端关闭后继续 `send` 不应崩溃(`test_SIGPIPE` 专门覆盖)。 -- 跨线程使用 socket 要把句柄传过去或让线程自己 `create`,不能共享 userdata。 diff --git a/skills/references/platform/crash.md b/skills/references/platform/crash.md deleted file mode 100644 index 0f8756b9..00000000 --- a/skills/references/platform/crash.md +++ /dev/null @@ -1,23 +0,0 @@ -# bee.crash - -崩溃处理器:进程崩溃时落 dump。签名见 `meta/crash.lua`,行为契约见 `test/test.lua`(入口就在用),实现在 `bee/crash/`。 - -## 要点 - -- `create_handler(dump_path)` 的 `dump_path` 是**目录**,崩溃日志写成 `/crash_.log`。 -- `dump_path` 传 `"-"` 时**关闭落盘**,只在崩溃时把日志打到控制台: - -```lua --- test/test.lua 的用法 -local crash = require "bee.crash" -local _ = crash.create_handler "-" -``` - -- handler 的 userdata 没有额外方法,靠 `` / GC 管理生命周期。 - -## 注意事项 - -- 只在 **Windows + MSVC**(且非 address sanitizer)构建下真正生效,其他平台是 `empty_handler`,构造调用是 **no-op**(`bee/crash/handler.h`)。因此跨平台代码可以无条件调用,但别指望在 Linux/macOS 上拿到 dump。 -- 参数是 `luaL_checkstring`,必须传字符串;非 Windows 平台不会校验路径是否存在。 -- 捕获的是 native 层崩溃(段错误、未处理异常),Lua 层的 `pcall` 错误栈不在覆盖范围。 -- 需要在崩溃后分析时,把 `dump_path` 指向可写目录并在 CI 里收集;不想生成文件就用 `"-"`。 diff --git a/skills/references/platform/debugging.md b/skills/references/platform/debugging.md deleted file mode 100644 index 59b4b4d6..00000000 --- a/skills/references/platform/debugging.md +++ /dev/null @@ -1,37 +0,0 @@ -# bee.debugging - -断点与调试器探测。签名见 `meta/debugging.lua`,实现在 `binding/lua_debugging.cpp` + `bee/nonstd/debugging.h`。本模块目前没有独立测试文件。 - -## 要点 - -- 这是 C/C++ 层的原生断点,不是 Lua 的 `debug.sethook`;在 VS/VSCode 附加进程时会停在 native 调用栈上。 -- `breakpoint()` **不判断**是否有调试器;`breakpoint_if_debugging()` 才是「有调试器才断」的安全版本。 -- `is_debugger_present()`:Windows 用 `IsDebuggerPresent()`,macOS 用 `sysctl` 的 `P_TRACED`,其他平台恒返回 `false`(于是 `breakpoint_if_debugging()` 也恒为 no-op)。 - -`breakpoint()` 的底层实现依编译器而定: - -| 构建环境 | 实现 | -|----------|------| -| 有 ``(C++26 `__cpp_lib_debugging`) | `std::breakpoint()` | -| MSVC(无 ``) | `__debugbreak()` | -| clang(无 ``) | `__builtin_debugtrap()` | -| 其它(如 GCC,无 ``) | 函数体为空,**no-op** | - -在实现了 trap 的构建环境里,没有调试器附加时断点异常会交给系统默认处理器(可能直接终止进程);没有 trap 实现的分支里则什么都不发生。所以生产代码里优先用 `breakpoint_if_debugging()`。 - -## 用法 - -```lua -local debugging = require "bee.debugging" - -if debugging.is_debugger_present() then - -- 调试模式下走额外校验 -end - -debugging.breakpoint_if_debugging() -- 挂调试器则中断,否则什么都不发生 -``` - -## 注意事项 - -- `is_debugger_present()` 也可以用来按环境切换日志级别——比自定义开关更可靠。 -- 想让 Lua 层停下来看调用栈,请用 `debug.sethook` / 调试器;本模块只处理 native 断点。 diff --git a/skills/references/platform/platform.md b/skills/references/platform/platform.md deleted file mode 100644 index b241b6f6..00000000 --- a/skills/references/platform/platform.md +++ /dev/null @@ -1,35 +0,0 @@ -# bee.platform - -平台信息。签名见 `meta/platform.lua`。模块返回的是**普通表**(非类),无需 ``。 - -## 要点 - -- 常用来分支的字段:`os`、`Arch`、`Compiler`、`CRT`、`DEBUG`。 -- 版本号在 `os_version` 里(`{ major, minor, revision }`),另有 `CompilerVersion` / `CRTVersion` 字符串。 - -## 用法 - -```lua -local platform = require "bee.platform" - -local isWindows = platform.os == "windows" -local isMinGW = isWindows and platform.CRT == "libstdc++" - -if platform.DEBUG then ... end -``` - -`test/test.lua` 启动时打印环境信息,是标准用法: - -```lua -local v = platform.os_version -print(("OS: %s %d.%d.%d"):format(platform.os, v.major, v.minor, v.revision)) -print("Arch: ", platform.Arch) -print("Compiler: ", platform.CompilerVersion) -print("CRT: ", platform.CRTVersion) -print("DEBUG: ", platform.DEBUG) -``` - -## 注意事项 - -- 测试里按平台跳过用例用 `lt.skip "module.test_name"`(`test/test_skip.lua`),按**特性**探测用 `supported "symlink"`(`test/supported.lua`,结果会缓存)——能力探测优先于平台判断。 -- `supported "hardlink"` 的判定就是 `platform.os ~= "android"`。 diff --git a/skills/references/platform/windows.md b/skills/references/platform/windows.md deleted file mode 100644 index 47a9ae80..00000000 --- a/skills/references/platform/windows.md +++ /dev/null @@ -1,39 +0,0 @@ -# bee.windows - -Windows 专有工具。签名见 `meta/windows.lua`,行为契约见 `test/test_windows.lua`。 - -**仅 Windows 可用**:其他平台 `require` 会失败,按 `platform.os` 分支或 `pcall` 包裹。 - -## 要点 - -- 编码:`u2a` / `a2u` 在 UTF-8 与 ANSI(GBK) 之间转换,只认 ANSI 的老 API 用得上。 -- 控制台:`isatty(file)` 判断句柄是不是终端;`write_console(file, msg)` 走 `WriteConsoleW`,能正确输出 UTF-16,避开 CRT 编码问题。 -- 文本模式:`filemode(file, "t"|"b")` 切 CRT 的换行转换,二进制模式下 `io.read "a"` 才会拿到原始 `\r\n`。 -- 排查占用:`find_file_holders(filepath)` 通过 NT API 枚举句柄表返回 PID 数组,`process_name(pid)` 把 PID 换成进程名(失败返回空串)。 -- `is_ssd(drive)` 判断驱动器是否 SSD,`"C"` 与 `"C:"` 都接受。 - -## 用法 - -```lua -local windows = require "bee.windows" - -local ansi = windows.u2a "中文" -- GBK 字节串 -local utf8 = windows.a2u(ansi) - -if windows.isatty(io.stdout) then - windows.write_console(io.stdout, "中文\n") -end - -windows.filemode(io.stdin, "b") -- 关掉 CRLF 转换(test_subprocess 里这么用) - -local pids = windows.find_file_holders "d:/build/out.exe" -for _, pid in ipairs(pids) do - print(pid, windows.process_name(pid)) -end -``` - -## 注意事项 - -- `find_file_holders` 需要相应权限,且只列出**当前进程可见**的句柄持有者。 -- 含代理对/生僻字的路径(WTF-8 场景):Lua 层字符串仍是 UTF-8,转换由库内部处理,`test_windows:test_wtf8` 覆盖了用这种文件名 `io.open`。 -- 非 Windows 平台不要 `require` 本模块。 diff --git a/skills/references/process/subprocess.md b/skills/references/process/subprocess.md deleted file mode 100644 index f6bb28db..00000000 --- a/skills/references/process/subprocess.md +++ /dev/null @@ -1,79 +0,0 @@ -# bee.subprocess - -子进程与管道。签名见 `meta/subprocess.lua`,行为契约见 `test/test_subprocess.lua`,测试脚手架见 `test/shell.lua`。 - -## 要点 - -- `spawn(args)` 的 `args[1]` 是程序路径,其后为参数(数组可嵌套,会展平);`spawn` 失败返回 `nil, err`,用 `assert` 包装。 -- 只有请求了对应管道的句柄才存在:`p.stdin` / `p.stdout` / `p.stderr`,都是标准 `file*`。 -- `stdin`/`stdout`/`stderr` 传 `true` 建管道、传 `file*` 直接接文件;`stderr = "stdout"` 表示共享标准输出。 -- `env` 是**覆盖**语义:缺省继承父进程,键值为 `false` 表示删除该变量。 -- `wait()` 之后 `is_running()` 为 `false`;用完记得 `detach()` 收尾。 -- `kill(0)` 只探测存活、不真杀;被 kill 的返回码平台相关(Windows 上是 `0x0F00`)。 -- 切换 `cwd` 会真正改子进程工作目录(`test_cwd` 用 `fs.current_path()` 验证)。 -- `setenv` 改的是**父进程**环境,后续 `spawn` 会继承。 - -## 用法 - -```lua -local subprocess = require "bee.subprocess" - -local p = assert(subprocess.spawn { - "lua", "-e", "io.write(io.read 'a')", -- 后续元素是参数 - cwd = "some/dir", - stdin = true, stdout = true, stderr = "stdout", - env = { BEE_TEST = "ok", OTHER = false }, - suspended = false, detached = false, - console = "new", hideWindow = false, searchPath = false, -- 后三个是 Windows 专有 -}) -local out = p.stdout:read "a" -assert(p:wait() == 0) -assert(p:detach() == true) -- 测试里每个进程用完都 detach -``` - -管道当 stdin(`test_subprocess:test_stdio_1`): - -```lua -local p = assert(subprocess.spawn { "lua", "-e", "io.write(io.read 'a')", stdin = true, stdout = true }) -assert(p:is_running()) -p.stdin:write "ok" -p.stdin:close() -- 关闭后子进程才 EOF -assert(p:wait() == 0) -assert(p.stdout:read(2) == "ok") -assert(p.stdout:read(2) == nil) -- 后续读到 nil -``` - -进程串联(把上一个的 stdout 当 stdin): - -```lua -local p1 = assert(subprocess.spawn { "lua", "-e", "io.write 'ok'", stdout = true }) -local p2 = assert(subprocess.spawn { "lua", "-e", "io.write(io.read 'a')", - stdin = p1.stdout, stdout = true }) -p1:wait(); p2:wait() -assert(p2.stdout:read "a" == "ok") -``` - -批量等待(`test_subprocess:test_select`): - -```lua -while #progs > 0 do - assert(subprocess.select(progs)) -- 等到任一进程结束 - local i = 1 - while i <= #progs do - if progs[i]:is_running() then - i = i + 1 - else - assert(progs[i]:wait() == 0) - progs[i]:detach() - table.remove(progs, i) - end - end -end -``` - -## 注意事项 - -- 用文件重定向时 `p.stdout` 就是传入的那个 `file*`(`assert(p.stdout == f)`),父进程 `close` 后子进程仍能写。 -- `subprocess.peek(file)` 探测管道可读字节数;`subprocess.quotearg(arg)` 处理空格/引号转义;`subprocess.get_id()` 取当前 pid。 -- Windows 下 `windows.filemode(io.stdin, "b")` 可关掉 CRT 的 CRLF 转换。 -- 测试统一用 `shell:runlua(script, options)`(`test/shell.lua`)起带正确 `package.cpath` 的 Lua 子进程,`options` 就是上面的 spawn 表,`options[1]` 或 `"_"` 用于插入额外 argv。