Skip to content

fix: FakeFish pitfalls — mysql callbacks, JIT const, json arrays, pool lease, net send - #20

Merged
esrrhs merged 8 commits into
masterfrom
fix/pitfalls-callbacks-const-json-pool
Oct 2, 2026
Merged

esrrhs merged 8 commits into
masterfrom
fix/pitfalls-callbacks-const-json-pool

Conversation

@esrrhs

@esrrhs esrrhs commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

背景

FakeFish(fakelua 的真实项目方)在实战中发现 8 个问题,文档见 fakefish 仓库 docs/fakelua-pitfalls.md。本 PR 逐一修复。

修复清单

P0-1 conn:query 回调与绑定参数跨帧持久化(重大重构)

  • fakelua 内存模型在跨帧/跨 Tick 时会对临时 arena 执行 Reset。为彻底根除跨帧闭包悬挂指针(UAF)风险,同时保持跨引擎一致性:
    • 异步回调契约确立为具名函数名字符串(如 "Module.on_result"),可选携带绑定参数(bound args)。
    • 绑定参数在发起时由持久化序列化器(WireEncodeCallbackArgs)封存入堆内存;回调派发时精确反序列化还原,不受顶层 Call 之间 arena 重置影响。
    • 增加执行引擎追踪(current_jit_ 与 JitContextScope),回调派发自动路由回发起调用时的对应引擎(TCC/GCC/解释器),保证文件级状态可见性一致。
    • 非法回调类型明确抛出 bad argument,不再静默丢弃。

P0-2 查询失败时部分错误路径完全没有回调

  • Query/StmtPrepare/StmtExecute 的所有提前 return 路径统一设置错误并派发回调。
  • 连接进入错误终态或主动 Close() 时,排队但未执行的 query 统一触发错误回调清空队列(DrainQueryQueue())。
  • 契约:query 一旦成功入队被调用,回调恰好被调用一次。

P1-3 模块级常量(const)赋值语义修复与严谨报错

  • 明确语言层 const 语义:文件级数值常量在编译期严格禁止二次赋值。
  • 当检测到对已声明常量的再赋值时,在语法/语义检查期抛出精确定位行号的编译异常(constant reassignment is not allowed),拒绝静默生成不可预期的 C 代码。

P1-4 连接池租约语义与异步查询安全归还

  • 新增 pool:with(fn) 租约 API:
    • 从连接池取出连接包装为原生对象传给 fn(conn),采用 RAII LeaseGuard 管理生命周期。
    • 异步安全保证:若 fn 内部发起了异步 query,在 fn 退出时若仍有在途/排队任务,延迟归还并标记 __mysql_auto_release__;待本连接所有在途 query 派发完毕后自动归还回池。
    • 若同步抛错或未发起异步任务,连接立即归还,防止泄漏。
  • 同连接并发 query 改为有序排队机制,避免连接未就绪静默报错或响应状态覆盖。

P1-5 json.encode 空 table 产出 [] 与 encode_array 优化

  • 纯数组启发式:空 table 编码为 [];非空仍要求 1..N 连续整数键。
  • 新增 json.encode_array() 严格通道:非数组形 table 直接报错;重构提取公共校验逻辑,避免双重遍历。
  • 清理无用遗留结构字段(spec_bytes, spec_cvars)与无用 arena 函数(HeapAllocator::Contains)。

P1-6 回调上下文多重静默限制

  • 派发上下文(IoContext::InDispatch())中的 send 改为入队、由本轮 tick 派发完后统一泵出(事件驱动改轮询驱动),消除平台相关的静默丢弃;发送失败记录 WARN 日志。
  • README 补充"回调上下文允许/禁止操作矩阵"。

P2-7 MySQL 结果值全为字符串、格式未文档化

  • 行值按列类型转换:整数列(TINY/SHORT/LONG/LONGLONG/INT24/YEAR)→ 整数,浮点/小数列 → 浮点,其余保持 string,NULL 为 nil;解析失败回退字符串。
  • SELECT / DML 结果表布局、行值类型规则写入 README(zh/en)。

P2-8 tick 泵序与嵌套回调延迟规范

  • README 补充:泵序固定 timer → net → http → mysql → redis;回调里发起的 IO 最早下一轮 tick 可见;mysql 同连接 query 排队规则。

测试验证

  • 全量单测运行通过,新增覆盖:
    • test_mysql.pool_with_lease_api
    • test_mysql.pool_with_fn_throw_returns_connection
    • test_mysql.pool_with_async_query_auto_release
    • test_mysql.bad_callback_type_throws
    • test_mysql.closure_callback_survives_frame_reset
    • test_json.encode_empty_table_as_array
    • test_json.encode_array_strict
    • infer.test_global_const* / exception.const_reassign
  • 消除废弃代码与未覆盖死分支,提高 patch 测试覆盖率。

兼容性说明

  • json.encode({}) 行为从 "{}" 变为 "[]";需要空对象的场景请使用带字符串键的表。
  • mysql 异步回调采用具名函数 + 持久化参数机制,彻底杜绝悬挂闭包崩溃问题。
  • mysql 行值按列类型转换为 number / string;既有 tonumber(v) 原样返回,平滑兼容。

esrrhs and others added 4 commits September 30, 2026 08:03
…ease, net send

Fixes the 8 issues found by the FakeFish project (fakelua-pitfalls):

- P0-1: mysql callbacks now accept inline closures (VarClosure*) in
  addition to global function names; invalid callback types throw a
  loud "bad argument" instead of being silently dropped by
  CVarToString("").
- P0-2: all early-return paths in Query/StmtPrepare/StmtExecute now
  fire the callback with an error. Contract: every conn:query() call
  results in exactly one callback.
