Skip to content

feat: process-reuse support + native log redirection (1.3.0)#4

Merged
FeodorFitsner merged 1 commit into
mainfrom
process-reuse
Jun 16, 2026
Merged

feat: process-reuse support + native log redirection (1.3.0)#4
FeodorFitsner merged 1 commit into
mainfrom
process-reuse

Conversation

@FeodorFitsner

Copy link
Copy Markdown
Contributor

Summary

  • New C exports + Python module API for handling Android process reuse — the scenario where the OS keeps the OS process alive after a back-button quit and restarts only the Dart VM. The old socket-based plugin coped via "skip Py_Initialize if Python is already up" + a still-running socket Flet server. The current dart_bridge transport had neither, so process-reuse would either crash (re-init) or silently hang (stale port handlers).
  • sys.stdout / sys.stderr redirection to native log sinks (logcat / os_log / stderr) installed right after Py_Initialize — restores the old plugin's adb logcat -s flet.python:* visibility lost during the dart_bridge migration.

Wire / API contract

New C exports (callable from Dart via FFI):

int  dart_bridge_is_python_initialized(void);
void dart_bridge_signal_dart_session(int n_pairs,
                                      const char* const* labels,
                                      const int64_t* ports);
int  dart_bridge_install_stdio_redirect(void);

New Python module method (built-in dart_bridge):

dart_bridge.add_session_restart_handler(callback)
# callback receives a {label: port} dict on every new Dart VM signal.

Behavioral change in serious_python_run(): when called with Py_IsInitialized() already true, no longer refuses with an error — instead applies the new env vars and fires dart_bridge_signal_dart_session with the labeled port map reconstructed from this run's config ("protocol"FLET_DART_BRIDGE_PORT, "exit"FLET_DART_BRIDGE_EXIT_PORT). Returns 0 immediately.

Registered Python handlers (flet's FletDartBridgeServer restart loop, the flet build template's sys.exit patcher) rewire to the new ports — user-level Python state and singletons are preserved across the Dart VM restart.

Native log redirection

Right after Py_Initialize, sys.stdout / sys.stderr are replaced with file-like wrappers whose write() forwards to a platform-specific native log sink:

  • Android: __android_log_write under tag flet.python (INFO for stdout, ERROR for stderr — visible via adb logcat -s flet.python:*)
  • iOS/macOS: os_log_with_type (visible in Console.app)
  • Other (desktop Linux/Windows): fwrite passthrough — preserves flet run console output and CI logs unchanged

Installed by dart_bridge_install_stdio_redirect(), called from sp_run_python() immediately after Py_Initialize so even bootstrap-time prints (sys.path injection, top-level user-app statements) land in the platform log instead of vanishing into /dev/null on mobile.

Implementation is at the Python sys.stdout layer (not fd 1/2 replacement via dup2 + pipe + reader thread) — simpler, no extra thread, catches print() and traceback writes directly. Native crashes that bypass Python's stdio aren't captured here (separate concern).

Other changes

  • dart_bridge_clear_handlers also clears the session-restart subscriber list, since those PyObject* refs are held the same way as port handlers.
  • CMakeLists.txt: version bump 1.2.21.3.0 (binary-compatible adds — existing consumers keep working without code changes; the new exports are looked up softly on the Dart side via lookupOrNull).

Test plan

  • Local desktop build (macOS): cmake --build clean, all four new exports present in nm -gU libdart_bridge.dylib.
  • ctypes smoke test of dart_bridge_is_python_initialized + dart_bridge_signal_dart_session against the "no embedded interpreter" code path — no crashes.
  • CI matrix build (Linux / Windows / Android / Apple xcframework).
  • End-to-end Android process-reuse test: launch app → back-button exit → re-tap icon (OS reuses process) → confirm UI re-appears with fresh session, no Py_Initialize crash in logcat, flet.python tag carries app's print output.
  • Cross-platform fresh-start regression: flet build apk / macos / windows / linux builds — is_python_initialized returns 0 on first run, signal_dart_session is a no-op, behavior unchanged from 1.2.2.

Consumer rollout

  • flet-dev/serious-python: Dart FFI bindings for the new exports use lookupOrNull so pre-1.3.0 binaries still load (calls degrade to no-op). Companion branch ready.
  • flet-dev/flet: flet.app.run_async dart_bridge branch grows a _DartBridgeServerHandle wrapper that swaps in a new FletDartBridgeServer on add_session_restart_handler callback. flet build template's initBridges calls signalDartSession on every startup. python.dart bootstrap makes _exit_port mutable and subscribes to restart events.

Plan doc: the-new-challenge-at-joyful-thacker.md (local plan file).

Two related additions to libdart_bridge, mainly to fix the Android
back-button-exit / process-reuse scenario where the OS keeps the OS
process alive but restarts only the Dart VM. The old socket-based
flet plugin coped via "skip Py_Initialize if Python is already up"
plus the still-running socket Flet server accepting the new client.
This binary did neither.

## Process reuse (Android lifecycle)

Three new C exports + one Python module method form the contract Dart
and Python use to swap to fresh native ports without re-initializing
Python:

  C exports:
    - int  dart_bridge_is_python_initialized(void)
    - void dart_bridge_signal_dart_session(int n, const char* const* labels,
                                            const int64_t* ports)
    - int  dart_bridge_install_stdio_redirect(void)

  Python module method:
    - dart_bridge.add_session_restart_handler(callback)
      callback receives a {label: port} dict on every new Dart VM signal.

serious_python_run() no longer aborts when Py_IsInitialized() is true;
instead it applies the new env vars (so anything reading os.environ
sees current values) and fires dart_bridge_signal_dart_session with the
labeled port map reconstructed from this run's config (currently
"protocol" ← FLET_DART_BRIDGE_PORT, "exit" ← FLET_DART_BRIDGE_EXIT_PORT).

Registered Python handlers (flet's FletDartBridgeServer restart loop,
the flet build template's sys.exit patcher, etc.) then rewire to the
new ports — the running Python state, user-level singletons, and
in-memory data are preserved.

## stdout/stderr → native log

Right after Py_Initialize, sys.stdout / sys.stderr are replaced with
file-like wrappers whose write() forwards to a platform-specific
native log sink:

  - Android: __android_log_write under tag "flet.python"
    (INFO for stdout, ERROR for stderr — visible via
    `adb logcat -s flet.python:*`)
  - iOS/macOS: os_log_with_type (OS_LOG_TYPE_DEFAULT / _ERROR),
    visible in Console.app
  - Other (desktop Linux/Windows): fwrite passthrough — preserves
    `flet run` console output and CI logs.

Installed by dart_bridge_install_stdio_redirect(), called from
sp_run_python() immediately after Py_Initialize so even bootstrap-time
prints (sys.path injection, module loads, top-level user-app statements)
land in the platform log instead of vanishing into /dev/null.

The implementation lives at the Python sys.stdout layer (not fd 1/2
replacement via dup2 + pipe + reader thread) — simpler, no extra
thread, catches print() and traceback writes directly. Native crashes
that bypass Python's stdio aren't captured here.

## Other changes

- dart_bridge_clear_handlers also clears the session-restart subscriber
  list, since those PyObject* refs are held the same way as port handlers.
- CMakeLists.txt: version bump 1.2.2 → 1.3.0 (binary-compatible adds;
  existing consumers keep working without code changes).

Smoke-tested locally on macOS: dylib builds clean, all new symbols
present in `nm -gU`, ctypes-harness exercise of the "no embedded
interpreter" code paths doesn't crash.
@FeodorFitsner FeodorFitsner merged commit 73b8445 into main Jun 16, 2026
14 checks passed
@FeodorFitsner FeodorFitsner deleted the process-reuse branch June 16, 2026 18:50
@FeodorFitsner FeodorFitsner restored the process-reuse branch June 16, 2026 18:51
@FeodorFitsner FeodorFitsner deleted the process-reuse branch June 16, 2026 20:26
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.

1 participant