Skip to content

Fix 1983 - #1984

Merged
oberstet merged 7 commits into
crossbario:masterfrom
oberstet:fix_1983
Oct 14, 2026
Merged

oberstet merged 7 commits into
crossbario:masterfrom
oberstet:fix_1983

Conversation

@oberstet

Copy link
Copy Markdown
Contributor

fixes #1983 - with proper two-phase, red-green TDD

…uffers or numpy

New src/autobahn/test/test_import_websocket_only.py (runs under both trial
and pytest). Each import runs in a fresh interpreter, with a fake "numpy"
package first on the path, so the tests do not depend on whether numpy is
installed:

- importing autobahn, autobahn.twisted.websocket or
  autobahn.asyncio.websocket must load neither the vendored FlatBuffers
  runtime nor numpy
- with a broken numpy (raises ImportError), the same imports must succeed
- after a WebSocket-only import, FlatBuffers still works when used
  (serializer roundtrip)

Expected: the first two fail on master (autobahn/__init__.py and
autobahn/wamp/message.py import the FlatBuffers runtime eagerly, which
imports numpy whenever installed), the third passes.

Note: This work was completed with AI assistance (Claude Code).
… interpreters

`just test-imports` now also runs src/autobahn/test/test_import_websocket_only.py.
CI calls it in the "Import Smoke Test" job for cpy311, cpy312, cpy313, cpy314
and pypy311, so the new tests also run on PyPy (the main test job runs them
on cpy314 only, via test-twisted and test-asyncio).

Expected: test_no_flatbuffers_no_numpy and
test_broken_numpy_does_not_break_import fail on all five interpreters.

Note: This work was completed with AI assistance (Claude Code).
@oberstet

Copy link
Copy Markdown
Contributor Author

TDD phase 1: red

This PR fixes #1983 test-first: first the red commits, which only add tests and must fail on
the current code, then the green commit with the fix, which must turn the same tests green
without changing them.

Red commit 1: 23c94af1, the regression tests

