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
When the React DevTools backend never answers a getProfilingData request, the agent's profiling store parks in the processing phase forever. isProfilingStarted keeps returning {"isProcessingData": true, "hasProfilingData": false} indefinitely, and stopProfiling returns an empty session with partial: true rather than saying anything went wrong.
waitForProfilingData bounds its own wait, so stopProfiling returns — but the phase itself never leaves processing, so every subsequent poll reports work still in progress that will never finish. A caller polling isProfilingStarted has no terminal state to wait for.
This was one of the two symptoms in #476. The fix in #477 removed the cause that was reachable there — the outbound channel was silently unbound after an app relaunch, so stopProfiling was dropped on the floor and no renderer ever replied. But the store's behaviour when nothing replies is unchanged, and there are other ways to reach it: a renderer that detaches mid-session, a backend that fails to respond, a socket that dies between the request and the reply.
Reproduction
Any condition where a getProfilingData request does not produce a profilingData event. Before #477 this was reproducible directly:
Create a session and confirm profiling works.
Restart the app, then re-create the session against the same device id.
stopProfiling returns zero commits with partial: true.
isProfilingStarted returns isProcessingData: true indefinitely — polled well past 60s.
Suggested solution
Give the processing phase a bounded lifetime in profiling-store.ts, so it resolves to a terminal state on timeout instead of parking. This detects "no renderer answered" regardless of cause, which is the property that matters — it does not require knowing why the reply never arrived.
stopProfiling should then be able to report that no renderer responded, rather than returning an empty session that reads like a successful recording of nothing.
Additional context
One alternative was considered and rejected: propagating the !state.sendMessage guard that getProps already has (store.ts:1484) to startProfiling / stopProfiling. It does not work as a health check.
More importantly, the check never tested delivery. sendReactDevToolsMessage is void sendDomainMessage(...) (session.ts:338), and sendDomainMessage's rejection is voided and marked handled, so a sendMessage that exists and fails on every call is indistinguishable from a healthy one. The guard asks whether a function reference is present, not whether the device is reachable.
A separate, larger change would make the send path genuinely observable: return the promise from sendMessage instead of voiding it, so startProfiling can fail on a dead socket rather than reporting {"ok": true}. That is a signature change through ReactDevToolsBridge.send and every call site, and should not ride along with the timeout fix.
While in this code, the getProps outbound-channel error message is worth re-wording. It currently reads "React DevTools outbound channel is unavailable for this device. Re-open React Native DevTools and try again." — which sent us after the wrong remedy during #476, when the real fault was the middleware's own missing re-bind.
Describe the bug
When the React DevTools backend never answers a
getProfilingDatarequest, the agent's profiling store parks in theprocessingphase forever.isProfilingStartedkeeps returning{"isProcessingData": true, "hasProfilingData": false}indefinitely, andstopProfilingreturns an empty session withpartial: truerather than saying anything went wrong.waitForProfilingDatabounds its own wait, sostopProfilingreturns — but the phase itself never leavesprocessing, so every subsequent poll reports work still in progress that will never finish. A caller pollingisProfilingStartedhas no terminal state to wait for.This was one of the two symptoms in #476. The fix in #477 removed the cause that was reachable there — the outbound channel was silently unbound after an app relaunch, so
stopProfilingwas dropped on the floor and no renderer ever replied. But the store's behaviour when nothing replies is unchanged, and there are other ways to reach it: a renderer that detaches mid-session, a backend that fails to respond, a socket that dies between the request and the reply.Reproduction
Any condition where a
getProfilingDatarequest does not produce aprofilingDataevent. Before #477 this was reproducible directly:startProfilingreports{"ok":true,"status":{"isProfilingStarted":true}}.stopProfilingreturns zero commits withpartial: true.isProfilingStartedreturnsisProcessingData: trueindefinitely — polled well past 60s.Suggested solution
Give the
processingphase a bounded lifetime inprofiling-store.ts, so it resolves to a terminal state on timeout instead of parking. This detects "no renderer answered" regardless of cause, which is the property that matters — it does not require knowing why the reply never arrived.stopProfilingshould then be able to report that no renderer responded, rather than returning an empty session that reads like a successful recording of nothing.Additional context
One alternative was considered and rejected: propagating the
!state.sendMessageguard thatgetPropsalready has (store.ts:1484) tostartProfiling/stopProfiling. It does not work as a health check.bindDevice()runs at construction and on everyonDisconnected, always with a real function.sendReactDevToolsMessageisvoid sendDomainMessage(...)(session.ts:338), andsendDomainMessage's rejection is voided and marked handled, so asendMessagethat exists and fails on every call is indistinguishable from a healthy one. The guard asks whether a function reference is present, not whether the device is reachable.A separate, larger change would make the send path genuinely observable: return the promise from
sendMessageinstead of voiding it, sostartProfilingcan fail on a dead socket rather than reporting{"ok": true}. That is a signature change throughReactDevToolsBridge.sendand every call site, and should not ride along with the timeout fix.While in this code, the
getPropsoutbound-channel error message is worth re-wording. It currently reads "React DevTools outbound channel is unavailable for this device. Re-open React Native DevTools and try again." — which sent us after the wrong remedy during #476, when the real fault was the middleware's own missing re-bind.