carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

LIVE https://todo-spinel.bfj.oncarlos.com 6692def

Where it stands

  • The shared suite passes on the live host: 67 passed, 3 skipped (the two JS-only edit tests with JS off, and the screenshot test with JS off). It passes locally too, against the x86-64 build and against the arm64 binary under qemu, both with SPINEL_GC_STRESS=1.
  • make test (test/run.sh, 86 checks of the contract over curl and raw sockets, run plain and again under GC stress) passes, against the x86-64 build and against the arm64 binary under qemu.
  • scripts/boot-check.sh dist/app-linux-arm64 passes.
  • dist/app-linux-arm64 is a static aarch64 ELF, 8.8 MB, no shared libraries.

Worth knowing

  • Build route. The host Spinel compiles Ruby to C; only the C compile and static link run in the emulated arm64 root (scripts/spinel-cross, scripts/arm64-cc). Running the arm64 Spinel itself under emulation also works but took 14 minutes for hello-world, against about 3 to 5 minutes this way. Both Spinels are the same commit (abf54280).
  • A GC bug, fixed in the tep patch. spinelkit's Url.parse_query holds a decoded key in an unrooted temporary while it decodes the value. On arm64 a collection in between dropped every todo title longer than about a thousand characters (the first live deploy, 947e5fe, failed the long-title test for this reason). On x86-64 it only shows under SPINEL_GC_STRESS=1. patches/2-tep-bakeoff.patch decodes forms in C instead. Worth reporting upstream to spinelkit/tep.
  • tep rewrites words inside string literals. Its translator turns request into req even inside strings, so a 403 message reading "cross-origin request refused" came out as "cross-origin req refused". The app's strings avoid those words.
  • The arm64 root now has libssl-dev installed (tep's HTTP layer links OpenSSL, and the static link needs libssl.a; it also pulls in -lz -lzstd).
  • One worker. tep's server here is a single blocking process that closes each connection after its response. Each request has a 10-second deadline for the whole exchange, so a slow or idle client can't hold it.

Review findings (all four fixed, deployed as 6692def)

  1. Per-read timeout. Replaced by one 10-second deadline per request: before every read and write, the socket timeouts are set to the time left. A test trickles one byte a second and is cut off within 12 s.
  2. Bodies capped at 64 KiB. tep's C shim now reads bodies whole, up to 256 KiB, into a growing buffer. Above that the answer is 413. A body short of its Content-Length, carrying a NUL, or sent chunked gets 400 before any handler runs, so nothing changes. tep's SQLite column buffer grew from 64 KiB to 256 KiB to match. A 10,000-emoji title (about 120 KB encoded) round-trips locally and on the live host.
  3. Unissued cookies accepted. A new visitors table records every token the server issues. A cookie that isn't in it gets a fresh random token. A migration backfills the tokens of lists that already existed, so current visitors keep their lists (checked against a database at the old schema).
  4. --listen on any address. The binary now accepts only loopback (127.0.0.0/8, ::1, localhost) and exits with status 2 for any other address, a missing port, or neither --socket nor --listen. tep's every-interface fallback is gone.

Review findings not fixed

None.