fix(drift-trio): pin fiber slot across asyncify parks (#10b layer 1)
Symbolized (HOIST_KEEP_NAMES=1): the trap is on the asyncify REWIND re-entering the fiber — stack-local COROUTINE+body were destroyed when Call() returned early on a park, so the rewind called through freed objects (latent UB in the ORIGINAL fire-and-forget runOnFiber too). Heap-pinned FiberSlot + explicit done flag + fiber-tail re-drain. Layer 2 (rewind interplay) still open — fuzz stays fixme'd; pageerror stacks now captured in fuzz artifacts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5cAM9M6q34n5X4dbrfVvi
This commit is contained in:
parent
3fc90e8fe2
commit
22cd32b7b2
2 changed files with 76 additions and 11 deletions
|
|
@ -438,7 +438,9 @@ for (const [cfg, label, mkActions, seedReg] of [
|
||||||
const t = m.text();
|
const t = m.text();
|
||||||
if (/collab|drift|parse|error|abort/i.test(t)) consoles[tabLabel]!.push(t.slice(0, 400));
|
if (/collab|drift|parse|error|abort/i.test(t)) consoles[tabLabel]!.push(t.slice(0, 400));
|
||||||
});
|
});
|
||||||
page.on("pageerror", (e) => consoles[tabLabel]!.push(`PAGEERROR ${e.message}`));
|
page.on("pageerror", (e) =>
|
||||||
|
consoles[tabLabel]!.push(`PAGEERROR ${e.message}\nSTACK ${(e.stack ?? "").slice(0, 4000)}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const actions = mkActions();
|
const actions = mkActions();
|
||||||
const reg = seedReg();
|
const reg = seedReg();
|
||||||
|
|
|
||||||
|
|
@ -61,32 +61,95 @@ inline bool& fiberBusy()
|
||||||
return busy;
|
return busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The in-flight body. HEAP-allocated and pinned for the body's whole life:
|
||||||
|
* when a body PARKS (asyncify suspension inside commit.Push), COROUTINE::Call
|
||||||
|
* RETURNS EARLY — the later asyncify rewind re-enters the fiber through the
|
||||||
|
* SAME callable at the SAME addresses (dynCall_vi → fcontext_entry →
|
||||||
|
* callerStub → the wrapper). Stack-local cor/body (the original runOnFiber
|
||||||
|
* AND the first serialized version) were destroyed on that early return, so
|
||||||
|
* the rewind called through freed objects — "table index is out of bounds"
|
||||||
|
* at rewind, memory corruption downstream (finding #10b's symbolized stack).
|
||||||
|
* `done` is the ONLY completion signal; Call() returning is not. */
|
||||||
|
struct FiberSlot
|
||||||
|
{
|
||||||
|
COROUTINE<int, int>* cor = nullptr;
|
||||||
|
std::function<void()>* body = nullptr;
|
||||||
|
bool done = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline FiberSlot& activeFiberSlot()
|
||||||
|
{
|
||||||
|
static FiberSlot s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline wxEvtHandler*& fiberHandler()
|
||||||
|
{
|
||||||
|
static wxEvtHandler* h = nullptr;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void drainFibers();
|
||||||
|
|
||||||
|
inline void reapFiber()
|
||||||
|
{
|
||||||
|
FiberSlot& slot = activeFiberSlot();
|
||||||
|
delete slot.cor;
|
||||||
|
delete slot.body;
|
||||||
|
slot.cor = nullptr;
|
||||||
|
slot.body = nullptr;
|
||||||
|
slot.done = false;
|
||||||
|
fiberBusy() = false;
|
||||||
|
}
|
||||||
|
|
||||||
inline void drainFibers()
|
inline void drainFibers()
|
||||||
{
|
{
|
||||||
|
FiberSlot& slot = activeFiberSlot();
|
||||||
|
|
||||||
if( fiberBusy() )
|
if( fiberBusy() )
|
||||||
return; // the running drain's while-loop covers the rest
|
{
|
||||||
|
if( !slot.done )
|
||||||
|
return; // parked body still in flight — its tail re-drains
|
||||||
|
|
||||||
|
reapFiber(); // completed via rewind since the last drain
|
||||||
|
}
|
||||||
|
|
||||||
auto& q = fiberQueue();
|
auto& q = fiberQueue();
|
||||||
|
|
||||||
while( !q.empty() )
|
while( !q.empty() )
|
||||||
{
|
{
|
||||||
fiberBusy() = true;
|
fiberBusy() = true;
|
||||||
|
slot.done = false;
|
||||||
std::function<void()> body = std::move( q.front() );
|
slot.body = new std::function<void()>( std::move( q.front() ) );
|
||||||
q.pop_front();
|
q.pop_front();
|
||||||
|
|
||||||
COROUTINE<int, int> cor( [&body]( int ) -> int
|
slot.cor = new COROUTINE<int, int>( []( int ) -> int
|
||||||
{
|
{
|
||||||
body();
|
FiberSlot& sl = activeFiberSlot();
|
||||||
return 0;
|
( *sl.body )();
|
||||||
} );
|
sl.done = true;
|
||||||
cor.Call( 0 );
|
|
||||||
fiberBusy() = false;
|
// If we parked, no drain is pending by the time the rewind
|
||||||
|
// completes — schedule the reap + next body from the fiber tail
|
||||||
|
// (CallAfter only queues; safe here).
|
||||||
|
if( wxEvtHandler* h = fiberHandler() )
|
||||||
|
h->CallAfter( []() { drainFibers(); } );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} );
|
||||||
|
|
||||||
|
slot.cor->Call( 0 );
|
||||||
|
|
||||||
|
if( !slot.done )
|
||||||
|
return; // parked — cor/body stay pinned for the rewind
|
||||||
|
|
||||||
|
reapFiber();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void runOnFiber( wxEvtHandler* aHandler, std::function<void()> aBody )
|
inline void runOnFiber( wxEvtHandler* aHandler, std::function<void()> aBody )
|
||||||
{
|
{
|
||||||
|
fiberHandler() = aHandler;
|
||||||
fiberQueue().push_back( std::move( aBody ) );
|
fiberQueue().push_back( std::move( aBody ) );
|
||||||
aHandler->CallAfter( []() { drainFibers(); } );
|
aHandler->CallAfter( []() { drainFibers(); } );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue