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

PerformanceObserver option buffered:true causes a double-borrow crash #25589

Closed
pshaughn opened this issue Jan 23, 2020 · 5 comments
Closed

PerformanceObserver option buffered:true causes a double-borrow crash #25589

pshaughn opened this issue Jan 23, 2020 · 5 comments

Comments

@pshaughn
Copy link
Member

@pshaughn pshaughn commented Jan 23, 2020

The spec now only allows buffered in combination with the type option and not the entryTypes option, and since we don't support the type option yet, we're failing early in all the WPT tests that use buffered and not getting to the actual behavior being tested. This crash thus needs a modified test to detect.

In the course of #24781 I saw this crash and I wanted to make sure I was uncovering a pre-existing bug rather than creating a new one, so I modified performance-timeline/multiple-buffered-flag-observers.any.js. This modified test is non-spec-compliant since it's combining entryTypes with buffered, but it demonstrates the problem when run on our current master which still allows that combination.

Modified test code
promise_test(() => {
  // The first promise waits for one buffered flag observer to receive 3 entries.
  const promise1 = new Promise(resolve1 => {
    let numObserved1 = 0;
    new PerformanceObserver((entryList, obs) => {
      // This buffered flag observer is constructed after a regular observer detects a mark.
      new PerformanceObserver(list => {
        numObserved1 += list.getEntries().length;
        if (numObserved1 == 3)
          resolve1();
      }).observe({entryTypes: ['mark'], buffered: true});
      obs.disconnect();
    }).observe({entryTypes: ['mark']});
    performance.mark('foo');
  });
  // The second promise waits for another buffered flag observer to receive 3 entries.
  const promise2 = new Promise(resolve2 => {
    step_timeout(() => {
      let numObserved2 = 0;
      // This buffered flag observer is constructed after a delay of 100ms.
      new PerformanceObserver(list => {
        numObserved2 += list.getEntries().length;
        if (numObserved2 == 3)
          resolve2();
      }).observe({entryTypes: ['mark'], buffered: true});
    }, 100);
    performance.mark('bar');
  });
  performance.mark('meow');
  // Pass if and only if both buffered observers received all 3 mark entries.
  return Promise.all([promise1, promise2]);
}, 'Multiple PerformanceObservers with buffered flag see all entries');

It appears that some inappropriate sharing is happening between one PerformanceObserver's entry list and the buffer of entries that another PerformanceObserver is supposed to see, so when one PerformanceObserver tries to clear its list on .disconnect() it's re-borrowing an already borrowed value.

@pshaughn
Copy link
Member Author

@pshaughn pshaughn commented Jan 23, 2020

I see nothing obvious to cause this, but there are a couple uses of .clone() in the code that I imagine might be shallow-copying cells they are supposed to be deep-copying, and someone more familiar with reference semantics in Servo's cell types might want to look at those first.

@jdm
Copy link
Member

@jdm jdm commented Jan 23, 2020

Fun fact - on recent revisions of master, you can build with --features refcell_backtrace and you should receive a more useful output for double borrow failures that show both the location of the triggering borrow and the original borrow that is conflicting.

@jdm
Copy link
Member

@jdm jdm commented Jan 23, 2020

That being said, I suspect the use of let entries = self.entries.borrow(); in PerformanceObserver::notify, which will cause the borrow to be held while we execute an arbitrary JS callback. We should scope that borrow smaller and obtain what we need from it before invoking the callback.

@pshaughn
Copy link
Member Author

@pshaughn pshaughn commented Jan 23, 2020

Oh, does the following let mut entries not itself drop the earlier let entries?

@pshaughn
Copy link
Member Author

@pshaughn pshaughn commented Jan 23, 2020

You were correct; test is now timing out safely for some other reason instead of crashing.

bors-servo added a commit that referenced this issue Jan 24, 2020
Make performance observers take "type" and "buffered" options without panicking.

<!-- Please describe your changes on the following line: -->
I updated the observe() method to align with spec, and fixed the borrow duration bug @jdm pointed out in #25589 so it wouldn't cause a hard crash. Some tests go from failing to passing, but others go from early failing to later timeout; performance observers still aren't doing the right thing in all cases.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #24781 and fix #25589

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
bors-servo added a commit that referenced this issue Jan 24, 2020
Make performance observers take "type" and "buffered" options without panicking.

<!-- Please describe your changes on the following line: -->
I updated the observe() method to align with spec, and fixed the borrow duration bug @jdm pointed out in #25589 so it wouldn't cause a hard crash. Some tests go from failing to passing, but others go from early failing to later timeout; performance observers still aren't doing the right thing in all cases.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #24781 and fix #25589

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
bors-servo added a commit that referenced this issue Jan 27, 2020
Make performance observers take "type" and "buffered" options without panicking.

<!-- Please describe your changes on the following line: -->
I updated the observe() method to align with spec, and fixed the borrow duration bug @jdm pointed out in #25589 so it wouldn't cause a hard crash. Some tests go from failing to passing, but others go from early failing to later timeout; performance observers still aren't doing the right thing in all cases.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #24781 and fix #25589

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
bors-servo added a commit that referenced this issue Jan 29, 2020
Make performance observers take "type" and "buffered" options without panicking.

<!-- Please describe your changes on the following line: -->
I updated the observe() method to align with spec, and fixed the borrow duration bug @jdm pointed out in #25589 so it wouldn't cause a hard crash. Some tests go from failing to passing, but others go from early failing to later timeout; performance observers still aren't doing the right thing in all cases.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #24781 and fix #25589

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

2 participants
You can’t perform that action at this time.