Skip to content

Desktop multiprocessing support: PYTHONINSPECT removal, version-keyed dart_bridge staging, dart_bridge 1.5.0 keep-alives#228

Merged
FeodorFitsner merged 7 commits into
mainfrom
multiprocessing
Jul 9, 2026
Merged

Desktop multiprocessing support: PYTHONINSPECT removal, version-keyed dart_bridge staging, dart_bridge 1.5.0 keep-alives#228
FeodorFitsner merged 7 commits into
mainfrom
multiprocessing

Conversation

@ndonkoHenri

@ndonkoHenri ndonkoHenri commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Part of flet-dev/flet#4283. Depends on flet-dev/dart-bridge#9 (must be merged and tagged 1.5.0 first). Consumed by flet-dev/flet#6662, whose build-template runners perform the actual argv interception.

Context

dart_bridge 1.5.0 adds serious_python_is_mp_invocation / serious_python_main (plus _w wide-char variants on Windows): host apps call them first thing in main to detect CPython child command lines produced by multiprocessing's spawn machinery and service them as a plain headless interpreter instead of re-launching the GUI. This PR contains serious-python's share of that fix.

Changes

  • PYTHONINSPECT=1 is no longer stamped into the host process environment by any platform implementation. It had no effect on the embedded interpreter itself (nothing in the embedded lifecycle runs pymain, the only consumer of Py_InspectFlag), but the platform plugins setenv it process-wide, so every child process inherited it — and a serviced multiprocessing worker IS a real interpreter run through Py_Main, which an inherited PYTHONINSPECT would hold open in interactive mode after its -c command completed. It equally affected any python interpreter users launch via subprocess from a packaged app. dart_bridge's serious_python_main also unsets it defensively, but the right fix is to stop leaking it.
  • SeriousPythonPlugin.swift gains keep-alive references for the two new exports. On Apple platforms dart_bridge is a static archive linked into the host executable and both entry points are dlsym-only, so without a static reference the app link's -dead_strip would discard them. This extends the existing keep-alive block that already protects serious_python_run and the DartBridge_* exports for Dart FFI. It is belt-and-braces alongside dart_bridge 1.5.0's own __attribute__((used)) marking, and it documents the dlsym contract at the one place Apple linkage is wired.
  • prepare_macos.sh / prepare_ios.sh: the extracted dart_bridge.xcframework in dist_* is now keyed to the dart_bridge version via a .dart_bridge_version marker, mirroring the existing .python_build_id marker. Previously the extraction was guarded by a bare directory-exists check, so a dart_bridge version bump silently kept staging the stale extraction from the previous version.
  • CHANGELOG entries (4.3.0) across all five packages, and the multiprocessing recommendation in docs/dedicated-data-channels.md now carries the desktop-only scope caveat.

Coordination notes for the release

The Swift keep-alives make serious_python_darwin hard-require dart_bridge >= 1.5.0 at link time, so this must not release before that tag exists. The dart_bridge_version pin bump itself is not in this PR: it flows from flet-dev/python-build's manifest.json via the generated python_versions.properties / python_versions.dart, and lands with the usual release commit together with the 4.3.0 pubspec bumps.

Verification

Verified end-to-end inside flet build apps on macOS (arm64), Windows 11 (arm64 host, x64 app), and Ubuntu 22.04 aarch64, together with flet-dev/dart-bridge#9 and flet-dev/flet#6662: worker functions defined in the app's main.py pickle and round-trip, ProcessPoolExecutor completes with worker reuse and cpu-scale speedups, children run headless (no Flutter engine, no windows), the resource tracker functions, and all children exit cleanly, under both spawn and Linux's 3.14 forkserver default.

All four platform implementations set PYTHONINSPECT=1 among the env vars
they setenv() before Py_Initialize. The variable has no effect on the
embedded interpreter itself — nothing in the embedded lifecycle runs
pymain, which is the only consumer of Py_InspectFlag — but because the
vars are written into the real process environment, every child process
the app spawns inherits it.

That inheritance becomes actively harmful with multiprocessing child
interception (dart_bridge >= 1.5.0, flet-dev/flet#4283): a serviced
worker child IS a real interpreter run through Py_Main, and an inherited
PYTHONINSPECT would hold it open in interactive mode after its -c
command completes instead of exiting. It equally affects any python
interpreter a user launches via subprocess from a packaged app.

dart_bridge's serious_python_main also unsets the variable defensively
(apps may run against an older serious_python), but the right fix is to
stop leaking it in the first place.
…xports

dart_bridge 1.5.0 adds serious_python_is_mp_invocation / serious_python_main
(flet-dev/flet#4283): the host app's main.swift dlsym's them before
NSApplicationMain to detect CPython child command lines produced by
multiprocessing's spawn machinery and service them as a plain headless
interpreter instead of booting a second GUI instance.

On Apple platforms dart_bridge is a static archive linked into the host
executable, and both entry points are dlsym-only — there is no static
call site anywhere, so the app link's -dead_strip would discard them.
Extend the existing keep-alive block (which already protects
serious_python_run and the DartBridge_* exports for Dart FFI's
DynamicLibrary.process() lookups) with references to the two new symbols.

This is belt-and-braces: dart_bridge 1.5.0 also marks its exports
__attribute__((used)) (no-dead-strip at the atom level), but the
keep-alive keeps working against toolchains or archives where that
attribute is absent, and documents the dlsym contract in the one place
Apple linkage is wired.
prepare_macos.sh / prepare_ios.sh guarded the dart_bridge extraction with
a bare directory-exists check, so once dist_* held an extracted
dart_bridge.xcframework, a dart_bridge version bump kept staging the
stale extraction from the previous version — the freshly downloaded zip
in the version-keyed cache was never unpacked. (The Python dist half of
the script already solved the identical problem with a .python_build_id
marker.)

Key the extraction the same way: a .dart_bridge_version marker next to
the extracted xcframework, re-extracting (rm -rf + unzip) whenever it is
missing or disagrees with the requested version.

Also fold the duplicated "$python_full_version-$python_build_date"
expression into a single pb_id variable used for both the cache dir and
the dist marker.
Draft the 4.3.0 CHANGELOG sections across all five packages for the
multiprocessing work (flet-dev/flet#4283):

- serious_python: the dart_bridge 1.5.0 child-interception contract
  (serious_python_is_mp_invocation / serious_python_main, plus the _w
  wide-char variants on Windows) and the PYTHONINSPECT removal.
- serious_python_darwin: dead-strip protection for the new exports and
  the version-keyed dart_bridge extraction in prepare_{macos,ios}.sh.
- serious_python_windows / _linux: platform-specific consumption notes
  (wWinMain / main.cc dlopen; the Python 3.14 forkserver-default angle
  on Linux).
- serious_python_android: PYTHONINSPECT removal only (no behavior
  change — Android doesn't support process spawning).

Also scope the multiprocessing recommendation in
docs/dedicated-data-channels.md: it applies to desktop hosts whose
binary services the spawn re-exec protocol; iOS/Android forbid spawning
child processes.

The version numbers reference the upcoming dart_bridge 1.5.0 /
serious_python 4.3.0 releases; the pubspec version bumps and the
dart_bridge_version pin update land with the release commit once
dart_bridge 1.5.0 is tagged.
…dide 314.0.2)

Regenerated via `dart run serious_python:gen_version_tables --release-date 20260708`.
Delivers dart_bridge 1.5.0 with the multiprocessing child-interception exports;
Pyodide for 3.14 bumped 314.0.1 -> 314.0.2. CPython versions unchanged
(3.12.13 / 3.13.14 / 3.14.6). 4.3.0 changelogs updated to record the snapshot.
Remove the generic 'Version bump aligning with the serious_python_* 4.3.0 release' line from the CHANGELOG entries across all platform implementations (Android, Darwin, Linux). The specific technical changes and dart_bridge updates are sufficient to document the release.
@FeodorFitsner FeodorFitsner merged commit 8f326f8 into main Jul 9, 2026
56 of 85 checks passed
@FeodorFitsner FeodorFitsner deleted the multiprocessing branch July 9, 2026 00:37
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