WIP: fix(0.23.0): the slice wire starves the reassembler, and three presenter defects behind it #21
Closed
enricobuehler
wants to merge 3 commits from
worktree-android-presenter-023-triage into main
pull from: worktree-android-presenter-023-triage
merge into: :main
:main
:worktree-july-rpm-and-cursor
:worktree-native-decode-m0
:worktree-release-0250
:worktree-presenter-cadence-wp1
:worktree-decky-brand-name
:worktree-win-amd-decoder-order
:worktree-stall-ride-through
:fix/pad-audio-wasapi-module-path
:feat/android-pad-audio
:worktree-adl-emul-probe
:worktree-haptics-m12-dry
:worktree-haptics-m9-richfb
:worktree-haptics-m11-settings
:worktree-apple-store-screenshots
:worktree-decky-slim-rework
:worktree-appstore-copy
:worktree-haptics-m10-wire
:worktree-haptics-m8-proto
:worktree-haptics-m7-windows
:worktree-haptics-m6-presenter
:worktree-wire-mtu-resilience
:worktree-haptics-m5-android
:worktree-haptics-m4-apple
:worktree-haptics-m3-rumble-engine
:worktree-android-presenter-023-triage
:worktree-vdisplay-resume-recovery
:worktree-kwin-vout-demirror
:fix/web-console-sweep
:fix/windows-web-console-lifecycle
:chore/windows-rerender-semantics
:fix/pf-encode-phase8
:fix/pf-encode-phase7
:feat/cursor-channel
:feat/desktop-mouse-mode
:feat/plugin-kit
:fix/network-split-standing-latency
:fix/windows-msix-arm64-and-manifest
:fix/encode-medium-tier
:fix/encode-rfi-taint-and-oob
:fix/encode-init-leak-and-twin-drift
:fix/encode-teardown-uaf
:feat/pyrowave-windows
:fix/ubuntu-2404-host-deb
:fix/ios-archive-provisioning
:merge/first-frame-latency
:feat/shared-clipboard-v2
:feat/apple-live-activities-widgets
:debug/touch-finger-logging
:feat/shared-clipboard
:midstream-resize
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
61547b512a |
fix(android/decode): a codec input slot the feeder can't fill goes back, and so does the AU
apple / swift (pull_request) Successful in 1m19s
apple / screenshots (pull_request) Skipped
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m7s
ci / rust-arm64 (pull_request) Successful in 2m20s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m6s
android / android (pull_request) Successful in 3m43s
ci / web (pull_request) Successful in 1m26s
ci / docs-site (pull_request) Successful in 2m6s
ci / rust (pull_request) Successful in 6m30s
`AMediaCodec_getInputBuffer` returning null for an index the input-available callback had just handed us dropped both the slot and the access unit on the floor. Every sibling path in this loop recycles the slot — the orphan-part discard and the oversize drop both say so in as many words — because nothing was written and nothing was queued, so it is still ours. Forgetting it leaks one of the codec's input buffers per occurrence: we never use it again and the codec never frees what it never received, so the pipeline runs out of input slots, `pending_aus` overflows into its drop-oldest arm, and the resulting keyframe storm reads as a decode fault rather than a bookkeeping one. The AU went with it, silently — no keyframe request, no freeze gate, unlike every other loss path here — leaving a hole in the reference chain whose concealment was free to reach the screen. Both go back now. `break` rather than `continue`, because a codec that cannot hand out an input buffer it has just advertised is in no state to be fed the rest of the parked queue on this pass, and retrying the same index against every parked AU would burn the whole backlog for nothing; the loop comes round again on the housekeeping wake within 5 ms if it was transient. Gates: cargo ndk check green on arm64 and armv7, fmt clean, Android clippy at the same 4 pre-existing warnings as the base commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
166c93c079 |
fix(android/present): the panel grid can be wrong in both directions, and the margin listens to the latch
android / android (pull_request) Canceled after 1m21s
apple / swift (pull_request) Successful in 1m18s
apple / screenshots (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 1m32s
ci / rust-arm64 (pull_request) Canceled after 1m28s
ci / web (pull_request) Canceled after 0s
ci / docs-site (pull_request) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (pull_request) Canceled after 1m31s
windows / build (x86_64-pc-windows-msvc) (pull_request) Canceled after 0s
Three defects in the 0.23.0 timeline presenter, all found while root-causing the field report that turned out to be the slice wire. None of them is that bug; all three are real, and the first is the one that would still bite once it is fixed. The panel-period learner could only ever narrow. It is seeded from the display mode Kotlin asked for — and `preferredDisplayModeId` is a REQUEST the system may refuse (Smooth Display off, battery saver, thermal, an OEM governor). Ask for 120 Hz on a panel that stays at 60 and the presenter pins an 8.33 ms grid on a 16.67 ms display with no way back, for the rest of the session: it then aims at instants that never arrive and releases faster than the panel scans. The learner moves both ways now, and lives in `punktfunk_core::phase::PanelGrid` where it is host-testable and where the iOS and desktop presenters can share it. The asymmetry is kept and made explicit — narrowing is immediate (a finer real grid is always safe to subdivide onto, and it is the per-uid down-rate case the seed most often gets wrong), widening needs eight consecutive agreeing observations and then takes the narrowest of them, because one wide sample is a missed callback and eight in a row is a display that really did slow down. The glass budget was a prediction with nothing underneath it. `OnFrameRendered` already reports what actually reached glass, but the budget never consulted it, so a wrong grid could hand SurfaceFlinger frames indefinitely: BufferQueue fills, MediaCodec runs out of output buffers, the decoder stalls, and the no-output backstop starts begging for keyframes. Releases are now counted against their confirms and the presenter holds back past six outstanding — loose on purpose, since the callbacks are allowed to arrive batched and a held frame in the newest-wins slot is a dropped one. It self-clears when the confirms catch up, and writes the ledger off after the same 100 ms the stale reopen uses, so a platform that stops confirming can never wedge the stream. `qWait` and `unconfirmed` join the 1 Hz pf.present line, which is what would have made this visible from a log. The adaptive latch margin widened on `paced_drops` — the newest-wins store's own policy evictions, which happen whenever the stream out-runs the panel and say nothing about SurfaceFlinger's latch lead. On a healthy device that walked the margin to its 2.5 ms ceiling and re-imposed the display latency the P2e sweep had just measured away. It now widens on the measured latch exceeding one panel period plus the live margin, which is what a missed vsync actually looks like. Also corrects two doc comments that named `display.refreshRate` as the panel_hz source; it has been the mode table since the A024 down-rate fix. Gates: 278 punktfunk-core lib tests (7 new PanelGrid cases incl. the refused-mode regression), clippy -D warnings and fmt clean, cargo ndk check green on arm64 and armv7. Android clippy reports the same 4 warnings as the base commit and no new ones. NOT yet confirmed on glass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8749bd1396 |
fix(core/packet): a slice-streamed frame costs its own size, not the whole frame ceiling
ci / web (pull_request) Successful in 1m4s
ci / rust-arm64 (pull_request) Successful in 1m42s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m5s
android / android (pull_request) Successful in 2m50s
ci / docs-site (pull_request) Successful in 2m45s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m2s
apple / swift (pull_request) Successful in 1m18s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 12m58s
The 0.23.0 slice wire flushes a block every MIN_STREAM_BLOCK_SHARDS, so every ordinary access unit is now opened by a SENTINEL — a header with no totals. The reassembler sized those frames at `max_frame_bytes`, which the QUIC handshake clamps to 8-64 MiB. That was survivable while sentinels were rare (the streamed path emitted one only for an AU exceeding a whole FEC block, ~281 KB); it is not survivable now that every frame is one. Two consequences, both measured: each access unit allocated and ZEROED a multi-megabyte buffer, and the in-flight budget (IN_FLIGHT_BUF_FACTOR x max_frame_bytes) was spent after ~3 concurrent frames — with production geometry, 12 ordinary AUs in flight lost 9 of them outright, every packet dropped before it could be placed. On a link with normal reorder that is a permanent loss storm: frames never complete, the re-anchor gate freezes the picture, and the client begs for keyframes. Only clients advertising VIDEO_CAP_MULTI_SLICE reach this path — Android and the Linux/Windows session client; Apple and the Windows in-process client never did, which is why it read as a platform-specific "video pipeline" fault in the field. A sentinel carries no total but does pin its own block's extent: a slice sentinel by its wire base, a legacy one by its full-K position. Size the buffer to that and grow as later blocks (or the final block's totals) reveal more. The budget is re-checked on growth for the same reason it is checked at open. The same flush also drained `pending` to empty whenever the AU's length was an exact multiple of the shard payload, leaving `finish_streamed` to seal a final block of one zero-padded FILLER shard. Its derived base overlapped the block flushed a moment earlier, retro-validation correctly read that as a lying header, and the whole AU died — one frame in every 1408 on a 1500-MTU link, ~12 s apart at 120 fps, each costing a freeze and a recovery keyframe. A flush now keeps one whole shard back, restoring the invariant `StreamedAu::pending` already documented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |