Skip to content

ANR on AG-216: RenderThread blocked in eglSwapBuffers with 8 multi-view SurfaceViews #76

Closed

Description

@compot-dev

Seen twice on the AG-216 Android TV box (Amlogic p281, Android 10, Mali-G31) during device tests of the 8-slot multi-view:

Time (2026-10-12) Build Situation
20:55 #64 (8 slots, SurfaceView cells from #63) 3×3 grid with 8 live cameras, menu interaction
22:45 #71 (UDP cell) 3×3 grid → tap on the ninth cell (menu) → U (8 cells stopped) → tap

am_anr: Input dispatching timed out … head age 38–45 s. The system then finished the activity.

Stacks (dumpsys dropbox --print data_app_anr), the same both times:

  • main: ViewRootImpl.performDraw → HardwareRenderer.nSyncAndDrawFrame → DrawFrameTask::postAndWait, i.e. waiting for the RenderThread.
  • RenderThread: eglSwapBuffers → libGLES_mali display_window_buffer → Surface::queueBuffer → binder transaction to SurfaceFlinger, blocked.

The app's own threads were idle. The compositor did not accept the window's next buffer for about 40 s. Before that, the screen showed a frozen frame while input was still queued.

Context: SurfaceFlinger used about 40 % CPU and the box load average was up to 30. Other apps (Play Store ANRs, a video app, Settings) were active on the same box at the time, so the box may simply have been overloaded. With TextureView cells (before #63) the app gave SurfaceFlinger one layer; with SurfaceView cells it gives up to 8 more.

To do (deferred): reproduce on an idle box, comparing 4 and 8 cells and TextureView with SurfaceView. If the extra layers are the cause, consider TextureView for the 3×3 grid on devices with few HWC overlay planes, or fewer layer changes when leaving the grid.

Activity

  1. added
    bugSomething isn't working
    deferredPostponed, not planned for now
    todayTasks for today
    on Oct 12, 2026
  2. compot-dev commented on Oct 13, 2026

    @compot-dev
    CollaboratorAuthor

    Seen a third time, 2026-10-13 14:13, Aceline-1 (build 0242d14), an otherwise idle box (Wi‑Fi off, no other apps in use), while switching slots 2 and 3 to UDP through the URL dialog: 3×3 grid (8 cells + U, all SurfaceView) → tap slot 2 (grid stopped, single view with its TextureView) → Settings dialog → UDP → Save → menu → tap 3.

    • OpenGLRenderer: Davey! duration=48624ms … QueueBufferDuration=48550217000 — the RenderThread waited 48.5 s in queueBuffer, i.e. SurfaceFlinger did not take the window's buffer; Choreographer: Skipped 2914 frames.
    • am_anr: Input dispatching timed out … Wait queue head age: 42159.8 ms; load average 8.8 (1 min) — lower than the earlier cases, so an overloaded box is not required.
    • The Java traces were written a second after the RenderThread recovered, so they show it idle and main in TextureView.onFrameAvailable.

    Common to all three cases: the window's layer set changes a lot at once (the grid of up to 9 SurfaceViews torn down, the TextureView of the single view and a dialog or popup appearing). Next step (planned today after #113): reproduce with a script (grid ↔ single ↔ dialog in a loop) and compare with fewer SurfaceViews / hiding the grid before removing it.

  3. compot-dev commented on Oct 13, 2026

    @compot-dev
    CollaboratorAuthor

    Status 2026-10-13 15:35 — reproducible, cause not found yet (time-boxed).

    Repro script on Aceline-1 (idle box, Wi‑Fi off): menu → slot 4 (grid → single), 4 s, menu → Q (single → grid), 8 s; repeat. Every run of 8–10 cycles gives 2–5 Davey! stalls of 3–68 s (QueueBufferDuration = the whole stall, i.e. SurfaceFlinger does not take the window's buffer) and usually an ANR; the longest ones come on single → grid. Once media.codec hung in D state (load 74) and only a reboot helped.

    Ruled out (PR #127, closed):

    • releasing grid decoders before their surfaces are destroyed (holder callback kept until surfaceDestroyed) — no change;
    • creating the decoders one at a time, 250 ms apart — no change.

    Next ideas (when this is picked up again):

    1. Capture SurfaceFlinger during a stall: dumpsys SurfaceFlinger (layer count, HWC composition type), dumpsys SurfaceFlinger --latency, top -H of surfaceflinger / composer / media.codec.
    2. Compare a 2×2 grid (4 SurfaceViews) with 3×3 (9) and slides mode (Menu: S — slide mode, multi-view on key frames only for every camera #108, low decode load) — separates layer count from decode load.
    3. Keep the grid's views alive (hide/show, no removeAllViews + rebuild) and only stop/start the sessions — fewer layer creations/destructions per switch.
    4. TextureView cells on this device class (one app layer; costs GPU composition) as an option.
  4. compot-dev commented on Oct 13, 2026

    @compot-dev
    CollaboratorAuthor

    One more data point (2026-10-13 16:55, Aceline-1, 6 cycles grid ↔ single): stalls of 24.5 s and 20.9 s again, but dumpsys SurfaceFlinger answered every ~4 s throughout them — SurfaceFlinger itself is not blocked. The window's own queueBuffer waits, i.e. the app's BufferQueue to SurfaceFlinger (a buffer not released back / a fence not signalled) rather than a frozen compositor. Next capture: dumpsys SurfaceFlinger --latency <app layer> and the app window's BufferQueue state during a stall, plus a run with the grid hidden (not removed) on single view.

  5. added
    in progressBeing worked on right now (open PR)
    and removed
    deferredPostponed, not planned for now
    on Oct 13, 2026
  6. compot-dev commented on Oct 13, 2026

    @compot-dev
    CollaboratorAuthor

    Status 2026-10-13 evening (Aceline-1, idle box) — still reproducible, two more ideas ruled out.

    Repro (script: grid → slot 4 single, 4 s → Q grid, 8 s; 8 cycles), stalls (Davey! = QueueBufferDuration) and ANRs:

    build / setting stalls (ms) ANR
    v2 (af88fb7-based) 65320 1
    grid views kept and reused, no removeAllViews + rebuild (experiment) 40184, 57024, 57046 2
    S on: every cell on key frames (~1 fps each), single view 1 fps too 5699, 5653, 4068, 52903, 52816 4

    So neither creating/destroying the 9 SurfaceView layers nor the decode/update rate is the cause.

    Captured during a stall (dumpsys SurfaceFlinger, grid on screen): 8 of the 9 SurfaceView layers are composed by the GPU (CLIENT), one goes to an HWC overlay (DEVICE); the 1920×1080 / 2560×1920 YUV sources are scaled to 426×240 by Mali-G31 composition. surfaceflinger ~42 % CPU, load 6.9, 216 MB RAM free. The app window's own buffer still waits in queueBuffer for tens of seconds.

    Next ideas (not tried):

    1. TextureView (or one GL surface) for the grid — the app composes the cells itself, SurfaceFlinger gets one layer (Quad: frequent blocking GC with four streams #61 found 1 MB/s of GC garbage with TextureView frame callbacks; would need a callback-free path).
    2. A 2×2 grid against 3×3 on the same box (layer count only).
    3. Test on another device class (the stalls may be specific to this Mali/HWC driver).
      Time-boxed here; back to deferred.
  7. added
    deferredPostponed, not planned for now
    and removed
    in progressBeing worked on right now (open PR)
    on Oct 13, 2026
  8. 4 remaining items

  9. compot-dev commented on Oct 14, 2026

    @compot-dev
    CollaboratorAuthor

    Status 2026-10-14: not reproducible right after a reboot.

    Aceline-1 (.144), v2.01, rebooted at ~15:45 MSK (it had been up ~21 h; at that point cells 6–8 were stuck at Paused: decoder overloaded even on key frames). Same repro as yesterday: grid → slot 4 single (4 s) → Q grid (8 s), 8 cycles, driven by D-pad. Every cycle rebuilt the grid (checked in logcat).

    run (box up ~1 h) stalls ≥ 2 s ANR
    3×3, slot 2 = HTTP MJPEG one, 3.1 s 0
    3×3, slot 2 = RTSP H.264 (yesterday's layout) none 0

    Yesterday the same script gave stalls of 40–65 s on every run. Those runs came after long test sessions, on the same day as the decoder hang (#145).

    New hypothesis: the stalls come from the box degrading with uptime or use (GPU/codec driver memory), not from the grid switch itself. Running now: a soak test on .144 (v2.02) that keeps the 3×3 grid playing and, every 40 min, logs /proc/meminfo, the app's graphics memory and an 8-cycle repro. This will show whether the stalls come back as uptime grows.

  10. compot-dev commented on Oct 14, 2026

    @compot-dev
    CollaboratorAuthor

    Status 2026-10-14 evening: the stalls build up inside the app process; restarting the app clears them.

    Aceline-1 (.144), v2.02, same repro (8 cycles of grid → slot 4 single → grid, D-pad):

    state stalls ≥ 2 s (ms) ANR
    box rebooted, app fresh (~16 cycles) 3128, then none 0
    soak, box up 63 min (~40 cycles) 10028 6390 6382 3481 2101 0
    soak, box up 116 min (~48 cycles) 20069 18076 18104 18171 29032 2032 0
    app force-stopped and started, box not rebooted (box up 2 h) 3048 0
    the same app process, 16 more cycles 22167 50790 3409 50955 50914 2

    So whatever builds up belongs to the app process, not to the box: a restart of the app alone brings it back to the clean state.

    What does not grow with the cycles:

    • threads (76–79);
    • native heap (~355 MB allocated right after start, the same later; that is the 9 decoders);
    • SurfaceFlinger layers (exactly 9 SurfaceView layers in the grid) and gralloc buffers (~100, about 11 per cell);
    • decoder instances (dumpsys media.resource_manager: 9 codecs for the live process; dead PIDs are listed with no clients);
    • hwui GPU caches. They depend on the view, not on the cycles: in the grid 2 textures (9 MB), in the single view 13 (97 MB = the TextureView's 1920×1088 frame buffers), released again on the way back.

    Not visible without root: the Mali driver's per-process GPU memory, and the app window's BufferQueue (the stall is the window's own queueBuffer).

    Next (another day):

    1. Single view on a SurfaceView instead of the TextureView. The TextureView's frames go through the app's own GL composition and its window buffer, which is the buffer that waits.
    2. Whether the time per cycle grows from the first switch on: per-cycle Davey! durations in one long run after an app restart.
  11. added
    tomorrowPlanned for the next day
    todayTasks for today
    and removed
    todayTasks for today
    tomorrowPlanned for the next day
    on Oct 14, 2026
  12. compot-dev commented on Oct 15, 2026

    @compot-dev
    CollaboratorAuthor

    Status 2026-10-15: a SurfaceView for the single view removes the stalls.

    Aceline-1 (.144), box up ~30 min, same 3×3 layout. Each run starts from a fresh app process and does 60 cycles of grid → slot 4 single (4 s) → grid (8 s), driven by D-pad. Davey! lines of the app process are counted per cycle.

    build cycles Davey! ≥ 1 s ≥ 2 s longest ANR
    v2.07 (318a5a4), single view on a TextureView 1–30 23 20 7 4.0 s 0
    31–60 17 16 10 14.0 s 0
    experiment (7938365, branch experiment/surfaceview-76), single view on a SurfaceView 1–30 2 2 0 1.4 s 0
    31–60 2 2 0 1.3 s 0

    With the TextureView the stalls grow with the cycles in one process, as seen yesterday: up to 14 s within 60 cycles. With the SurfaceView there are almost no stalls and no growth. So the build-up is in the TextureView path, where frames go through the app's own GL composition and window buffer.

    The experiment is not ready to merge. It loses three things that need a proper replacement:

    1. Pinch-zoom and pan in single view: a SurfaceView has no setTransform(). It needs scaling or translating the view itself, or a crop.
    2. Long-press screenshot and the debug-page snapshot of the single view now use PixelCopy, which needs API 24+. It also must not block the UI thread.
    3. The switch from MJPEG to H.264/H.265 in single view hides and shows the view to get a fresh surface. This is untested.

    Next: a real PR with the single view on a SurfaceView that covers these three points.

  13. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtodayTasks for today

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions