Skip to content

Fix Python 3.13/3.14 on-device crashes: mimalloc seccomp open() + pruned _pyrepl#29

Merged
FeodorFitsner merged 1 commit into
mainfrom
fix/mobile-seccomp-pyrepl-3.13-3.14
Jul 12, 2026
Merged

Fix Python 3.13/3.14 on-device crashes: mimalloc seccomp open() + pruned _pyrepl#29
FeodorFitsner merged 1 commit into
mainfrom
fix/mobile-seccomp-pyrepl-3.13-3.14

Conversation

@ndonkoHenri

Copy link
Copy Markdown
Contributor

Two runtime bugs make Python 3.13/3.14 apps crash on mobile — before any app code runs. Both are flet build mobile-test blockers on 3.13/3.14 (3.12 is unaffected).

Bug 1 — Android: mimalloc's raw open(2) is seccomp-killed (x86_64)

CPython bundles mimalloc starting in 3.13. Its load-time constructor (_mi_process_initmi_process_init, an .init_array entry) reads /proc/sys/vm/overcommit_memory via mi_prim_open, which on platforms where SYS_open is defined (x86_64/x86/arm32) issues the bare open(2) syscall directly (to avoid recursion while the allocator is initializing). Android's bionic seccomp policy forbids open(2) — only openat(2) is allowed — so the kernel raises SIGSYS (SYS_SECCOMP) at dlopen() of libpython, before the interpreter starts. Every native import (indeed the whole app) dies.

  • arm64 is latent (no SYS_open; mimalloc's #else path uses libc open(), which bionic routes through openat), so real arm64 devices are unaffected — but the CI/emulator target is x86_64, where it always crashes.
  • 3.12 has no mimalloc, so it never hit this.

Fix: android/patches/mimalloc_seccomp_openat.patch rewrites the one mi_prim_open call:

-  return syscall(SYS_open,fpath,open_flags,0);
+  return syscall(SYS_openat,AT_FDCWD,fpath,open_flags,0);

SYS_openat(AT_FDCWD, …) is semantically identical and is the syscall Android permits. Applied gated >= 3.13 in android/build.sh (validated to apply to 3.13.14 and 3.14.6 sources).

Bug 2 — iOS: _pyrepl is pruned but 3.14 pdb imports it

_pyrepl was excluded from the mobile stdlib as "interactive-REPL / dev-only". But CPython 3.14's pdb.py imports _pyrepl at module load, so anything importing pdb (e.g. pytest's debugging plugin) dies with ModuleNotFoundError: No module named '_pyrepl'. 3.13's pdb doesn't import it, which is why 3.13 iOS was unaffected.

Fix: un-prune _pyrepl in android/python-android-dart.exclude and darwin/python-darwin-stdlib.exclude.

Validation

Built green here (run 29187831914, all versions × platforms). Verified the fixed binaries directly: the x86_64 libpython3.14.so's mi_prim_open site now emits syscall(257=openat, AT_FDCWD) instead of syscall(2=open), and the stdlib bundle restores stdlib/_pyrepl/. Then ran it end-to-end on-device via mobile-forge (bundling this run's runtime): all six legs green — android + iOS × 3.12/3.13/3.14 — where android 3.13/3.14 and iOS 3.14 previously crashed.

Two distinct on-device runtime bugs that only surface on a genuine 3.13/3.14
mobile run (3.12 unaffected), found via mobile-forge genuine multi-Python mobile
tests (fork run 27439192170):

1. _pyrepl was pruned from the iOS + Android stdlib excludes as a "dev-only /
   interactive-REPL" module. But CPython 3.14 pdb.py imports _pyrepl at module
   load, so any on-device code importing pdb (incl. pytest) dies with
   "ModuleNotFoundError: No module named _pyrepl". 3.13 pdb does not import it,
   which is why 3.13 iOS was unaffected. Un-prune _pyrepl in
   darwin/python-darwin-stdlib.exclude + android/python-android-dart.exclude.

2. mimalloc (bundled in CPython since 3.13) issues a bare open(2) syscall from
   its load-time constructor (mi_process_load -> unix_detect_overcommit reading
   /proc/sys/vm/overcommit_memory) where SYS_open is defined (x86_64). Android
   bionic seccomp forbids open(2) -> SIGSYS at dlopen of libpython, before the
   interpreter starts. arm64 is unaffected (no SYS_open; mimalloc falls back to
   libc open() -> openat). Patch mi_prim_open to use SYS_openat
   (android/patches/mimalloc_seccomp_openat.patch, gated >=3.13 in build.sh).
@FeodorFitsner FeodorFitsner merged commit dc76612 into main Jul 12, 2026
32 checks passed
@FeodorFitsner FeodorFitsner deleted the fix/mobile-seccomp-pyrepl-3.13-3.14 branch July 12, 2026 17:26
ndonkoHenri added a commit to flet-dev/mobile-forge that referenced this pull request Jul 14, 2026
…er/packaging fixes, deploy-via-dispatch (#106)

Flet 0.86 changed the mobile packaging model — site-packages ship as a compressed
`sitepackages.zip` (zipimport); native `.so` are relocated to `jniLibs/<abi>/` with `.soref`
markers on Android; and on iOS each extension is framework-ized and **linked into the app at
launch**. That broke a cluster of recipes *on-device* even though their wheels build cleanly.

This branch fixes the regressions across three layers — **forge core**, the **recipes**, and the
**CI / recipe-tester** — and adds a **deploy-via-dispatch** path so fixed wheels can be published to
pypi.flet.dev without a push to `main`.

## forge core — `src/forge/build.py` (`fix_wheel`), `schema/meta-schema.yaml`

Three generic, recipe-agnostic packaging fixes:

- **iOS `MH_BUNDLE` → `MH_DYLIB`** (`06f4f71`). CMake/scikit-build extensions link as `MH_BUNDLE`
  (they ignore CPython's `LDSHARED=-dynamiclib`); serious_python framework-izes and **links** them,
  and `ld` rejects a bundle (`Unsupported mach-o filetype (only MH_OBJECT and MH_DYLIB can be
  linked)`). `fix_wheel` injects an `LC_ID_DYLIB`, flips the filetype, and re-signs ad-hoc.
- **Android foreign-arch `.so` drop** (`385430f`). A plain-setuptools sdist that builds
  `build_ext --inplace` into forge's reused source tree leaks a prior ABI slice's arch-tagged `.so`
  into the next slice's wheel (pymongo shipped the arm64 `_cbson`/`_cmessage` **byte-for-byte** in
  the x86_64 wheel → serious_python strips the arch off the SOABI tag → two arches collide on one
  `<name>.soref` → Gradle `duplicate entry`). `fix_wheel` now drops any `.so` whose ABI-tag triplet
  ≠ the target's.
- **Android symbol-versioned `PyInit` ABI-tag** (`385430f`). forge ABI-tags a bare `NAME.so`
  exporting `PyInit_<name>` so serious_python writes its `.soref`. onnxruntime's pybind module
  exports the init symbol **version-scripted** (`PyInit_..._state@@VERS_1.0`), which the exact-match
  test missed → the `.so` shipped bare on both arches → on-device `ModuleNotFoundError`. The match
  now tolerates a trailing `@version` suffix.

Plus a new `extract_packages` meta field (schema + recipe-tester wiring): Android-only list of
packages to ship **extracted to disk** rather than inside `sitepackages.zip`, for "path-hungry"
packages that read bundled data through a real `__file__` path (`NotADirectoryError` inside a zip).

## Recipe fixes

**Flet 0.86 loader / packaging:**
- **llama-cpp-python** — ctypes loader loads bundled libs by soname, falls through to the `jniLibs`
  bare-soname on Android, accepts `sys.platform == "android"` (PEP 738, py3.13+), and resolves iOS
  `.fwork` framework markers.
- **opencv-python** — load native `cv2` under the top-level name (Android) + resolve the iOS
  framework-ized native.
- **python-magic** — ship `magic.mgc` **in-wheel** and load it from memory (`magic_load_buffers`);
  Flet 0.86's `copyOpt` drops the flet-lib `opt/` data file.
- **pycryptodome / pycryptodomex** — resolve the native module via `importlib`.
- **selectolax** — drop the vestigial `modest` namespace import.
- **matplotlib** — fix an iOS **launch SIGSEGV**: ft2font's pybind11 enum helper (`_enums.h`
  `P11X_DECLARE_ENUM`) acquired the Python GIL in a C++ **static initializer**, which dyld runs at
  app launch **before `Py_Initialize()`** under serious_python's link-at-launch iOS model. Deferred
  the GIL/pybind work to `bind_enums()` (runs inside `PYBIND11_MODULE`, at import).
