- 신선한 탐사자(fable) 단일 컨텍스트, 지시서 -round10-brief.md §2 레인 A·C 완료, B 부분, D ALL PASS - audit/handtrace-round10-reference-impl/: round7 참조 구현을 현재 계약으로 갱신·재실행 (부수: round7/ref9 _recompute 첫 인자 오류 발견·정정) - base/·인덱스 레이어는 미변경 — 결정은 사용자 배치 회신 뒤 -round10-followup.md로 Co-authored-by: qwreey <me@qwreey.moe> Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
53 lines
2.9 KiB
Text
53 lines
2.9 KiB
Text
--!nocheck
|
|
-- H-147 재검토: 진입 시점에 이미 죽은 핸들에서 Rerun()이 불리면.
|
|
local q = require("./core10")
|
|
local S = q.Source(0)
|
|
local function row(l, e, x) print(("%-52s | %s %s"):format(l, q.effectState(e), x or "")) end
|
|
|
|
print("== 케이스 (1): 직전 cleanup이 self:Unsubscribe()를 부른다 ==")
|
|
local runs1, cleans1 = 0, 0
|
|
local e1; e1 = q.Effect(function(self)
|
|
runs1 += 1
|
|
return function() cleans1 += 1; if cleans1 == 1 then self:Unsubscribe() end end
|
|
end, S)
|
|
e1:Subscribe()
|
|
S:Set(1) -- Rerun: 루프 머리 _consumeCleanup → cleanup이 Unsubscribe → wasAlive=false → fn 실행(!)
|
|
row("Set 후", e1, ("runs=%d (죽은 뒤 fn이 돌았으면 2) cleans=%d"):format(runs1, cleans1))
|
|
S:Set(2)
|
|
row("다음 Set — 버려짐", e1, ("runs=%d"):format(runs1))
|
|
e1:Subscribe() -- 재구독: _installed가 true라 resubscribeTail이 Rerun을 생략(Refresh는 true)
|
|
row("재Subscribe — Refresh true라 Rerun은 돈다", e1, ("runs=%d cleans=%d"):format(runs1, cleans1))
|
|
e1:Unsubscribe()
|
|
row("Unsubscribe — 고아였던 cleanup이 여기서 소진되는가", e1, ("cleans=%d"):format(cleans1))
|
|
|
|
print("\n== 케이스 (2): Unsubscribe 뒤 타이머의 self:Rerun() ==")
|
|
local runs2, cleans2 = 0, 0
|
|
local e2 = q.Effect(function(self) runs2 += 1; return function() cleans2 += 1 end end, S)
|
|
e2:Subscribe(); e2:Unsubscribe()
|
|
row("Unsubscribe 직후", e2, ("runs=%d cleans=%d"):format(runs2, cleans2))
|
|
e2:Rerun() -- 타이머 흉내
|
|
row("죽은 핸들 Rerun() — fn 실행 + cleanup 저장", e2, ("runs=%d cleans=%d"):format(runs2, cleans2))
|
|
collectgarbage() -- 아무도 안 잡으면 cleanup은 영영 안 불린다(사용자가 e2를 잡고 있는 한 남아 있다)
|
|
e2:Subscribe()
|
|
row("재Subscribe — installed=true, Refresh false → Rerun 생략", e2, ("runs=%d cleans=%d"):format(runs2, cleans2))
|
|
e2:Unsubscribe()
|
|
row("Unsubscribe — 죽은 실행의 cleanup이 소진됨", e2, ("cleans=%d (소진됐으면 2)"):format(cleans2))
|
|
|
|
print("\n== 케이스 (3): leaf 파괴 뒤 타이머의 self:Rerun() ==")
|
|
local inst = q.newInst("inst")
|
|
local runs3, cleans3 = 0, 0
|
|
local e3 = q.Effect(function(self) runs3 += 1; return function() cleans3 += 1 end end, S)
|
|
q.bindLifetime(inst, e3)
|
|
q.destroyInst(inst)
|
|
row("destroy 직후", e3, ("runs=%d cleans=%d"):format(runs3, cleans3))
|
|
e3:Rerun()
|
|
row("죽은 leaf 핸들 Rerun()", e3, ("runs=%d cleans=%d BindData(gcconn dead)=%s"):format(runs3, cleans3, tostring(q.BindData[e3] ~= nil)))
|
|
local inst2 = q.newInst("inst2")
|
|
q.bindLifetime(inst2, e3)
|
|
row("재bind — installed=true라 Refresh 없으면 Rerun 생략", e3, ("runs=%d cleans=%d"):format(runs3, cleans3))
|
|
q.destroyInst(inst2)
|
|
row("inst2 destroy — 그 cleanup 소진", e3, ("cleans=%d"):format(cleans3))
|
|
|
|
print("\n== 케이스 (0) 대조군: 생성자 최초 Rerun은 죽은 게 아니라 '산 적 없음' ==")
|
|
local e0 = q.Effect(function() return function() end end)
|
|
row("생성 직후 (installed=true여야 — H-143 회귀 대조)", e0)
|