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

Fire resourcetimingbufferfull asynchronously #163

Closed
wants to merge 3 commits into from

Conversation

yoavweiss
Copy link
Contributor

@yoavweiss yoavweiss commented Aug 30, 2018

Fixes #141 and #96

This aligns the spec with WebKit's implementation to make sure that resourcetimingbufferfull can fire asynchronously without losing any entries in the process.

Tests are still missing.

@igrigorik @npm1 - can you take a look?


Preview | Diff

@yoavweiss
Copy link
Contributor Author

@JosephPecoraro @rniwa - can you take a look as well?

index.html Outdated
<li>Set the <a>resource timing buffer full flag</a> to true.</li>
<ol style="list-style-type: lower-latin;">
<li>Set <a>resource timing buffer full flag</a> to true.</li>
<li>Queue a task to run <a>fire a buffer full event</a></li>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: period missing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ack

index.html Outdated
</ol>
</li>
<li>If <a>resource timing buffer size limit</a> minus <a>resource timing buffer
current size</a> is not larger than <a>resource timing secondary buffer current
Copy link
Contributor

Choose a reason for hiding this comment

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

larger than or equal to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch

index.html Outdated
</li>
<li>If <a>resource timing buffer size limit</a> minus <a>resource timing buffer
current size</a> is not larger than <a>resource timing secondary buffer current
size</a>, abort these steps.</li>
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might make sense to clear the secondary buffer even in the 'abort' case, so that we can keep listening to future entries even if the primary buffer was not cleared during this call. This is in line with the previous behavior, which would ignore entries when the buffer is full and the buffer full flag is true. Or even better but more complex, make the secondary buffer a queue, and if the queue becomes full and we want to add an entry then we pop the first in line (oldest entry). This way we maintain a list of 'recent' entries ready for when the primary buffer becomes available. WDYT? Also, I think we should add some entries from the secondary buffer even if not all of them can be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it makes sense to clear the secondary buffer either way.
I don't know if adding more complexity here (e.g. making it a queue) would provide a lot of benefit.

I guess we can add some entries from secondary to primary even if not all of them can fit, as long as we don't trigger RTbufferfull from within "fire a buffer full event"

@npm1
Copy link
Contributor

npm1 commented Aug 31, 2018

Thanks, LGTM! I should note a non-obvious change on this PR (not sure it's intentional?):

Before, setResourceTimingBufferSize would never fire resourcetimingbufferfull, and resourcetimingbufferfull would be fired immediately once the buffer becomes full when adding entries (if size is 3, fired after adding the 3rd entry). But this is buggy: if I setResourceTimingBufferSize to reduce the limit to something less than the current size then no event is fired, but then the next time we try to add an entry, there's no space so we fire the event but we are forced to drop the entry. I think it is for this reason that Chrome fires the event on setResourceTimingBufferSize when needed (avoids forcefully dropping the next entry).

With this PR, resourcetimingbufferfull is only fired (or more precisely, now a task is queued to fire it) when we try to add an entry and fail to do so (if size is 3, fired when failing to add the 4th entry). In particular it is still not fired on setResourceTimingBufferSize. But now doing this is fine because the secondary buffer should generally prevent us from dropping the entries. So this fixes the bug :)

@yoavweiss
Copy link
Contributor Author

Thanks, LGTM! I should note a non-obvious change on this PR (not sure it's intentional?):

Before, setResourceTimingBufferSize would never fire resourcetimingbufferfull, and resourcetimingbufferfull would be fired immediately once the buffer becomes full when adding entries (if size is 3, fired after adding the 3rd entry). But this is buggy: if I setResourceTimingBufferSize to reduce the limit to something less than the current size then no event is fired, but then the next time we try to add an entry, there's no space so we fire the event but we are forced to drop the entry. I think it is for this reason that Chrome fires the event on setResourceTimingBufferSize when needed (avoids forcefully dropping the next entry).

With this PR, resourcetimingbufferfull is only fired (or more precisely, now a task is queued to fire it) when we try to add an entry and fail to do so (if size is 3, fired when failing to add the 4th entry). In particular it is still not fired on setResourceTimingBufferSize. But now doing this is fine because the secondary buffer should generally prevent us from dropping the entries. So this fixes the bug :)

This change is indeed intentional. Thanks for reviewing! :)

@yoavweiss
Copy link
Contributor Author

Waiting on tests before merging

@yoavweiss
Copy link
Contributor Author

@npm1 - looking at the code, I see that the event timing implementation follows the same model as resource timing, which makes me wonder if we should generalize the approach. Thoughts?

@npm1
Copy link
Contributor

npm1 commented Sep 4, 2018

I think the buffer mechanic is independent on the fact that this is resource timing so it might be worth generalizing. The only possible difference is whether we are forced to trigger the event async or not, but I think it would be better to be consistent and do async always even if not needed for a particular entryType. That said, I wonder if we really do need the clearing buffer mechanism for entry types that are only buffered until load. It seems to me that we want to stop buffering after load, so not sure how important this is in that case.

<li>Queue a task to run <a>fire a buffer full event</a>.</li>
</ol>
<li>If <a>resource timing secondary buffer current size</a> is less than
<a>resource timing buffer size limit</a>, add <i>new entry</i> to the
Copy link

Choose a reason for hiding this comment

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

We don't check condition in our implementation since the script wouldn't have a chance to clear the buffer until it receives the event. The secondary buffer is basically unbounded for us.

Copy link
Contributor

Choose a reason for hiding this comment

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

The point of the buffer size limit is to limit the amount of memory used. Having an unbounded secondary buffer makes that limit pointless.

Copy link

Choose a reason for hiding this comment

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

Well, if we're delaying the event dispatching to the next task, how are author scripts supposed to know to clear the buffer? This unboundedness only occurs until the next task, and I don't see any reason why it would be bad. The script authors can just allocate a bunch of arrays to do the same if any.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I agree that the unboundedness here would be short lived, I prefer to avoid scenarios where the event is called again on every microtask. So I prefer to define the buffer as bounded to the same size as the RT buffer to guaranty that if the developer either cleared the buffer or doubled its size, all entries will be successfully copied.

This can result in dropped entries when the buffer is too small, but I doubt that would happen in practice, and it makes the processing model simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, if we're delaying the event dispatching to the next task, how are author scripts supposed to know to clear the buffer?

I'm not sure I understand the issue.
The proposed model is:

  • An entry which overflows the buffer, triggers the event queueing.
  • The entry as well as following ones go into the secondary buffer.
  • Once the event is fired, developers should make room in the primary buffer (either clear or bump its size)
  • Right after that, entries are copied from secondary to primary if possible

Do you see a problem with that model?

Copy link

Choose a reason for hiding this comment

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

Oh now I remember why non-first resourcetimingbufferfull events are fired synchronously. This is so that we can ensure that either the buffer is emptied after a single task, or that the remaining entries are dropped on the floor.

Not that if we end up enqueuing more ResourceTiming entries as a result of dispatching resourcetimingbufferfull, we can fall into an infinite loop here but that's no worse than the current status quo, and this is no different from someone causing a mutual recursion between event listeners.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What is the benefit of adding all that complexity and edge cases? Why is it all of the sudden OK to fire the events synchronously the second time, but not the first time?

I think a simpler solution would be to basically tell people:

  • Don't set the buffer size to ridiculously small amounts. I doubt many people decrease the buffer sizes from their default size.
  • Clear or significantly increase (at least double) the buffer when it's full

I can see an argument in favor of adding console messages when this happens, so that developers can catch such scenarios, if they at all exist. But adding a ton of complexity in order to save a few entries in theory seem excessive.

Copy link

Choose a reason for hiding this comment

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

Why is it all of the sudden OK to fire the events synchronously the second time, but not the first time?

Because we're already in a new task. There is no security or implementation issues from firing an event synchronously.

Copy link
Contributor Author

@yoavweiss yoavweiss Sep 6, 2018

Choose a reason for hiding this comment

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

Because we're already in a new task

new task relative to what?

There is no security or implementation issues from firing an event synchronously.

Can you expand on why you objected to the original sync event firing that was in the spec? What were the issues there?

Copy link

@rniwa rniwa Sep 6, 2018

Choose a reason for hiding this comment

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

The problem is this. There is a lot of code in the browser engine where the resource load gets canceled (e.g. because the document is getting removed from a frame), and adding a resource timing entry in those timing is not permissible.

However, once this new timer fires, there is nothing like that happening down in the stack frame. So it's totally okay to synchronously fire events. Let me put this way. If it weren't okay to fire multiple resourcetimingbufferfull events, then it wouldn't have been okay to fire even one of them. It doesn't really matter how many events we fire. The important difference whether we fire any at all.

data-cite='!PERFORMANCE-TIMELINE-2/#dfn-performance-entry-buffer'>performance
entry buffer</a>.</li>
<li>Increment <a>resource timing buffer current size</a> by 1.</li>
<li>Remove <i>entry</i> from <a>secondary entry buffer</a>.</li>
Copy link

Choose a reason for hiding this comment

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

Looks like this is missing a step to fire resourcetimingbufferfull once the primary buffer becomes full again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because the secondary buffer is bounded, and we assume developers do the right thing (either clear or double the buffer size), there's no need to fire the event again from here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that one behavior change with this PR is that the event doesn't fire when the buffer is full, but fired when it overflows.

Copy link

Choose a reason for hiding this comment

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

Okay, that needs to be changed once the secondary buffer's limit is lifted.

Copy link
Contributor

Choose a reason for hiding this comment

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

Even if we intend to keep an unbounded secondary buffer, for consistency we might want to fire the additional events async. I see no reason why this one would need be sync. So essentially we would just call the "add a PerformanceResourceTiming entry" algorithm.

Copy link

Choose a reason for hiding this comment

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

Making those secondary events async would be fine with me.

@yoavweiss
Copy link
Contributor Author

See
https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/page/Performance.cpp#L178
https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/page/Performance.cpp#L211

@rniwa I understand that the spec PR diverges from the current WebKit model. However, it seems like a reasonable compromise that enables capping the secondary buffer size without too much complexity.

@yoavweiss
Copy link
Contributor Author

Tests for this PR as well as Chromium implementation are at https://chromium-review.googlesource.com/c/chromium/src/+/1200863

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Sep 5, 2018
This change implements the processing model from PR 163[1], when
it comes to setResourceTimingBufferSize(), clearResourceTimings()
and the firing of the resourcetimingbufferfull event.

[1] w3c/resource-timing#163

Change-Id: I3fd901fa411dd128a723e77cd120f50974a775a8
@rniwa
Copy link

rniwa commented Sep 5, 2018

FYI, we're not going to change our behavior. Letting the secondary buffer overflow without ever letting the script react to it is simply not acceptable.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Sep 5, 2018
This change implements the processing model from PR 163[1], when
it comes to setResourceTimingBufferSize(), clearResourceTimings()
and the firing of the resourcetimingbufferfull event.

[1] w3c/resource-timing#163

Change-Id: I3fd901fa411dd128a723e77cd120f50974a775a8
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Nov 20, 2018
This change implements the processing model from PR 163[1], when
it comes to setResourceTimingBufferSize(), clearResourceTimings()
and the firing of the resourcetimingbufferfull event.

[1] w3c/resource-timing#163

Change-Id: I3fd901fa411dd128a723e77cd120f50974a775a8
@yoavweiss
Copy link
Contributor Author

As discussed at TPAC, this approach is abandoned. Closing.

@yoavweiss yoavweiss closed this Nov 22, 2018
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