Skip to content

Profiling store parks in the processing phase when no renderer answers #481

Description

@V3RON

Describe the bug

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:

  1. Create a session and confirm profiling works.
  2. Restart the app, then re-create the session against the same device id.
  3. startProfiling reports {"ok":true,"status":{"isProfilingStarted":true}}.
  4. stopProfiling returns zero commits with partial: true.
  5. 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.

  • After fix(middleware): re-bind the React outbound channel when a session reconnects #477 that state is unreachable in production — bindDevice() runs at construction and on every onDisconnected, always with a real function.
  • 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.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions