You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- The recent fixes made shutdown reliable again: synthetic hangs are gone, native source shutdown is bounded, and the real-source rerun repro now completes.
5
+
- The remaining concern is architectural: stop/close semantics currently work, but they are spread across Python high-level API, Python FFI, and Rust sink/source implementations in a way that feels patchy.
6
+
- In particular, the desired product semantics are now clearer than the code structure: **shutdown should be bounded best-effort cleanup, not guaranteed full pipeline delivery or full drain**.
7
+
- The current code still contains compatibility-oriented and cross-layer shutdown logic that obscures that model.
8
+
- User preference: preserve only the high-level Python API contract; low-level/internal `_macloop` shutdown compatibility may be simplified or broken if that leads to a cleaner design.
9
+
- User preference for high-level close behavior: if a native source does not stop within the bounded timeout, `AudioEngine.close()` should still complete without hanging, but it should surface a warning/log signal rather than silently succeeding or raising by default.
10
+
11
+
## Approach
12
+
Refactor shutdown around a simpler lifecycle contract:
13
+
1.**Engine owns source lifecycle** — native sources start/stop on the engine lifecycle, not on sink lifecycle.
14
+
2.**Sink close only detaches the sink** — bounded worker stop + route lease/consumer return, but no source orchestration.
15
+
3.**Bounded best-effort close is explicit** — native stop timeouts are part of the normal close model, not an exceptional hidden path.
16
+
4.**High-level close warns on deferred native cleanup** — `AudioEngine.close()` remains non-hanging and non-fatal by default, but emits a warning/log when native cleanup is deferred.
17
+
5.**Cross-layer close API becomes explicit** — remove signature probing / fallback behavior and replace it with one clear sink-close contract.
18
+
6.**Allow small semantic cleanup** — internal/FFI shutdown contracts may be cleaned up rather than preserved verbatim if that materially simplifies the design, as long as the high-level Python API remains stable.
19
+
20
+
This should be done incrementally, preserving user-visible reliability while shrinking the shutdown state machine.
21
+
4.**Cross-layer close API becomes explicit** — remove signature probing / fallback behavior and replace it with one clear sink-close contract.
22
+
5.**Allow small semantic cleanup** — internal/FFI shutdown contracts may be cleaned up rather than preserved verbatim if that materially simplifies the design, as long as the high-level Python API remains stable.
23
+
24
+
This should be done incrementally, preserving user-visible reliability while shrinking the shutdown state machine.
25
+
26
+
## Files to modify
27
+
-`macloop/__init__.py`
28
+
-`python_ffi/src/lib.rs`
29
+
-`core_engine/src/outputs/asr_sink.rs`
30
+
-`core_engine/src/outputs/wav_file.rs`
31
+
-`core_engine/src/engine.rs`
32
+
-`tests/test_runtime_helpers.py`
33
+
-`tests/test_e2e_synthetic.py`
34
+
-`tests/test_medium_e2e_real_capture.py`
35
+
- possibly `tests/test_ffi_backend.py`
36
+
37
+
## Reuse
38
+
- Bounded native lifecycle helpers already exist in `python_ffi/src/lib.rs`:
39
+
-`start_native_source_with_deadline(...)`
40
+
-`stop_native_source_with_deadline(...)`
41
+
-`cleanup_pending_cleanups_with_deadline(...)`
42
+
- Route consumer handoff/restore already exists and should be reused rather than re-invented:
43
+
-`AudioEngineController::take_output_consumer(...)` in `core_engine/src/engine.rs`
44
+
-`AudioEngineController::restore_output_consumer(...)` in `core_engine/src/engine.rs`
45
+
-`restore_route_consumers(...)` in `python_ffi/src/lib.rs`
46
+
- Bounded worker shutdown patterns already exist in Rust sinks and should stay as the basis:
47
+
- bounded polling in `core_engine/src/outputs/asr_sink.rs`
48
+
- bounded polling in `core_engine/src/outputs/wav_file.rs`
49
+
- Existing regression coverage to preserve during refactor:
-[ ] Step 1: Document and codify the target lifecycle contract in code comments/tests:
56
+
- engine close = bounded best-effort source shutdown
57
+
- sink close = bounded detach + route return
58
+
- close does not guarantee full drain/delivery
59
+
-[ ] Step 2: Simplify Python high-level close flow in `macloop/__init__.py` so it no longer performs feature detection via exception shape/signature fallback, and define one clear warning/log path for deferred native cleanup.
60
+
-[ ] Step 3: Simplify FFI sink close API in `python_ffi/src/lib.rs` into one explicit contract for route restoration instead of optional engine-aware probing, even if that changes low-level/internal `_macloop` shutdown semantics.
61
+
-[ ] Step 4: Keep source lifecycle orchestration centralized in `PyAudioEngineBackend`, and remove any remaining sink/source lifecycle coupling that is only there for historical shutdown bugs.
62
+
-[ ] Step 5: Separate explicit sink shutdown semantics from fallback/drop cleanup semantics in Rust sink implementations, so `Drop` remains best-effort cleanup and explicit close remains the ownership-return path.
63
+
-[ ] Step 6: Revisit route ownership bookkeeping and reduce duplicate state where possible (Python `_claimed_routes` vs backend consumer availability), without regressing same-engine restart behavior.
64
+
-[ ] Step 7: Tighten and align tests around the simplified model, especially:
65
+
- backend timeout is non-hanging and best-effort
66
+
- high-level `engine.close()` emits the intended warning/log when native cleanup is deferred
67
+
- same-engine restart still works
68
+
- real-source rerun stays stable
69
+
- low-level tests are updated to validate the new internal contract only where still intentionally supported
0 commit comments