-
Notifications
You must be signed in to change notification settings - Fork 293
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
fix: breakpoints not persisting in ephemeral files after reload #277
Conversation
Fixes #274 This became more interesting than expected. There were two problems. Fixing either of them resolves the linked issue, but let's take care of them both. 1. It seems like (some?) files imported in the extension host include the Node.js module prefix in their hashed content. Maybe this is an Electron thing; I haven't seen it happen before. This was causing us to treat those sources as runtime-only and not trust the disk. 2. It looks like VS Code will preserve file source references between runs. For us, the source references are pretty ephemeral, incremented globally for the lifetime of the debug *adapter*. So in fact if you restart the debug session, you're guarenteed to never get any overlapping source references from the previous session. I'm not sure whether this should change. We could reset the increment between sessions so that scripts might get the same references, but it's just a matter of load order, and if a small change caused a script to be inserted early on then every reference will be different. Alternatively we could form the source reference off a hash of some metadata about the file. If we got a collision we'd need to increment but it might be a nice way to make things usually work. For now, in `setBreakpoints` I have just made a change to remove the source reference if we see it coming in for a source we don't have but which has a path we can fall back to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also take advantage of the adapterData
property on Dap.Source
to store something like the path+hash that would let you match a Source from a previous session with the same one with different sourceReference in the current session.
Good idea. I have it kind of working, but it looks like VS Code gets somewhat confused about breakpoints moving between sources references. In this scenario:
Over DAP, here's what happens: 1. setBreakpoints in the original fileThis is okay. The sourceReference points to the old file, but using the url we can find the original one. Note that the path doesn't actually resolve anywhere--if it did resolve to a valid file, the source reference would be 0 per spec. {
"source": {
"name": "bootstrap",
"path": "/Users/copeet/Github/vscode-pwa/demos/webpack/webpack/bootstrap",
"sourceReference": 100,
"adapterData": {
"url": "file:///Users/copeet/Github/vscode-pwa/demos/webpack/webpack/bootstrap"
}
},
"lines": [
7
],
"breakpoints": [
{
"line": 7
}
],
"sourceModified": false
} 2. loadedSource for new scriptA different source reference this time. That's OK, we'll figure things out.
3. breakpoint eventWe update the previously-set breakpoint and tell VS Code it got verified in the new source reference. At this point, if I had the new source reference file open, I would expect that VS Code would move the breakpoint into that file. {
"reason": "changed",
"breakpoint": {
"id": 9,
"verified": true,
"source": {
"name": "bootstrap",
"path": "/Users/copeet/Github/vscode-pwa/demos/webpack/webpack/bootstrap",
"sourceReference": 304,
"adapterData": {
"url": "file:///Users/copeet/Github/vscode-pwa/demos/webpack/webpack/bootstrap"
}
},
"line": 8,
"column": 1
}
} 4. we pause and VS code gets confusedThe stacktrace contains the new source reference, and we open that file. However the breakpoint remains in the old source. Clicking on the breakpoint triggers a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will let you resolve breakpoints from old Dap sourceReferences, but will you still generate a different sourceReference for the same document on the second run? Meaning the user might still end up with two tabs open to the same file?
src/adapter/sources.ts
Outdated
if (ref.sourceReference) return this._sourceByReference.get(ref.sourceReference); | ||
if (ref.path) return this._sourceByAbsolutePath.get(ref.path); | ||
if (ref.sourceReference) { | ||
const byRef = this._sourceByReference.get(ref.sourceReference); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's going on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default breakpoints set before reload will have a source reference, but it's invalid. If we saw that, we used to return undefined, this change falls back to looking at the path/adapter dat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I misread it, it might be more clear to return byRef
instead of calling get a second time.
Yea, this is possible, and quite frequent with the issue I mentioned... We could try to keep a persistent map around. Or do what I mentioned maybe use the script content as a seed to get the source reference, so that unchanged files will always have the same source reference between runs. |
The hash speed was bugging me, particularly since we're soon going to need to hash files twice (as of #277) so last night I wanted to see if a webassembly version would fare better. Turns out it does. Hashing 1KB: ``` 7,520 ops/sec > current (1x) 72,500 ops/sec > wasm (9.64x) Benches: 2 Fastest: wasm Elapsed: 10.9s ``` The current JS algorithm only is hasing 7.5MB/s, which may sound like a decent amount, but remember some node_modules can be massive. This brings up a bit. ![](https://i.redd.it/tfugj4n3l6ez.png)
The hash speed was bugging me, particularly since we're soon going to need to hash files twice (as of #277) so last night I wanted to see if a webassembly version would fare better. Turns out it does. Hashing 1KB: ``` 7,520 ops/sec > current (1x) 72,500 ops/sec > wasm (9.64x) Benches: 2 Fastest: wasm Elapsed: 10.9s ``` The current JS algorithm only is hasing 7.5MB/s, which may sound like a decent amount, but remember some node_modules can be massive. This brings up a bit. ![](https://i.redd.it/tfugj4n3l6ez.png)
Actually, even with the same source reference between sessions, VS Code still tries to treat them as separate files. I'll update the issue. I think this is the direction I would prefer to go though, so I'll get it in; it doesn't regress anything and should work when Code's behavior is fixed. |
Fixes #274
This became more interesting than expected. There were two problems.
Fixing either of them resolves the linked issue, but let's
take care of them both.
It seems like (some?) files imported in the extension host include
the Node.js module prefix in their hashed content. Maybe this is an
Electron thing; I haven't seen it happen before. This was causing
us to treat those sources as runtime-only and not trust the disk.
It looks like VS Code will preserve file source references between
runs. For us, the source references are pretty ephemeral, incremented
globally for the lifetime of the debug adapter. So in fact if you
restart the debug session, you're guarenteed to never get any
overlapping source references from the previous session.
I'm not sure whether this should change. We could reset the increment
between sessions so that scripts might get the same references, but
it's just a matter of load order, and if a small change caused a
script to be inserted early on then every reference will be different.
Alternatively we could form the source reference off a hash of some
metadata about the file. If we got a collision we'd need to increment
but it might be a nice way to make things usually work.
For now, in
setBreakpoints
I have just made a change to remove thesource reference if we see it coming in for a source we don't have
but which has a path we can fall back to.