Landmines
The recurring foot-guns. Read once; the pattern-match wakes up when you see it again.
sys.exit(n) in -c module — fixed upstream in composectl 80f82012
Symptom (until recently): a smoke that ends with sys.exit(0 if passed == total else 1) would return the correct exit code but drop
the last flush of stdout. Every "N/N PASS" summary was invisible; only
failures showed up.
Root cause: composectl's wasmtime host tore the guest instance down
before the WASI stdio streams flushed. Fixed in
~/git/webassembly-component-orchestration commit
80f82012
("fix(hosts/wasmtime): preserve stdout/stderr across guest trap paths").
If you see mysteriously-empty smoke output, confirm pylon-launch is
built against a composectl newer than that commit.
Lean vs fat builds — smoke gates
The feat/pylon-lean-arrow-endpoint branch commented numpy's C
extensions out of Modules/Setup.local to shrink pylon.wasm for the
Arrow-endpoint use case. Any smoke that transitively depends on numpy
needs to guard against its absence.
The canonical gate is at the top of every smoke (see
tests/wave11_shim_smoke.py:23-65):
import os
def _probe(mod: str) -> bool:
try:
__import__(mod); return True
except BaseException:
return False
_HAVE_NUMPY = _probe("numpy")
_REQUIRE_FAT = os.environ.get("PYLON_REQUIRE_FAT") == "1"
_NUMPY = ("numpy", _HAVE_NUMPY)
def check(label, thunk, requires=()):
missing = [r for r in requires if not r[1]]
if missing and not _REQUIRE_FAT:
# SKIP — counts toward passed on lean builds
return
...
Release-cut runs set PYLON_REQUIRE_FAT=1, which promotes SKIPs to
FAILs. Lean CI runs leave it unset, so the same smoke stays green on
both variants.
PyCapsule_SetPointer(cap, NULL) triggers ValueError on 3.14
PyCapsule_SetPointer silently rejects NULL on CPython 3.14, so the
classic "mark this capsule closed" idiom (SetPointer(cap, NULL); SetDestructor(cap, NULL)) sets the ValueError instead of doing
nothing. This bit us across sqlite, duckdb, and several other caps
and was swept in commit 76b2f93e.
Use the SetDestructor(NULL) + SetName(..., ".closed") pattern
instead. Reference at
cpython-ext/_sqlite_capability/_sqlite_capability_module.c:302-303:
PyCapsule_SetDestructor(cap_obj, NULL);
PyCapsule_SetName(cap_obj, CONN_CAPSULE_NAME ".closed");
Extraction helpers do a name check that fails after the rename, so double-close becomes a clean error and the capsule GC does not double-drop.
_dynlink in a module scope — do not
Never expose duckdb_dynlink, crc32c_dynlink, or any other name
containing _dynlink to user code. One user-facing name per cap.
Linkage mode is an implementation detail. The private
_<name>_dynlink_cap C ext stays available for probes; the
user-facing import <name> must not care whether the underlying
provider is compose-static or runtime-linked.
Retirement of this class of leak: commits 28d2c4f3 and fb396eb3.
duckdb_value_varchar returns NULL for UUID cells
DuckDB's C API's duckdb_value_varchar returns NULL for UUID column
values (task #1799 open upstream). Workaround: cast to VARCHAR in SQL:
SELECT id::VARCHAR FROM t WHERE ... -- returns "550e8400-e29b-...", not NULL
See tests/duckdb_cap_smoke.py and commit c1b4bffd for the smoke
that documents this.
Artifact rename: python.composed.wasm → pylon.wasm
Landed in commit feee530e. Older scripts, blog posts, and comments
still reference the old name; new code and docs should use
pylon.wasm. The Makefile emits a symlink for the transition period.