- P1-3: file-level numeric locals with reassignment points are no
  longer emitted as C const (which broke JIT compilation with
  "cannot assign to variable with const-qualified type"). Never-
  reassigned file constants keep the const optimization.
- P1-4: in-flight query on the same connection is queued instead of
  erroring; new pool:with(fn) lease API auto-returns the connection
  even when fn throws.
- P1-5: json.encode({}) now produces [] (pure-array heuristic);
  new json.encode_array() for strict array-shaped encoding.
- P1-6: sends issued inside dispatch callbacks are queued and pumped
  by the tick after dispatch completes; send failures log WARN.
- P2-7: result rows convert values by column type (int/float columns
  return numbers instead of strings); result table layout documented.
- P2-8: tick pump order and callback-context rules documented in
  native READMEs (zh/en), incl. the allowed/forbidden operation matrix.

exception.const_reassign test updated: it asserted the P1-3 bug
(compile failure); now expects successful compilation per the new
semantics. New regression tests for all the above.
Runtime values sit on the temporary arena, which State::Reset() clears at each top-level Call, so a raw VarClosure* stored for a later tick was dangling. Callbacks are now copied onto the const arena, which lives with the State. Also document the json.encode({}) break, DECIMAL precision loss, and InDispatch, and cover pool:with throw plus numeric column conversion.

Co-authored-by: Cursor <cursoragent@cursor.com>
A file-level numeric literal stays static const, and assigning it from a user function is a compile error with a source location. local x = func() is unchanged: the declaration is lowered to nil and __fakelua_init assigns it once.

Co-authored-by: Cursor <cursoragent@cursor.com>
… close-error contract

PR20 changed mysql result rows to convert by column type (integer columns
return numbers) and changed conn:query on a closed connection to deliver
the error via callback instead of throwing. The C++ unit tests and README
were updated, but seven legacy Lua integration scripts still asserted the
old string values / pcall behavior, failing the Build pipeline (debug,
release, cov).

- stmt/multi_result/pool/query_error/datatypes/stmt_params: compare integer
  columns against numbers instead of strings
- lifecycle: expect the closed-connection query error through the result
  callback after runtime.tick(), not a synchronous pcall error
@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.32653% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.93%. Comparing base (d0db1dc) to head (2232a7b).

Files with missing lines Patch % Lines
src/native/serialize/wire_codec.cpp 89.30% 23 Missing ⚠️
src/native/mysql/mysql_connection.cpp 84.55% 19 Missing ⚠️
src/native/net/native_net.cpp 82.05% 7 Missing ⚠️
src/native/mysql/native_mysql_pool.cpp 98.23% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #20      +/-   ##
==========================================
+ Coverage   87.80%   87.93%   +0.12%     
==========================================
  Files         119      122       +3     
  Lines       24042    24363     +321     
==========================================
+ Hits        21110    21423     +313     
- Misses       2932     2940       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

esrrhs added 4 commits October 1, 2026 18:52
…alized bound args

Inline closures cannot be held safely across ticks: runtime values live on
the temporary arena that every top-level Call resets, and pinning closure
graphs into the const arena made that arena grow for the whole State
lifetime (no GC).

Async mysql callbacks (connect/query/stmt_prepare/stmt_execute) now accept
only an in-package global function name, optionally followed by pure-data
bound arguments:

  conn:query(sql, "on_result", tag, ctx_table)
  -> on_result(conn, err, result, tag, ctx_table)

Bound args are encoded at registration into a self-owned byte blob using
the existing serialize wire format (new internal wire_codec module shared
with serialize.encode/decode; the Lua wire format is unchanged), survive
any number of arena Resets, and are decoded in the dispatch frame and
appended after the fixed callback arguments. The blob is released when
the query completes or the connection is destroyed, so memory tracks
in-flight queries instead of growing the const arena.

Non-serializable values (closures, native objects) anywhere in bound args
raise bad argument instead of being silently dropped. pool:with still
takes an inline closure since it invokes fn synchronously in-frame.

- remove arena_pin.{h,cpp} (const-arena closure deep copy)
- extract wire codec from native_serialize into reusable wire_codec
- update contract tests/README; cross-frame test driven on TCC only
  (named callbacks dispatch to the TCC artifact, file-level locals are
  per-engine statics and not shared across CallAll's engines)
… pinned TCC

Named mysql callbacks were always resolved through the TCC artifact, so a
callback registered from GCC/interpreter code executed TCC machine code.
With CallAll exercising all engines on one State, per-engine file-level
statics written by callbacks were not visible to the registering engine.

Track the currently executing engine on the State and propagate it through
every Lua->C++ invocation boundary:
- State holds current_jit_ with a C++ JitContextScope RAII and C-compatible
  getter/setter (TCC compiles generated code as C, cannot use C++ scopes)
- CallByNameImpl (native function dispatch), DispatchCall (Lua function /
  closure dispatch from C++) and the FlCallClosure case ladder (direct
  closure calls from JIT C code, incl. the native object method bridge)
  push/restore the caller engine via FakeluaJitContextPush/Pop
- ResultCallback records the engine at registration; InvokeCallback
  resolves and calls the function in that same engine (fallback to other
  engines only if its artifact is missing)
- pool:with invokes its synchronous fn closure with the current engine

This restores the same per-engine closure semantics as inline closures;
the cross-frame contract test runs under CallAll again. All 1551 tests
pass (excluding local-redis test_redis).
@esrrhs
esrrhs merged commit 4e396e8 into master Oct 2, 2026
9 checks passed
@esrrhs
esrrhs deleted the fix/pitfalls-callbacks-const-json-pool branch October 2, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants