Saturday, August 15, 2026

FBSimCity v0.9.0: the machine room — the real Firebird engine inside the explorable city

FBSimCity, the explorable isometric city of Firebird internals, is at v0.9.0 — and this one adds the thing the project has been circling since it started. The city has always carried the same caveat: it is a model, it parses no SQL, and no Firebird code runs in your browser. The new machine room is the other thing.

The machine room

The machine room runs the actual Firebird engine: version 6.0.0 of the embedded engine, compiled to WebAssembly by Electric Firebird the way PGlite does it for PostgreSQL, in a worker on the page. No server, nothing uploaded, and the database kept in IndexedDB so it survives a reload. On top of it sits a SQL workbench and six guided sequences, each one ending in a number you can hold against something the city draws:

  • SELECT CURRENT_TRANSACTION FROM RDB$DATABASE, twice — watch the id advance.
  • OIT, OAT and Next straight out of MON$DATABASE — the same three markers the city draws over the transaction yard.
  • Two UPDATEs to one row, then MON$RECORD_STATS back-version reads and purges — multi-generational architecture, counted by the engine.
  • RDB$RELATIONS — the catalogue is ordinary tables.
  • MON$IO_STATS fetches against reads — exactly what the cache plaza draws.
  • The MON$DATABASE settings list — no log setting in it, because there is no log.

Every verdict is computed from what the engine actually returned, so a sequence can tell you the answer was not what was expected. The page also states what the runtime cannot show: one attachment means there is no second session to queue behind, so no lock wait to watch; there is no replica; and gbak and nbackup are separate programs against a file that does not exist there.

A correction to v0.8.0

v0.8.0 shipped a page saying this embed was impossible, and it measured an iframe reporting not cross-origin isolated to prove it. The reasoning was right up to the last step. The engine is built with threads, so it needs SharedArrayBuffer, which browsers hand only to a cross-origin isolated page, and GitHub Pages will not send the COOP/COEP headers that grant isolation.

What I missed is that a service worker can synthesise those headers for any page that ships one — including this project's own — and that both projects publish to mariuz.github.io, so the engine's assets are same-origin and need no CORP header under require-corp. The old measurement was real. The iframe was not isolated because the page doing the framing was not isolated either, which was the part I never tested. The engine page now records the mistake rather than quietly dropping it.

Also in v0.9.0

  • The city used to load cold. Every cache slot empty, one version per tower, and the markers sitting exactly where they were initialised. It now warms 25 model seconds before the first frame. A database that has never run is not a neutral starting point — it is a state you will never meet.
  • Six touch and camera defects, found after reading how PGSimCity fixed the equivalent: pinch zoomed about the canvas origin instead of the fingers, two-finger pan was discarded entirely, a third finger landing jumped the city, touchcancel was unhandled, a pinch fought the camera fly-to, and keyboard +/ drifted the same way.
  • The readouts are now checked against the model behind them, and the throughput figure against independently counted completions. A gauge that wanders will happily paint a healthy city red.

And v0.8.0, for anyone who missed it

Keyboard navigation through every district — Tab and Shift+Tab walk them in pipeline order and announce each one through an aria-live region, which the city needed because it is a canvas and everything in it was mouse-only. Plus a fuzzer over 324 knob combinations, a soak, and enforced documentation coverage.

287 assertions, all passing. The suite runs in the browser with no framework and no build step: mariuz.github.io/FBSimCity/test/

City: mariuz.github.io/FBSimCity
Machine room: mariuz.github.io/FBSimCity/machine
Release notes: v0.9.0
Source: github.com/mariuz/FBSimCity (MIT, plain HTML/JS, no build step)

It remains a model for intuition, not an emulator. What is real, merely scaled, or a plausible stand-in is written down in the knob audit, along with the places the model is kinder than Firebird and the places it is harsher. Corrections are very welcome and do get acted on — the whole replication model was rewritten in v0.6.1 after Dmitry Sibiryakov pointed out on firebird-general that a synchronous replica which dies does not hang commits; it stops replicating and lets them through.

FBSimCity is an independent educational project, not affiliated with or endorsed by the Firebird Project. Firebird® is a registered trademark of the Firebird Foundation Incorporated.

Sunday, August 09, 2026

Electric Firebird: Firebird Database Engine Compiled to WebAssembly, With a Live Demo

Electric Firebird (mariuz/electric-firebird on GitHub) compiles the Firebird database engine to WebAssembly, following the same approach PGlite took for PostgreSQL. The goal is to let Firebird run both server-side (Node.js) and fully client-side in the browser, with no server round-trip required for queries.

Highlights:

Two backends: a native Node.js implementation (via the Firebird Embedded Server) and a WASM build for browsers

Browser version persists data to IndexedDB, surviving page reloads

Parameterized queries, transactions, typed values, and cross-tab safety are supported

