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
FCMBroadcastReceiver ANRs on Android when a push arrives while OneSignal’s 2-thread IO pool is blocked on init HTTP to api.onesignal.com (android_params.js and sdk/features). goAsync() does not extend the ~10s broadcast timeout, so pendingResult.finish() never runs in time.
This is not a main-thread Application.onCreate stall. initWithContext returns in ~100ms on the main thread; the ANR is the broadcast watchdog.
Related: #2709 (open). Play Vitals data there showed 5.9.5 prewarm did not reduce FCMBroadcastReceiver ANR share. That issue attributes the problem to dispatcher construction race. Our traces show the dispatchers are already warm and both IO workers are stuck in TCP connect — pool saturation + timeout mismatch (and on many devices, IPv6 with no Happy Eyeballs).
Also related (different root cause, same symptom string): #1542, #1436, #2324, #2619.
Environment
SDK
5.9.8 and 5.9.9 (SDK-Version=onesignal/android/050908 and 050909). Also reproduced under Gradle dynamic range [5.6.1, 5.9.99].
Device
Xiaomi Mi 10 (umi), Android 13 / MIUI
App
Process started by FCM or already in foreground; OneSignal.initWithContext(applicationContext, appId) from Application.onCreate
Network
China Mobile; device has a global IPv6 address; api.onesignal.com has AAAA (Cloudflare)
Repro
Force-stop the app (or wait until the process is dead).
Send an FCM / OneSignal push so Play services delivers com.google.android.c2dm.intent.RECEIVE (or open the app and send a push in the first ~60s while init HTTP is still in flight).
Within ~10s: ANR Broadcast of Intent { act=com.google.android.c2dm.intent.RECEIVE cmp=.../com.onesignal.notifications.receivers.FCMBroadcastReceiver }.
Warm process after init HTTP has finished: later pushes usually do not ANR.
Timeline (5.9.8, one occurrence)
Times local GMT+8, pid 20919:
Time
Event
16:19:03
OneSignalImp initialization completed on main (~96ms)
16:19:03–04
GET .../android_params.js on OneSignal-IO-2; GET .../sdk/features/android/050908 on OneSignal-IO-1
~16:19:24
FCM google.sent_time
16:19:35
System ANR (broadcast ~10s)
16:20:08
Both HTTP responses (~64s); then NotificationWorkManager enqueueing notification work
Same pattern on 5.9.9 with the app already in the foreground: init finished in 132ms; both GETs still connecting; Timeout of broadcast ... started 10001ms ago → ANR.
What the ANR dump shows
Main thread: typically idle (MessageQueue.nativePollOnce). Sometimes unrelated Compose draw. Not blocked in OneSignal.
at libcore.io.Linux.poll
at libcore.io.IoBridge.isConnected
at libcore.io.IoBridge.connectErrno
at java.net.PlainSocketImpl.socketConnect
at java.net.Socket.connect
at com.android.okhttp.internal.io.RealConnection.connectSocket
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode
at com.onesignal.core.internal.http.impl.HttpClient$makeRequestIODispatcher$job$1.invokeSuspend(HttpClient.kt:206)
at com.onesignal.common.threading.ThreadUtilsKt$launchOnIO$1.invokeSuspend
at com.onesignal.common.threading.OneSignalDispatchers$launchOnIO$1.invokeSuspend(OneSignalDispatchers.kt:202)
SDK code (5.9.9) that matches this
FCMBroadcastReceiver.onReceive: OneSignalDispatchers.prewarm() then goAsync() then suspendifyOnIO { initWithContext; processBundle; pendingResult.finish() }.
OneSignalDispatchers: ThreadPoolExecutor(core=2, max=3, LinkedBlockingQueue(200)). JDK semantics: queue fills before max, so effective concurrency is 2.
ConfigModel.httpGetTimeout default 60000ms; HttpClient applies it to connect and read timeout. One hung GET holds an IO thread for up to 60s.
Cold start fires two GETs at once (android_params.js + sdk/features) → both IO threads busy.
sequenceDiagram
participant Play as PlayServices
participant Rx as FCMBroadcastReceiver
participant IO as OneSignal_IO_pool_2
participant API as api.onesignal.com
Note over IO: GET android_params.js
Note over IO: GET sdk/features
IO->>API: TCP connect (can hang 60s)
Play->>Rx: c2dm RECEIVE
Rx->>Rx: goAsync
Rx->>IO: suspendifyOnIO processBundle
Note over IO: queued, both workers in connect
Note over Rx: 10s, finish never called, ANR
Loading
IPv6 / Happy Eyeballs (why connect hangs)
Same device, same URL, same session:
Client
Result
curl (Happy Eyeballs, IPv4 fallback)
connect ~0.3s, HTTP 200, total <1s
curl -6
fails after ~15s (code=000)
SDK HttpURLConnection
same request in-flight 10–64s; ANR dump in IoBridge.connectErrno
Device has a global IPv6 address.
api.onesignal.com has AAAA (Cloudflare). IPv6 path to that anycast is broken on this carrier.
Java HttpURLConnection / InetAddress has no Happy Eyeballs (RFC 8305); it serializes addresses and sits on IPv6 until connectTimeout (60s).
System.setProperty("java.net.preferIPv4Stack", "true") in Application.attachBaseContext did not change SDK connect behavior on this device.
This also fits #2709’s device mix (emerging-market OEMs where IPv6 is on but Cloudflare IPv6 routing is poor).
What we already ruled out (app side)
Main-thread OneSignal.User / feature-gate runBlocking (separate ANR class; we moved those reads off main — main is idle in these dumps).
Native POST_NOTIFICATIONS vs OneSignal.Notifications.requestPermission.
R8 / missing -keep (staging/debug, minify off; stacks show full OneSignal names).
UncaughtExceptionHandler (this is a watchdog timeout, not a crash).
Downgrade-only experiments: 5.9.8 reproduced; 5.9.9 reproduced. [Bug]: One Signal causing too many ANRS #2619 also reported the same broadcast ANR string on 5.7.7 (different inner stack).
App FirebaseMessagingService: Play still delivers c2dm to OneSignal’s receiver at priority 999 first.
Requested fix
Any of these would close the 10s window:
Do not share the 2-thread IO pool with goAsync work. Use a dedicated thread/pool (or Dispatchers.IO unbounded) so processBundle + finish() cannot wait behind 60s connects.
Enqueue WorkManager thenfinish()immediately — you already have NotificationWorkManager; today enqueue still sits behind the hung GETs.
Happy Eyeballs or IPv4-first for HttpURLConnection (or switch the SDK HTTP stack to OkHttp, which does HE).
Much shorter connect timeout for init GETs (seconds, not 60s), without blocking the FCM receiver on those calls.
prewarm() only helps pool construction, not workers stuck in connect().
Happy to attach a truncated dropbox excerpt (IO threads only) if useful.
Summary
FCMBroadcastReceiverANRs on Android when a push arrives while OneSignal’s 2-thread IO pool is blocked on init HTTP toapi.onesignal.com(android_params.jsandsdk/features).goAsync()does not extend the ~10s broadcast timeout, sopendingResult.finish()never runs in time.This is not a main-thread
Application.onCreatestall.initWithContextreturns in ~100ms on the main thread; the ANR is the broadcast watchdog.Related: #2709 (open). Play Vitals data there showed 5.9.5
prewarmdid not reduceFCMBroadcastReceiverANR share. That issue attributes the problem to dispatcher construction race. Our traces show the dispatchers are already warm and both IO workers are stuck in TCP connect — pool saturation + timeout mismatch (and on many devices, IPv6 with no Happy Eyeballs).Also related (different root cause, same symptom string): #1542, #1436, #2324, #2619.
Environment
SDK-Version=onesignal/android/050908and050909). Also reproduced under Gradle dynamic range[5.6.1, 5.9.99].umi), Android 13 / MIUIOneSignal.initWithContext(applicationContext, appId)fromApplication.onCreateapi.onesignal.comhas AAAA (Cloudflare)Repro
com.google.android.c2dm.intent.RECEIVE(or open the app and send a push in the first ~60s while init HTTP is still in flight).Broadcast of Intent { act=com.google.android.c2dm.intent.RECEIVE cmp=.../com.onesignal.notifications.receivers.FCMBroadcastReceiver }.Warm process after init HTTP has finished: later pushes usually do not ANR.
Timeline (5.9.8, one occurrence)
Times local GMT+8, pid 20919:
OneSignalImp initialization completedon main (~96ms)GET .../android_params.jsonOneSignal-IO-2;GET .../sdk/features/android/050908onOneSignal-IO-1google.sent_timeNotificationWorkManager enqueueing notification workSame pattern on 5.9.9 with the app already in the foreground: init finished in 132ms; both GETs still connecting;
Timeout of broadcast ... started 10001ms ago→ ANR.What the ANR dump shows
Main thread: typically idle (
MessageQueue.nativePollOnce). Sometimes unrelated Compose draw. Not blocked in OneSignal.OneSignal-IO-1andOneSignal-IO-2(both Native /poll):SDK code (5.9.9) that matches this
FCMBroadcastReceiver.onReceive:OneSignalDispatchers.prewarm()thengoAsync()thensuspendifyOnIO { initWithContext; processBundle; pendingResult.finish() }.OneSignalDispatchers:ThreadPoolExecutor(core=2, max=3, LinkedBlockingQueue(200)). JDK semantics: queue fills before max, so effective concurrency is 2.ConfigModel.httpGetTimeoutdefault 60000ms;HttpClientapplies it to connect and read timeout. One hung GET holds an IO thread for up to 60s.android_params.js+sdk/features) → both IO threads busy.goAsync()does not extend it (Chronic background ANRs (FCMBroadcastReceiver, NotificationDismissReceiver, BootUpReceiver, SyncJobService) unchanged after 5.9.5 prewarm (PR #2664) — version-code data #2709). 60s vs 10s.IPv6 / Happy Eyeballs (why connect hangs)
Same device, same URL, same session:
curl(Happy Eyeballs, IPv4 fallback)curl -6code=000)HttpURLConnectionIoBridge.connectErrnoapi.onesignal.comhas AAAA (Cloudflare). IPv6 path to that anycast is broken on this carrier.HttpURLConnection/InetAddresshas no Happy Eyeballs (RFC 8305); it serializes addresses and sits on IPv6 untilconnectTimeout(60s).System.setProperty("java.net.preferIPv4Stack", "true")inApplication.attachBaseContextdid not change SDK connect behavior on this device.This also fits #2709’s device mix (emerging-market OEMs where IPv6 is on but Cloudflare IPv6 routing is poor).
What we already ruled out (app side)
OneSignal.User/ feature-gaterunBlocking(separate ANR class; we moved those reads off main — main is idle in these dumps).POST_NOTIFICATIONSvsOneSignal.Notifications.requestPermission.-keep(staging/debug, minify off; stacks show full OneSignal names).UncaughtExceptionHandler(this is a watchdog timeout, not a crash).FirebaseMessagingService: Play still deliversc2dmto OneSignal’s receiver at priority 999 first.Requested fix
Any of these would close the 10s window:
goAsyncwork. Use a dedicated thread/pool (orDispatchers.IOunbounded) soprocessBundle+finish()cannot wait behind 60s connects.finish()immediately — you already haveNotificationWorkManager; today enqueue still sits behind the hung GETs.HttpURLConnection(or switch the SDK HTTP stack to OkHttp, which does HE).prewarm()only helps pool construction, not workers stuck inconnect().Happy to attach a truncated dropbox excerpt (IO threads only) if useful.