New src/autobahn/test/test_import_websocket_only.py. Each test imports in a fresh
subprocess
(so modules other tests already loaded don't hide anything), with
USE_TWISTED / USE_ASYNCIO removed from the environment, and with a fake numpy package put
first on PYTHONPATH:

The tests are plain unittest test cases, so both trial (Twisted) and pytest (asyncio)
collect them.

CI run 38387068318:
red as expected. Code test coverage (Combined Twisted + asyncio) failed in
check-coverage-twisted with FAILED (skips=32, failures=2, successes=326). The 2 failures are
exactly the two tests above:

builtins.AssertionError: True is not false : import autobahn loads the FlatBuffers runtime
autobahn.test.test_import_websocket_only.TestWebSocketOnlyImports.test_no_flatbuffers_no_numpy
  File ".../src/autobahn/__init__.py", line 35, in <module>
    from autobahn import flatbuffers
  ...
  File ".../src/autobahn/flatbuffers/number_types.py", line 21, in <module>
    np = import_numpy()
  File ".../src/autobahn/flatbuffers/compat.py", line 75, in import_numpy
    import numpy as np
  File "/tmp/tmpek9e0oqp/numpy/__init__.py", line 1, in <module>
    raise ImportError('simulating a broken numpy install')
ImportError: simulating a broken numpy install
...
autobahn.test.test_import_websocket_only.TestWebSocketOnlyImports.test_broken_numpy_does_not_break_import

The roundtrip test passed, as it should on the current code.

Red commit 2: 9ca0d0bb, run the tests on every interpreter

The main test suite runs on CPython 3.14 only. To cover the other interpreters, and PyPy in
particular (where we use importlib.util.LazyLoader for the fix), the just test-imports recipe
now also runs test_import_websocket_only.py. That recipe is the "Import Smoke Test" job, which
runs on cpy311, cpy312, cpy313, cpy314 and pypy311.

CI run 38396172114: red as
expected.

  • Import Smoke Test (cpy314), CPython 3.14.8: 6 failed, 35 passed. The 6 failures are the two
    tests above, one per import, and the rest of the import smoke test still passes:

    SUBFAILED(modname='autobahn') ...::test_broken_numpy_does_not_break_import - AssertionError: import autobahn failed:
    SUBFAILED(modname='autobahn.twisted.websocket') ...::test_broken_numpy_does_not_break_import - AssertionError: import autobahn.twisted.websocket failed:
    SUBFAILED(modname='autobahn.asyncio.websocket') ...::test_broken_numpy_does_not_break_import - AssertionError: import autobahn.asyncio.websocket failed:
    SUBFAILED(modname='autobahn') ...::test_no_flatbuffers_no_numpy - AssertionError: True is not false : import autobahn loads the FlatBuffers runtime
    SUBFAILED(modname='autobahn.twisted.websocket') ...::test_no_flatbuffers_no_numpy - AssertionError: True is not false : import autobahn.twisted.websocket loads the FlatBuffers runtime
    SUBFAILED(modname='autobahn.asyncio.websocket') ...::test_no_flatbuffers_no_numpy - AssertionError: True is not false : import autobahn.asyncio.websocket loads the FlatBuffers runtime
    ========================= 6 failed, 35 passed in 4.75s =========================
    

    So autobahn.asyncio.websocket and autobahn.twisted.websocket load the FlatBuffers runtime
    too, not just import autobahn. They do it through autobahn.wamp (see autobahn/__init__.py eagerly imports flatbuffers → numpy is loaded in every process (for v25.12.1 and later) #1983).

  • The other matrix legs (cpy311, cpy312, cpy313, pypy311) were cancelled before they
    finished. The matrix is fail-fast, so the first failing leg stops the others.

  • Code Quality Checks: failed in the coverage step with FAILED (skips=32, failures=2, successes=326), the same as red commit 1. The two coverage upload steps failed after it because
    no report was written.

  • Everything else passed, including both Import Smoke Test (lowest-direct deps) legs, which run
    only test_import_all.py.

Locally, on PyPy 3.11 (7.3.23) without the fix, just test-imports pypy311 gives the same
result, 6 failed, 35 passed.

Next: phase 2, green

The green commit changes only the two places described in #1983:

  1. src/autobahn/__init__.py: from autobahn import flatbuffers moves from module level into
    check_zlmdb_flatbuffers_version_in_sync(), the only function that uses it.
  2. src/autobahn/wamp/message.py: flatbuffers and message_fbs become lazy modules
    (importlib.util.LazyLoader), loaded on the first FlatBuffers message built or parsed.

No test changes. Expected: the same CI jobs green, now with all five Import Smoke Test legs
including pypy311 running to the end.

Note: This comment was drafted with AI assistance (Claude Code).

…t is used

Importing autobahn for WebSocket only (Twisted or asyncio) no longer loads
the vendored FlatBuffers runtime, nor numpy, which that runtime imports at
module level whenever installed. A broken numpy install can no longer make
autobahn unimportable for WebSocket users.

- autobahn/__init__.py: move "from autobahn import flatbuffers" from
  module level into check_zlmdb_flatbuffers_version_in_sync(), the only
  user, next to its existing local "import zlmdb.flatbuffers".
- autobahn/wamp/message.py: import "autobahn.flatbuffers" and
  "autobahn.wamp.message_fbs" through importlib.util.LazyLoader (the
  "Implementing lazy imports" recipe from the importlib docs), so they
  are executed on the first FlatBuffers message built or parsed.

No test changes: the red tests from the previous two commits now pass,
on CPython 3.14 and PyPy 3.11 (35 passed, 6 subtests passed).

Note: This work was completed with AI assistance (Claude Code).
@oberstet

Copy link
Copy Markdown
Contributor Author

TDD phase 2: green

Green commit: 8077fcb0, the fix

It changes only the two places described in #1983, and no tests:

  1. src/autobahn/__init__.py: from autobahn import flatbuffers moves from module level into
    check_zlmdb_flatbuffers_version_in_sync(), the only code that uses it, next to its existing
    local import zlmdb.flatbuffers.
  2. src/autobahn/wamp/message.py: autobahn.flatbuffers and autobahn.wamp.message_fbs are
    imported through importlib.util.LazyLoader (the "Implementing lazy imports" recipe from the
    importlib documentation). They are registered when message.py is imported, but executed only
    on first attribute access, i.e. when the first FlatBuffers message is built or parsed.

+28 / -4 lines in total. Nothing changes in the vendored FlatBuffers runtime, in the
flatc-generated code, or in any public import path.

CI

All workflows for 8077fcb0 passed: main,
wheels,
wheels-docker,
wheels-arm64 and
wstest.

The same jobs that were red before:

job red (9ca0d0bb) green (8077fcb0)
Import Smoke Test (cpy314), CPython 3.14.8 6 failed, 35 passed 35 passed
Import Smoke Test (cpy313), CPython 3.13.16 cancelled 35 passed
Import Smoke Test (cpy312), CPython 3.12.3 cancelled 35 passed
Import Smoke Test (cpy311), CPython 3.11.17 cancelled 35 passed
Import Smoke Test (pypy311), PyPy 3.11.15 cancelled (6 failed, 35 passed locally) 35 passed
Code Quality Checks: coverage, Twisted / trial (with NVX) FAILED (skips=32, failures=2, successes=326) PASSED (skips=32, successes=328)
Code Quality Checks: coverage, Twisted / trial (without NVX) not run PASSED (skips=31, successes=329)
Code Quality Checks: coverage, asyncio / pytest not run 298 passed, 6 skipped / 299 passed, 5 skipped

All three new tests pass under both trial and pytest, and on all five interpreters, PyPy
included. So with the fix:

  • import autobahn, import autobahn.twisted.websocket and import autobahn.asyncio.websocket load
    neither the FlatBuffers runtime nor numpy (goal 3).
  • The same imports succeed when numpy is installed but broken.
  • FlatBuffers serialization still works, and still loads the runtime when used (goal 1).

check_zlmdb_flatbuffers_version_in_sync() (goal 2) keeps its name and behaviour. Locally, against
zlmdb 26.7.1, it returns 25.12.19 as before.

Memory

Measured locally, as peak RSS (VmHWM) right after the import in a fresh interpreter, median of 5
runs, with numpy installed. † means numpy was loaded.

import CPython 3.14: 26.7.1 → this PR PyPy 3.11: 26.7.1 → this PR
import autobahn 28.7 MB † → 11.9 MB 87.9 MB † → 64.4 MB
import autobahn.asyncio.websocket 51.0 MB † → 37.7 MB 117.0 MB † → 99.2 MB
import autobahn.twisted.websocket 63.0 MB † → 50.1 MB 132.3 MB † → 113.7 MB
from daphne.server import Server 64.4 MB † → 51.5 MB 135.9 MB † → 115.5 MB

The full tables, the method, and the scripts to reproduce them follow in a separate docs commit on
this PR (docs/memory-footprint.rst, scripts/memory-footprint/).

Note: This comment was drafted with AI assistance (Claude Code).

…e and after

New docs page docs/memory-footprint.rst (linked after wheels-inventory):
peak RSS (VmHWM) right after an import, in a fresh interpreter, for
CPython 3.14 and PyPy 3.11, comparing autobahn 25.10.2 (FlatBuffers
optional), 26.7.1 (vendored FlatBuffers, loaded eagerly) and this branch.
Rows: the interpreter, cryptography, numpy, asyncio, Twisted, txaio,
autobahn (WAMP and WebSocket imports), Daphne and Crossbar.io.

With the fix, every autobahn import drops by about 13-17 MB on CPython
and 18-24 MB on PyPy, and none loads numpy any more.

- scripts/memory-footprint/measure.py: measures the venv it runs in
- scripts/memory-footprint/run.sh: creates throwaway venvs (uv) per
  autobahn version, plus a crossbar venv, and runs measure.py in each
- scripts/memory-footprint/render.py: renders the results as the
  included tables (docs/memory-footprint/*.inc)
- scripts/memory-footprint/results/: the raw results the page shows
- justfile: "just memory-footprint <cpy314|pypy311> [label]"

Note: This work was completed with AI assistance (Claude Code).
Next release is 26.10.1 (26.9.1 is not released, superseded).

Note: This work was completed with AI assistance (Claude Code).
…1.dev1

Re-measured after the version bump, so the recorded versions match the
development version. Values within measurement noise of the previous run.

Note: This work was completed with AI assistance (Claude Code).
@oberstet
oberstet merged commit 3ad2add into crossbario:master Oct 14, 2026
48 checks passed
@oberstet
oberstet deleted the fix_1983 branch October 14, 2026 10:41
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.

autobahn/__init__.py eagerly imports flatbuffers → numpy is loaded in every process (for v25.12.1 and later)

1 participant