ActionMapper: bound serialization breadth, move SQLite write off the action thread #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally filed by @coilysiren on 2026-05-19T02:33:38Z - https://github.com/coilysiren/eco-replay/issues/8
Problem - With eco-replay installed, a single player click on the live server (coilysiren/infrastructure#183) allocated enough memory to freeze kai-server. Idle play was fine. Two structural bugs in the recorder make this possible.
Bug 1:
ActionMapper.ToRowserialization is bounded in depth but not breadth.BodySettingssetsMaxDepth = 2, which only constrains nesting.ShallowResolvershort-circuits properties whose declaredPropertyType.FullNameis exactly one ofUser/ItemStack/WorldObject/Deed. That misses:object,IAlias,IOwned, or any interface / base class. Runtime type doesn't match theFullNameexact-match, so Newtonsoft enumerates.IEnumerable<T>,IList<T>,IDictionary<,>,HashSet<T>of complex Eco entities (inventories, chunks, world-object lists, property graphs). MaxDepth=2 lets each element be fully expanded one layer.Errorhandler swallows exceptions but doesn't stop enumeration. A 100k-element collection of complex objects allocates GB of intermediate strings before anything errors.Click actions (interact / place / trade / claim) carry exactly these wide references. Idle actions don't.
Fix sketch:
ShallowResolver, walkprop.PropertyTypeand its base types + implemented interfaces againstSkipTypes, not just exactFullNamematch. Better: maintain an allow-list of safe primitive-ish property types and skip everything else.IEnumerable(other thanstringand primitive collections) at any depth as a count summary:\"<n items>\", not the full enumeration. A customJsonConverteron the resolver is cleaner than fightingDefaultContractResolver.body_jsonsize after the fact: if> 16 KBafter serialization, replace with{\"truncated\": true, \"action_type\": ...}.Bug 2: SQLite insert runs synchronously on Eco's action thread.
EventStore.InserttakeswriteLockand doesExecuteNonQueryfrom insideActionPerformed. That's on whatever thread Eco firesActionUtil. Under load this:Fix sketch:
Channel<EventRow>(bounded, DropOldest)betweenActionPerformedand a single backgroundTaskthat drains the channel and batches inserts.ActionPerformedbecomes: build row,TryWrite, return. Never blocks the game thread.Out of scope - the host-level memory cage already landed in coilysiren/infrastructure#183. That stops the host from freezing again, but the mod is still uninstalled until this issue closes.
Verify -
GameActionwith a 10k-itemIEnumerableproperty:ToRowreturns in bounded time,body_jsonsize is bounded.Storage/EcoReplay.dbgrows normally.MemoryHigh=10Gcgroup soft-cap does not get touched during normal play.