- **jq** — gate the static-link syntax per platform (GNU `ld` `-l:NAME.a` vs Apple `ld64 -lX`);
  `flet-libjq` keeps its static archives (`libjq.a`/`libonig.a`).
- **scikit-learn** — drop the obsolete iOS multiprocessing-guard patch (python-build ships
  `_posixshmem` now).

**Build-number bumps (republish with the fixes above):** pymongo→2, onnxruntime→2, matplotlib→2,
llama-cpp-python→2, opencv-python→2, ncnn→2, faiss-cpu→2, pyarrow→2, pyobjus→2, opaque→2,
pycryptodome→2, pycryptodomex→2, python-magic→2, scikit-learn→2, jq→2, coolprop→11, pysodium→11,
selectolax→11, flet-libjq→11.

**Minor (no wheel change):** astropy/spacy/thinc gain `extract_packages` (data via `__file__`);
flet-lib{crc32c,freetype,jpeg,png,pyjni,yaml,geos} version jinja-templating.

## CI / recipe-tester (`.github/workflows/*`, `tests/recipe-tester/*`, `setup.sh`)

- **`publish` input** — publish to pypi.flet.dev via `workflow_dispatch` (`publish: true`), in
  addition to the automatic push-to-main path. Lets a deploy dispatch publish with a pinned
  `python_build_run_id` + `serious_python_ref`.
- **`serious_python_ref`** + **`python_build_run_id`** inputs — validate recipes against an
  unreleased serious_python branch / a specific python-build run before release; the recipe-tester
  template appends the sp git-override when set.
- Android on-device app is built for **x86_64 only** (the emulator's arch) — faster and sidesteps
  `excluded_arches` pip-resolution failures (e.g. pyarrow/onnxruntime dropping armeabi-v7a).
- `stage_recipe.sh` derives `[tool.flet.android] target_arch` from the built wheels and wires
  `extract_packages` + `test.requires`.
- `setup.sh` pins python-build release **`20260714`** (includes `dc76612`'s `_pyrepl` prune +
  mimalloc seccomp `open()` fix — required for genuine 3.13/3.14 on-device tests).

## Validation

- **matplotlib** — full all-python matrix green (iOS + android × py3.12/3.13/3.14).
- On-device verified across the cluster: pymongo, onnxruntime, pyarrow (android), polars, shapely,
  pyzmq, selectolax, xxhash, llama-cpp-python, jq, opaque, ncnn (+opencv-python), python-magic, …
- The earlier 3.13/3.14 android "seccomp `SIGSYS`" timeouts and py3.14 iOS `_pyrepl` failures were
  **flet/python-build infra bugs**, fixed by python-build `dc76612` (flet-dev/python-build#29) — not recipe bugs.
- On-device flow uses serious_python `main`.

## Consumer notes

The following packages now build **and run on-device** under Flet 0.86 (Android + iOS), published to
`pypi.flet.dev`. Add them under `[tool.flet.dependencies]` (or your app's deps) as usual.

- **matplotlib** — Agg backend works; `savefig(..., format="png")` verified on device. (iOS
  previously crashed at launch — fixed.)
- **pymongo** — driver installs cleanly across all arches (no more x86_64/arm64 `.soref` collision).
- **onnxruntime** — `InferenceSession` loads and runs (the pybind module now imports on device).
- **opencv-python** — `import cv2` works on Android and iOS; heavy `.so`, expect a larger app.
- **python-magic** — works without a system `magic` DB: the compiled `magic.mgc` ships in the wheel
  and is loaded from memory; no `[tool.flet]` data config needed.
- **llama-cpp-python** — GGUF models load via the ctypes bindings on Android and iOS.
- **ncnn** — pulls **opencv-python** as a runtime dep (both must resolve on your target).
- **jq / opaque** — depend on `flet-libjq` / `pysodium` respectively (published alongside).
- **pyarrow, faiss-cpu, scikit-learn, pyobjus, pycryptodome(x), selectolax, coolprop** — import +
  core functionality verified on device.

Path-hungry packages (astropy, spacy, thinc, matplotlib) set `extract_packages` in their recipe, so
their bundled data resolves on Android automatically — no app-side config required.
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.

2 participants