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

fix: breakpoints not persisting in ephemeral files after reload #277

Merged
merged 6 commits into from
Jan 31, 2020

Conversation

connor4312
Copy link
Member

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.

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.
@connor4312 connor4312 requested a review from roblourens January 29, 2020 23:10
@connor4312 connor4312 added this to the January 2020 milestone Jan 29, 2020
Copy link
Member

@roblourens roblourens left a 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.

@connor4312
Copy link
Member Author

connor4312 commented Jan 31, 2020

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:

  • I set a breakpoint in a file that doesn't exist on disk.
  • I restart the session. I hit the breakpoint, but my debugger is paused in a 'different' identical file to the one by breakpoint is in. I can switch between the two.

Over DAP, here's what happens:

1. setBreakpoints in the original file

This 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 script

A different source reference this time. That's OK, we'll figure things out.

{
  "reason": "new",
  "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"
    }
  }
}

3. breakpoint event

We 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 confused

The 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 sources request for the old source reference.

Copy link
Member

@roblourens roblourens left a 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?

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);
Copy link
Member

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?

Copy link
Member Author

@connor4312 connor4312 Jan 31, 2020

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.

Copy link
Member

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.

@connor4312
Copy link
Member Author

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?

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.

connor4312 added a commit that referenced this pull request Jan 31, 2020
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)
connor4312 added a commit that referenced this pull request Jan 31, 2020
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)
@connor4312
Copy link
Member Author

connor4312 commented Jan 31, 2020

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.

@connor4312 connor4312 merged commit 2641d81 into master Jan 31, 2020
@connor4312 connor4312 deleted the fix/bp-in-external branch July 6, 2020 18:22
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.

Extension host debugging: Breakpoints don't rebind after restart
2 participants