Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expo go detaching debugger alternative fix #994

Conversation

maciekstosio
Copy link
Contributor

@maciekstosio maciekstosio commented Feb 28, 2025

This PR fixes issue where debugger is detached after restart, when runing Expo Go app. This is due to the fact that when reloading metro, the new instance of debugger with new websocket adress is created. This is alternate solution to: #985

This PR solves it as follows:

  • after running metro.reload() we wait for app to be ready
  • then we get list of available runtimes and theire WS addresses
  • we check if current vscSession is still on list from above
  • if not we close the connection, and start again

How Has This Been Tested:

For debugger that detaches during restart i.e. expo-go test app:

  • Lunch expo-go app
  • Test if debugger works (for example log or break point)
  • Restart with Reload JS
  • Wait until it restarts, and make sure the debugger is still attached (there should be also nice ui with "launching", "attaching to debugger")

For apps that debugger do not detach i.e. react-native-76

  • Lunch expo-go app
  • Test if debugger works (for example log or break point)
  • Add breakpoint in
    return this.connectJSDebugger(metro);
  • Restart with Reload JS
  • Wait until it restarts (it should not stop at the breakpoint)
  • Make sure the debugger is still attached (the UI should be similar as above, but the "attaching debugger" will pass in the blink of an eye)

Copy link

vercel bot commented Feb 28, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
radon-ide ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 4, 2025 0:52am

@maciekstosio maciekstosio changed the title @maciekstosio/expo go detaching debugger alternative fix Expo go detaching debugger alternative fix Feb 28, 2025
@maciekstosio maciekstosio marked this pull request as draft February 28, 2025 11:03
Copy link
Collaborator

@filip131311 filip131311 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left an inline comment, more over would you mind adding an information to the PR description that the changes relate to the old debugger, seems like something that might be important in the future

return listJson;
}

await sleep(progressiveRetryTimeout(retryCount));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this code, it seems to only be reachable when the fetch above fails, but that would throw uncaught rejection anyway preventing any future retries, wouldn't it? does this condition: listJson.length happen often? If so could you add a comment explaining it?

That being said it seems the fetch above should be wrapped in try catch block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only, basically we're pooling the variables. The list can be empty at the beginning.

Previously we did it in a way that we we're retraing entrie fetchDebuggerURL, but my understanding is that it could fail only when fetch was empty. I needed to have method that fetches the list separatly, so we don't open the websocket to runtime, as it closes previous connection, thus I moved the retry logic to other place, but I belive the entrie process will work as before.

Nevertheless, I agree we should wrap it in try catch block, good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in: 9aaf04d

Copy link
Collaborator

@filip131311 filip131311 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now

// query list from http://localhost:${metroPort}/json/list
const list = await fetch(`http://localhost:${this._port}/json/list`);
const listJson = await list.json();
public async fetchRuntimeList(): Promise<CDPTargetDescription[] | undefined> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't list runtimes here but websocket targets. This is a more abstract concept as a single target actually may cover many runtimes. Specifically this happens when you do "reload JS" in react native – new runtime is created but it is listed under the same WS target.

I'd rename to fetchCDPWebsocketTargets or something simpler by removing "CDP" or "Websocket" fragments

return this.start();
}

return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented about this in the other PR, but I don't like that this method returns false in two following scenarios:

  • when there's no need to reconnect
  • when there's need to reconnect and we fail to reconnect properly

Then, this method returns true only when we need to reconnect and that was successful.

I'd feel more natural to return true always when there's a proper connection established regardless of whether we needed to reconnect or not, and then return false in case we couldn't establish the connection

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I changed it there, but checkout this branch earlier.

Comment on lines 45 to 48
const availableAddresses =
possibleRuntimes?.map((runtime) => runtime.webSocketDebuggerUrl) ?? [];

if (!availableAddresses.includes(this.vscSession?.configuration?.websocketAddress)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems overly complicated I think you just want to find runtime with the given ws URL, so this should be something like:

const currentWsAddress = this.vscSession?.configuration?.websocketAddress;
const hasCurrentWsAddress = possibleRuntimes?.some(runtime => runtime.webSocketDebuggerUrl === currentWsAddress)

Copy link
Member

@kmagiera kmagiera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple inline comments outside of this review. Please address them prior to merging.

I also think it'd be appropriate for the test plan to cover scenarios w/o expo-go. I understand this PR changes behavior in those cases too. Specifically we should verify that we don't reconnect in those setups. We will also be showing different states in the UI when refresh happens from what I understand?

@maciekstosio
Copy link
Contributor Author

I left a couple inline comments outside of this review. Please address them prior to merging.

I also think it'd be appropriate for the test plan to cover scenarios w/o expo-go. I understand this PR changes behavior in those cases too. Specifically we should verify that we don't reconnect in those setups. We will also be showing different states in the UI when refresh happens from what I understand?

Yes, I'll add information about checking for regression, but the flow will be more or less the same. By more or less I mean that for other setups overlay with "attaching debugger" will pass in the blink of an eye, but will be checked anyway.

@filip131311
Copy link
Collaborator

The other solution in #985 won

@filip131311 filip131311 closed this Mar 5, 2025
maciekstosio added a commit that referenced this pull request Mar 5, 2025
This PR fixes issue where debugger is detached after restart, when
runing Expo Go app. This is due to the fact that when reloading metro,
the new instance of debugger with new websocket adress is created.

This PR solves it as follows:
- after running `metro.reload()` we wait for app to be ready
- once it's ready, we're checking if old debug session is available
- when old runtime is not available we close old debug session and start
the now one
- if old runtime is available, then we do nothing 

How to determine the old debug session is available:
 
 So there are to approches:
 - trying to execture something through the debugger
 - check if old debugger is on the list (#994)
 
The problem with the first one is that when debuger is stale, it might
just not answer, in that case we timeout after 1 sec.
The problem with the second one is that sometimes there is ws target
available that can't be used (Expo Go case), this the solution is not
working reliably. To overcome those two problems we combined both
solutions. We run both checks in `promise.any`, so:
- If ping returns true, we're good (connection alive)
- if checking list of ws targets returns false, we're good (connection
closed)
- if current ws target is on the list, we reject so `promise.any` waits
for ping and ping will check that reliably after at most 1 sec

### How Has This Been Tested: 

**For debugger that detaches during restart i.e. expo-go test app:**
- Lunch expo-go app 
- Test if debugger works (for example log or break point)
- Restart with `Reload JS`
- Wait until it restarts, and make sure the debugger is still attached
(there should be also nice ui with "launching", "attaching to debugger")

**For apps that debugger do not detach i.e. react-native-76**
- Lunch expo-go app 
- Test if debugger works (for example log or break point)
- Add breakpoint in
https://github.com/software-mansion/radon-ide/blob/59372cc4e83d28edb2ae6f183b2f0d0a2e48eeb9/packages/vscode-extension/src/debugging/DebugSession.ts#L107
- Restart with `Reload JS`
- Wait until it restarts (**it should not stop at the breakpoint**)
- Make sure the debugger is still attached (the UI should be similar as
above, but the "attaching debugger" will pass in the blink of an eye)

|Expo Go Before|Expo Go After|
|-|-|
|<video
src="https://github.com/user-attachments/assets/5ae32a1c-4c33-4a55-98a1-c645f4e92e59"
/>|<video
src="https://github.com/user-attachments/assets/eca7c8c2-8806-45c7-a386-10f79dd706d7"
/>|


|RN76 Before|RN 76 After|
|-|-|
|<video
src="https://github.com/user-attachments/assets/e0b81615-e2f7-412a-b3e5-6f83e141b9a7"
/>|<video
src="https://github.com/user-attachments/assets/70b6fae8-8ebe-4ba3-bc5d-141c9dc545ef"
/>|
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants