Skip to content

Fix/lmdb reopen dbi#351

Merged
NanCunChild merged 7 commits into
ferrumc-rs:masterfrom
NanCunChild:fix/lmdb-reopen-dbi
Jun 6, 2026
Merged

Fix/lmdb reopen dbi#351
NanCunChild merged 7 commits into
ferrumc-rs:masterfrom
NanCunChild:fix/lmdb-reopen-dbi

Conversation

@NanCunChild

Copy link
Copy Markdown
Collaborator

fix: #a05ebe42 can not load any existed worlds

NanCunChild and others added 7 commits June 6, 2026 01:22
…luid ticks

Two allocation/refcount hot spots in the fluid tick loop, both pure
performance with no behavioural change (the serial/parallel parity test and
the fluid integration suite pass unchanged):

- `seed_fluid_tick` built a fresh `WorldBlockView` — cloning the `GlobalState`
  handle — on every call. Applying a batch of changes wakes the six fluid
  neighbours of every changed block, so a cascade did roughly seven `Arc`
  clones per changed cell. Split out `seed_fluid_tick_with_view` and build one
  view per batch in `apply_changes` and `seed_on_block_break`; the view reads
  the live chunk cache, so reusing it across the intervening writes is
  equivalent to rebuilding it each time. The public `seed_fluid_tick` stays as
  a thin wrapper for the placement/break callers.

- `ChunkTickQueue::drain_due` / `drain_due_capped` allocated a fresh full-size
  replacement vector on every call — for every chunk with pending ticks, every
  game tick, even when nothing was due. Replaced with in-place `retain`
  compaction (order preserved, so the capped path still defers the overflow).
perf: cut per-seed state-handle clones and per-drain allocations in f…
Registry data is sourced from JSON, which cannot express NBT's distinct
numeric tags. The generic `craftflow_nbt::to_writer(&serde_json::Value)` path
serialised every JSON integer as a `Long` and every real as a `Double`. The
vanilla Java client coerces numeric tags leniently and tolerates this, but a
strict client deserialises the registry into typed structs and rejects any
field whose tag is not exactly what the schema expects — so e.g.
`dimension_type.height` (a Long here, an Int in the codec) made such clients
fail to parse the dimension registry and disconnect during login.

Encode the `dimension_type` registry through a small schema-aware converter:
integers default to `Int` (widening to `Long` only when they do not fit i32),
booleans to `Byte`, reals to `Double`, with per-field overrides for the three
fields whose vanilla tag differs — `ambient_light` (Float), `coordinate_scale`
(Double), and `fixed_time` (Long). All other registries keep the byte-for-byte
output of the previous path until they need a schema of their own.

Verified end to end: offline-mode azalea 0.14 (Minecraft 1.21.8) bots that
previously panicked on `MismatchedFieldType("DimensionTypeElement::height")`
now parse the registry and fully join, reaching the Play state and loading in.
A dev-only load-testing tool that spawns a swarm of offline-mode azalea bots
to connect to a running FerrumC server and (optionally) wander, reproducing the
chunk-loading, movement, and broadcast load of many simultaneous players for
measuring server tick performance under real concurrency.

- Standalone crate (its own `[workspace]`, listed under `exclude` in the root
  Cargo.toml) so azalea's large dependency tree never compiles as part of the
  main build, CI, or release.
- Pinned to `azalea = "=0.14.0"` (Minecraft 1.21.8 / protocol 772) to match
  FerrumC's protocol. azalea 0.14 transitively depends on a set of pre-release
  RustCrypto crates whose newer published versions have incompatible APIs; the
  committed Cargo.lock pins the whole stack back to the working versions
  (documented in the README, with restore commands).
- Configurable bot count, join stagger, idle vs wandering, reconnect-on-drop,
  and a periodic connected/peak/joins/disconnects metrics line.

Transport and login are verified end to end against a local server (bots reach
the server and begin the login sequence). Note: a strict client cannot yet
fully join because FerrumC's registry NBT encodes some `dimension_type` fields
with the wrong tag width (e.g. `height` as Long instead of Int; `ambient_light`
/`coordinate_scale` as int instead of float/double). The vanilla Java client
coerces these leniently; azalea deserializes into typed structs and rejects
them. Making that registry data wire-conformant is a separate server-side fix.
fix: emit schema-correct NBT tags for the dimension_type registry
test: add azalea-based stress-test bot (tools/stress-bot)
Reopening an existing world failed on the first database access after a
restart with `IO error: Invalid argument (os error 22)`, leaving the
server unable to load any persisted save.

`open_table` opened a named-database handle inside a read transaction and
then dropped (aborted) that transaction before caching the handle. LMDB
ties a freshly opened handle to the transaction that opened it: the handle
stays private until the transaction is *committed*, and is closed
automatically if the transaction is aborted. The cached handle was
therefore already closed, and reusing it in a later transaction failed
with `EINVAL`.

The sibling `open_or_create_table` never hit this because it opens inside a
write transaction that it commits, which promotes the handle into the
shared environment. Newly generated worlds also worked for that reason
(tables are first created through that path), which is why the failure only
surfaced on the second run.

Open the handle in a committed write transaction in `open_table` as well so
it remains valid for the lifetime of the environment. The cost is paid once
per table on first access.

Adds `test_reopen_reads_existing_table`, which reinitialises a backend over
an existing path and reads a previously written table — the reopen path the
existing single-instance tests never exercised.
@NanCunChild NanCunChild merged commit f0f41f3 into ferrumc-rs:master Jun 6, 2026
2 checks passed
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.

1 participant