Apache-2.0 license

Requires Node.js 20+ and a Firebird client library to build; WASM compilation uses an Emscripten SDK

Live in-browser demo (SQL runs entirely client-side, nothing is uploaded): https://mariuz.github.io/electric-firebird/

Still under active development — DDL/DML execution and transactions are confirmed working in both Node and Chromium — but feedback, testing, and contributions are very welcome.

Tuesday, August 04, 2026

Firebird Adds an Optional Static fbclient — and Solves the Symbol-Collision Problem Properly

Firebird's client library, libfbclient / fbclient.dll, has always been distributed exclusively as a shared library. PR #9104 by Adriano dos Santos Fernandes, merged into the Firebird source tree on July 24, 2026, changes that: it introduces an optional, non-default static archive (libfbclient.a on POSIX, fbclient_static.lib on Windows) for applications that need to link the client library statically instead of loading it as a shared object/DLL.

Why static linking wasn't offered before

The blocker wasn't packaging — it was memory management. Firebird overrides the global C++ operators operator new, operator new[], operator delete, and operator delete[] (in src/common/classes/alloc.cpp) so that any bare, non-pool allocation inside Firebird is routed through its own internal memory pool.

In the shared library, this is harmless: a POSIX version script (and a Windows .def file) keeps those overridden symbols hidden, so an application linking against libfbclient.so or fbclient.dll keeps its own global allocator untouched.

A static archive has no such boundary. Its object files get merged directly into the host application at link time, so the linker would silently resolve the host's own bare new/delete calls to Firebird's internal definitions — hijacking the host application's memory allocation without any warning. That's a nasty, hard-to-diagnose class of bug, and it's exactly why a static build was never shipped.

The fix: rename every internal symbol at the archive level

Rather than hand-maintain a list of symbols to hide, the PR takes a systematic approach: every internal global symbol not part of Firebird's public API gets renamed with a __fbclient_ prefix as a post-processing step on the compiled archive. This covers the global operators, decNumber symbols, C++ mangled names, vtables, typeinfo, guard variables — everything — generated automatically from the existing export list rather than curated by hand.

On POSIX (Linux and macOS), this happens in two steps:

  1. Archive slimmingld -r with -u flags seeded from every symbol in builds/posix/firebird.vers pulls in only the archive members actually reachable from the public API, using cross-reference output (--cref on ELF, -map on Darwin) to identify and repack just those members.
  2. Symbol renamingobjcopy --redefine-syms (GNU objcopy on Linux, llvm-objcopy on macOS) renames every non-API global symbol to a __fbclient_-prefixed name, using a rename map generated automatically via nm --defined-only -g.

Both platforms share a single helper script, static_client.sh (autoconf-generated from builds/posix/static_client.sh.in), differing only in which cross-reference flag ld uses.

On Windows, the equivalent pipeline runs via a new fix_fbclient_static.bat: it parses builds/win32/defs/firebird.def for the public API, runs llvm-nm on every object file to collect defined symbols, builds the same kind of rename map (preserving __stdcall @n decoration where relevant), and applies it with llvm-objcopy --redefine-syms.

Regular pool-based allocation via FB_NEW / FB_NEW_POOL — the pattern used throughout the Firebird codebase — was never affected by any of this, since it never touched the global operators in the first place.

A side effect: no DllMain on Windows

A statically linked fbclient has no separate DLL module, so DllMain (previously in src/jrd/os/win32/ibinitdll.cpp) never runs for it. Most of what DllMain did turns out to degrade safely without it — config-root lookup falls back to the host executable's path, and loader-lock hazard checks simply don't apply to a statically linked EXE.

The one real gap was per-thread cleanup, which used to run only from DllMain's DLL_THREAD_DETACH notification. The PR closes it with a new ThreadCleanup class (src/common/classes/ThreadCleanup.h/.cpp) that uses Fiber-Local Storage (FlsAlloc / FlsSetValue / FlsFree) on Windows — mirroring the destructor semantics pthread_key_create already gives the POSIX build for free, and working identically whether the code ends up in a DLL or linked directly into the host binary.

Building it

POSIX:

make -C temp/debug TARGET=Debug client_static

produces <firebird>/lib/libfbclient.a from the same object set as the shared library (yValve + remote client + common). Most third-party dependencies (tommath, tomcrypt) still need to be linked separately by the consuming application; decNumber/libdecFloat is the one exception — it's small and vendored in-tree, so it's merged directly into the archive.

Windows:

builds\win32\make_all.bat CLIENT_ONLY=STATIC

builds yvalve as fbclient_static.lib under new DebugStatic / ReleaseStatic configurations, then runs the symbol-fixup script automatically.

Verification is straightforward with nm/llvm-nm: the public isc_* API stays global, while global operators (_Znwm, _ZdlPv, etc.) and internal symbols like decNumberFromString disappear from their original names entirely — replaced by __fbclient_-prefixed equivalents.

What's in the diff

25 files changed, +2168/-268 lines, across three commits:

  • .github/workflows/static-build.yml — new manual CI workflows to build and validate the static client on Linux, macOS, and Windows.
  • builds/posix/static_client.sh.in, builds/win32/fix_fbclient_static.bat, builds/win32/compile_static.bat — the new post-processing pipeline.
  • builds/win32/msvc15/*.vcxproj*, *.props — new Debug/Release static configurations wired into the Visual Studio solution.
  • src/common/classes/ThreadCleanup.h/.cpp — the new FLS-based thread cleanup, replacing the old DLL_THREAD_DETACH path.
  • src/yvalve/MasterImplementation.cpp, utl.cpp, src/common/os/win32/mod_loader.cpp, src/jrd/os/win32/ibinitdll.cpp — call sites updated for the DLL-less code path.
  • doc/README.StaticClient.md — new, thorough documentation covering the rationale, build steps, and verification commands for both platforms.

For any project embedding Firebird's client library directly into a host binary, this closes a real gap — and it does it without punting the symbol-collision risk onto the integrator.

Source: FirebirdSQL/firebird PR #9104

Saturday, August 01, 2026

FBSimCity v0.6.0: the replication district — journal segments, commit order, and a synchronous replica that dies

Correction (added 9 August 2026): the section below headed “A synchronous replica that dies hangs commits” is wrong. Dmitry Sibiryakov pointed this out on firebird-general, and the source confirms it. In src/jrd/replication/Publisher.cpp, checkStatus() is called with canThrow = false on the commit path, so it cannot throw; disable_on_error (default true) instead clears the replicating flags, disposes the replicator and logs STOP_ERROR. The commit succeeds and replication tears itself down.

The reason it can do that without misleading anyone is the part I had missed: Firebird’s synchronous replication is not two-phase commit, so there was never a durability guarantee to protect. The real failure mode is arguably worse than the hang I described — replication stops, commits keep succeeding, nobody is told, and the replica quietly rots until somebody notices. Fixed in v0.6.1. The original text is left below unchanged.


FBSimCity, the explorable isometric city of Firebird internals, is at v0.6.0 with a new replication district.

Replication without a log

Firebird has no write-ahead log to ship, so its replication is logical — and it has to be. As each transaction commits, the changes themselves are written into a replication journal segment. When a segment fills it is sealed and queued for the replicator, and a new one opens behind it. Crucially the segments preserve commit order, so the replica replays history exactly as the primary lived it.

  • Journal Yard — where commits are journalled. If the segments cannot be shipped, they stack up here visibly.
  • Replicator — asynchronous ships at its own pace and the replica trails, so commits never wait. Synchronous makes the commit itself wait, so the primary runs at the speed of the slowest replica.
  • Replica Database — a second database, drawn as its own shallower excavation, replaying the journal in commit order with its applied history filling in as it catches up.

Set the replica slow and watch lag build, or unreachable and watch the segments pile up: run the replica-lag scenario. Bring it back and it resumes from the oldest unshipped segment, in order.

A synchronous replica that dies hangs commits

This is the behaviour I was most careful to get right. A synchronous replica that becomes unreachable does not quietly fall back to asynchronous. Silently downgrading would mean claiming a durability guarantee the configuration no longer has, so commits hang instead — which is the honest behaviour, and the reason synchronous replication is a decision rather than a default. Watch it happen.

A fourth operator decision

The replica is gone and its journal segments are accumulating on the same volume the database writes to. Stop replication and discard the backlog, and the disk stops filling but the replica needs a fresh restore rather than a resume. Keep journalling, and nothing is lost if it returns soon — but you are betting free space at a steady rate, and if the volume fills the primary stops too, which is a far larger outage than the one you were protecting against.

Both answers cost something, and the verdict quotes numbers measured from the run.

Also in this release

  • The test suite grew to 131 assertions, including commit-order preservation and in-order catch-up after an outage. It caught the two new scenarios being undocumented before this shipped, and a version mismatch between data.js and the on-screen badge.
  • The top bar had been silently wrapping to two rows on narrower screens, a regression that crept in one button per release. It is a single row again.

City: mariuz.github.io/FBSimCity
Release notes: v0.6.0
Source: github.com/mariuz/FBSimCity (MIT, plain HTML/JS, no build step)

It remains a model for intuition, not an emulator. What is real, merely scaled, or a plausible stand-in is written down in the knob audit. Corrections very welcome, particularly on the replication mechanics, which I modeled from the documentation rather than the engine source.

FBSimCity is an independent educational project, not affiliated with or endorsed by the Firebird Project. Firebird® is a registered trademark of the Firebird Foundation Incorporated.