YouTube Music: ANR on playback — runaway AbstractFuture.toString() recursion (~12k chained futures) #1504
Unanswered
simonbaren
asked this question in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
Patched YouTube Music freezes and is killed by the system a few seconds to a few minutes
after playback starts. It presents as a crash, but there is no exception and no native
crash — it is an ANR. The main thread is
Runnable(never blocked), spinning inside arecursive
toString()walk over a chain of ~11,000–12,700 chained GuavaAbstractFutureobjects, building a multi-hundred-megabyte string until the 5 s input-dispatch deadline
expires and
ActivityManagerforce-finishes the process.Stream spoofing itself works correctly. The failure is in the async plumbing, not in
stream acquisition.
Environment
app.revanced.android.apps.youtube.music8.40.54 (versionCode 84054240)app.revanced.manager.flutter)app.revanced.android.gms0.3.13.3.250932 (250932006)OnePlus/CPH2449/OP594DL1:16/TP1A.220905.001/T.R4T3.457a297-20cee51-20cee58:user/release-keysANDROID_REEL_NO_AUTH(default)Unpatched YouTube Music 8.40.54 on the same device does not exhibit this.
Steps to reproduce
app is killed.
Idle launch alone is stable (verified >2 min with no playback). Playback is the trigger.
If a queue is restored at startup, playback begins automatically and it appears to "crash
on launch".
Evidence
It is an ANR, not a crash
logcat -b crashis empty; noFATAL EXCEPTION, no tombstone, across all captures.Main thread: recursive
toString()over a future chainThe thread is
Runnableand burning CPU (utm=326 stm=171), not waiting on a lock.bhnjis a GuavaAbstractFutureConfirmed from an unrelated stack in the same process, which shows the
AbstractFutureAPI surface on the same class:bhnw.gWis the nested-future accessor (pendingToString-equivalent). The recursion isAbstractFuture.toString()→"setFuture=[" + delegate + "]"→ delegate'stoString(),walked over an unbounded
setFuturechain.Chain depth across five independent ANR captures
bhnj.toStringbhnw.gW(chain links)Highly reproducible and configuration-independent.
Memory signature
The partially-built string dominates the heap; GC runs roughly every 100 ms:
Single Large-Object-Space allocations of 90–227 MB. (
LARGE_HEAPis set, so the processsurvives long enough to ANR rather than OOM.)
Stream spoofing is working — it is not the cause
With ReVanced debug logging enabled, every streaming request succeeds:
No
Playback errorlines for the default client, and playback ran ~3 minutes acrossseveral tracks before the ANR. (
ANDROID_REEL— not the default — does returnPlayback error (App is outdated?) ANDROID_REEL: 400 response: Bad Request.)Ruled out
Spoof video streams patch — removing it changes the failure mode but not the outcome;
the identical recursion occurs with it on and off (captures 1–2 vs 3–5). Verified in the
decompiled APK that no app class references
SpoofVideoStreamsPatchwhen deselected.Spoof client selection — all selectable clients tried; no change.
App version — 8.40.54 is the suggested version.
Patch bundle age — v6.2.1 is current.
GmsCore version — reproduced on both 0.3.13.2 and 0.3.13.3.
GmsCore background restrictions — already battery-whitelisted
(
user,app.revanced.android.gms,10530),RUN_ANY_IN_BACKGROUND: allow. TheCached(nirvana)kill of…gms:persistentoccurs after the failures begin.App data corruption — clearing data only removes the saved queue (so nothing
auto-resumes at launch); playback still reproduces it.
Logged errors as the driver — nothing logged occurs at anywhere near chain scale:
Empty persistent device token received from InnerTubeYT.onesie … Response code: 413Service not supported: 161Invalid playback type; streaming data is not playableThe chain accumulates silently, which points at a retry/continuation path that calls
setFutureon a growing chain rather than replacing it.Suspected cause
An operation on the playback path retries by chaining
setFutureonto the same futureinstead of replacing the delegate, so the chain grows without bound for the lifetime of the
process. Any later stringification of the head — e.g. Guava's
AbstractFuture.get(long, TimeUnit)timeout path, which embedstoString()in itsTimeoutExceptionmessage — then walks the whole chain on the main thread.Two independent bugs are implied:
setFuturechain (the leak).self-reference but not against long chains.
Secondary finding: GmsCore phenotype action mismatch
Independent of the above, and worth fixing separately.
Patched YouTube Music requests the un-renamed action against the renamed package:
APK string table:
com.google.android.gms.phenotype.service.STARTapp.revanced.android.gms.phenotype.service.STARTBut the provider authority was rewritten (
app.revanced.android.gms.phenotype, 3occurrences) — so the rewrite is inconsistent: authority renamed, service action not.
GmsCore registrations (
aapt2 dump xmltree):app.revanced.android.gms.phenotype.service.STARTcom.google.android.gms.phenotype.service.STARTSo patches v6.2.1 pair correctly only with 0.3.13.3. On 0.3.13.2 the bind can never
succeed (28 failures in 13 s → 0 after upgrading).
Note also that the GmsCore version check misparses the
-usersuffix and reports adowngrade as an upgrade:
Diagnostic method
adb logcat -b allplusdumpsys dropbox --printfor thedata_app_anrentries and/data/anr/anr_*stacks (unreadable directly on this non-rooted device). ReVanced debuglogging (
revanced_debug+revanced_debug_stacktrace) enabled for the final capture.APK inspected with
aapt2 dumpandapktool2.7.0.All reactions