| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I'll come to my computer later tonight to read the relevant portion of window_performance.cc quite a bit more carefully to understand this in detail, but figured I'd scribble what I have now. :-)
// event_ might potentially be null if this is std::move()-ed.This pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
Maybe a check of sorts to guard against underflow in the next line?
E.g. CHECK(entry_nesting_level_ > 0);
DomWindow()->GetFrame()->GetChromeClient().NotifyPresentationTime(This is still in the same method, but I think you removed the check from l. 651 (base version) so it could crash? Or is checking for |entry| sufficient to prevent a null dereference?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Mostly LG. This is a lot cleaner.
Persistent<PerformanceEventTiming> entry_;This (and the others) really should be stored as raw pointers since this is stack allocated. I think for `event_` that would require clearing state on move() though.
if (!DomWindow() || !DomWindow()->GetFrame()) {What would happen if running the event handler detaches the window?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
// event_ might potentially be null if this is std::move()-ed.This pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
I dont actually think this ever does or could get moved. But I dont want to remove this without understanding the historical context.
DomWindow()->GetFrame()->GetChromeClient().NotifyPresentationTime(This is still in the same method, but I think you removed the check from l. 651 (base version) so it could crash? Or is checking for |entry| sufficient to prevent a null dereference?
I thought it was, but I will add it back to be extra careful.
In general I wouldn't think this could change in the middle of event dispatch (and still have the event finish dispatch), but I've been surprised before! (i.e. devtools debugging pauses or something)
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// event_ might potentially be null if this is std::move()-ed.This pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
No I think it's just a product of this being moveable (during creation). This is common for moveable RAII classes, and you'd have to make it non-moveable to avoid this. (btw it should be "this will be null if this has been moved", not potentially).
DomWindow()->GetFrame()->GetChromeClient().NotifyPresentationTime(This is still in the same method, but I think you removed the check from l. 651 (base version) so it could crash? Or is checking for |entry| sufficient to prevent a null dereference?
I thought it was, but I will add it back to be extra careful.
In general I wouldn't think this could change in the middle of event dispatch (and still have the event finish dispatch), but I've been surprised before! (i.e. devtools debugging pauses or something)
Along the lines of what I asked in the other comment, I'm pretty sure you can construct an example where an iframe causes itself to be removed in an event handler.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
DomWindow()->GetFrame()->GetChromeClient().NotifyPresentationTime(Michal MocnyThis is still in the same method, but I think you removed the check from l. 651 (base version) so it could crash? Or is checking for |entry| sufficient to prevent a null dereference?
Scott HaseleyI thought it was, but I will add it back to be extra careful.
In general I wouldn't think this could change in the middle of event dispatch (and still have the event finish dispatch), but I've been surprised before! (i.e. devtools debugging pauses or something)
Along the lines of what I asked in the other comment, I'm pretty sure you can construct an example where an iframe causes itself to be removed in an event handler.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// event_ might potentially be null if this is std::move()-ed.This pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
No I think it's just a product of this being moveable (during creation). This is common for moveable RAII classes, and you'd have to make it non-moveable to avoid this. (btw it should be "this will be null if this has been moved", not potentially).
Interesting. So, by changing the header to
EventTiming(EventTiming&&) = delete;
EventTiming& operator=(EventTiming&&) = delete;
I confirmed that TryCreate does in fact cause something like std::move to happen, as the object is returned in the std::optional - it won't compile with the delete version above.
Below some speculation / theory:
I'm also thinking that maybe the destructor runs twice, once as the thingy that was stack-allocated in TryCreate gets deleted (the remnants of what was moved), and then in the caller (there are multiple), once the std::optional<EventTiming> goes out of scope.
And, the check in l. 125 below may accomplish to filter out the bogus constructor invocation, because the Persistent<Foo> is smart enough to clear out event_ from the stack-allocated instance?
On the usage of the Persistent<Foo> field: I have the feeling that it's actually correct, because the stack is a garbage collection root, and we don't want these objects to get garbage collected before the EventTiming instance gets deleted?
Perhaps we could do away with the TryCreate returning an std::optional<EventTiming> with the hidden move stuff, and instead have that logic in a more pedestrian instance method. E.g., void Init(...) with the same parameters as TryCreate?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// event_ might potentially be null if this is std::move()-ed.Scott HaseleyThis pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
Johannes HenkelNo I think it's just a product of this being moveable (during creation). This is common for moveable RAII classes, and you'd have to make it non-moveable to avoid this. (btw it should be "this will be null if this has been moved", not potentially).
Interesting. So, by changing the header to
EventTiming(EventTiming&&) = delete;
EventTiming& operator=(EventTiming&&) = delete;I confirmed that TryCreate does in fact cause something like std::move to happen, as the object is returned in the std::optional - it won't compile with the delete version above.
Below some speculation / theory:
I'm also thinking that maybe the destructor runs twice, once as the thingy that was stack-allocated in TryCreate gets deleted (the remnants of what was moved), and then in the caller (there are multiple), once the std::optional<EventTiming> goes out of scope.
And, the check in l. 125 below may accomplish to filter out the bogus constructor invocation, because the Persistent<Foo> is smart enough to clear out event_ from the stack-allocated instance?
On the usage of the Persistent<Foo> field: I have the feeling that it's actually correct, because the stack is a garbage collection root, and we don't want these objects to get garbage collected before the EventTiming instance gets deleted?
Perhaps we could do away with the TryCreate returning an std::optional<EventTiming> with the hidden move stuff, and instead have that logic in a more pedestrian instance method. E.g., void Init(...) with the same parameters as TryCreate?
I think I will not change this established pattern, but will update the semanstics a bit and clean the small new bug introduced.
(The idea of move semantics leaving an empty shell of an object behind that gets destructed is pretty standard modern C++, and I prefer assigning optionals to creating Init() methods, which just have the opposite problem: you could destruct a non-initialized object that has an empty event_)
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Persistent<PerformanceEventTiming> entry_;This (and the others) really should be stored as raw pointers since this is stack allocated. I think for `event_` that would require clearing state on move() though.
There was recently some back and forth on this best practice, I thought. If its alright with you, I will leave this for this patch and we can investigate?
if (!DomWindow() || !DomWindow()->GetFrame()) {What would happen if running the event handler detaches the window?
Done
Maybe a check of sorts to guard against underflow in the next line?
E.g. CHECK(entry_nesting_level_ > 0);
Done
DomWindow()->GetFrame()->GetChromeClient().NotifyPresentationTime(This is still in the same method, but I think you removed the check from l. 651 (base version) so it could crash? Or is checking for |entry| sufficient to prevent a null dereference?
Scott HaseleyI thought it was, but I will add it back to be extra careful.
In general I wouldn't think this could change in the middle of event dispatch (and still have the event finish dispatch), but I've been surprised before! (i.e. devtools debugging pauses or something)
Michal MocnyAlong the lines of what I asked in the other comment, I'm pretty sure you can construct an example where an iframe causes itself to be removed in an event handler.
lol, test failures confirm it!
I've added back the guard, but also done so slightly more robustly: if the event starts to get processed and the window is detached, we would previously leave the event in a half measured state.
This is somewhat fine, the event timing has no window to get reported to, and presumably the performance object is going to get cleaned up too.
But the state of the event timing measurements gets into a broken place and if ever there were more event timings things may blow up, so I "unwind the work of the event stack" and explicitly assign a fallback time for this case.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
cl passed the run, and ive addressed feedback. ptal next week!
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Michal Mocny abandoned this change.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// event_ might potentially be null if this is std::move()-ed.This pre-existing comment feels a little scary, is this an indication that stuff gets processed out of the expected RAII order? Could it be that the new, more efficient code is more reliant on the order than the original (the search in the other file that you're getting rid of, basically)? (speculating / ignore as you see fit please, I think I don't understand it well enough yet to really tell)
Johannes HenkelNo I think it's just a product of this being moveable (during creation). This is common for moveable RAII classes, and you'd have to make it non-moveable to avoid this. (btw it should be "this will be null if this has been moved", not potentially).
Michal MocnyInteresting. So, by changing the header to
EventTiming(EventTiming&&) = delete;
EventTiming& operator=(EventTiming&&) = delete;I confirmed that TryCreate does in fact cause something like std::move to happen, as the object is returned in the std::optional - it won't compile with the delete version above.
Below some speculation / theory:
I'm also thinking that maybe the destructor runs twice, once as the thingy that was stack-allocated in TryCreate gets deleted (the remnants of what was moved), and then in the caller (there are multiple), once the std::optional<EventTiming> goes out of scope.
And, the check in l. 125 below may accomplish to filter out the bogus constructor invocation, because the Persistent<Foo> is smart enough to clear out event_ from the stack-allocated instance?
On the usage of the Persistent<Foo> field: I have the feeling that it's actually correct, because the stack is a garbage collection root, and we don't want these objects to get garbage collected before the EventTiming instance gets deleted?
Perhaps we could do away with the TryCreate returning an std::optional<EventTiming> with the hidden move stuff, and instead have that logic in a more pedestrian instance method. E.g., void Init(...) with the same parameters as TryCreate?
I think I will not change this established pattern, but will update the semanstics a bit and clean the small new bug introduced.
(The idea of move semantics leaving an empty shell of an object behind that gets destructed is pretty standard modern C++, and I prefer assigning optionals to creating Init() methods, which just have the opposite problem: you could destruct a non-initialized object that has an empty event_)
On the usage of the Persistent<Foo> field: I have the feeling that it's actually correct, because the stack is a garbage collection root, and we don't want these objects to get garbage collected before the EventTiming instance gets deleted?
That's not true: Blink does conservative stack scanning to avoid GCing objects with a raw pointer on the stack. Please see https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md#stack_allocated for the part specifically about using raw pointers in STACK_ALLOCATED objects and https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md#raw-pointers for the general case. You should only need Persistent<> when the object doesn't live on the stack, or (as recently learned) if you need to keep it alive past when it's used on the stack, neither or which I believe apply here.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
Persistent<PerformanceEventTiming> entry_;I won't block this since this is the established pattern here, but I think this is wrong. This class was changed from heap allocated (where persistents were needed) to STACK_ALLOCATED here: https://chromium-review.googlesource.com/c/chromium/src/+/5627563/16/third_party/blink/renderer/core/timing/event_timing.h), and I think this was just missed in review.
From https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md#STACK_ALLOCATED: "Any fields holding garbage-collected objects should use raw pointers or references."
And since the values are all used in the destructor, there's no risk of scoping issues like we saw with the tracing thing recently.
If we don't fix this here, can we follow up and fix this?
for (base::TimeTicks LoadTimingInfo::* ts :Is this from JJ doing too much formatting? Fine to keep it, but the blame will be a bit wonky.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Persistent<PerformanceEventTiming> entry_;I won't block this since this is the established pattern here, but I think this is wrong. This class was changed from heap allocated (where persistents were needed) to STACK_ALLOCATED here: https://chromium-review.googlesource.com/c/chromium/src/+/5627563/16/third_party/blink/renderer/core/timing/event_timing.h), and I think this was just missed in review.
From https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md#STACK_ALLOCATED: "Any fields holding garbage-collected objects should use raw pointers or references."
And since the values are all used in the destructor, there's no risk of scoping issues like we saw with the tracing thing recently.
If we don't fix this here, can we follow up and fix this?
I will fix in this patch, then!
for (base::TimeTicks LoadTimingInfo::* ts :Is this from JJ doing too much formatting? Fine to keep it, but the blame will be a bit wonky.
It is. `jj fix` is the equivalent of `git cl format` but has a known bug where it formats the whole file. Uploads via JJ will automatically run a fix.
But I've since learned that I can upload with `jj upload --no-fix`, and can still rely on `git cl format` manually.
I will do so!
(not a huge deal for this patch, but future revisions have larger deltas)
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Michal MocnyI won't block this since this is the established pattern here, but I think this is wrong. This class was changed from heap allocated (where persistents were needed) to STACK_ALLOCATED here: https://chromium-review.googlesource.com/c/chromium/src/+/5627563/16/third_party/blink/renderer/core/timing/event_timing.h), and I think this was just missed in review.
From https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/heap/BlinkGCAPIReference.md#STACK_ALLOCATED: "Any fields holding garbage-collected objects should use raw pointers or references."
And since the values are all used in the destructor, there's no risk of scoping issues like we saw with the tracing thing recently.
If we don't fix this here, can we follow up and fix this?
I will fix in this patch, then!
Done
Michal MocnyIs this from JJ doing too much formatting? Fine to keep it, but the blame will be a bit wonky.
It is. `jj fix` is the equivalent of `git cl format` but has a known bug where it formats the whole file. Uploads via JJ will automatically run a fix.
But I've since learned that I can upload with `jj upload --no-fix`, and can still rely on `git cl format` manually.
I will do so!
(not a huge deal for this patch, but future revisions have larger deltas)
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
if (event_ && performance_) {I think this stuff being null relied on behavior from the Persistent template, so now you'd have to clear these in a custom implementation of one of these or set a bool or similar to indicate that the destructor shouldn't run?
EventTiming(EventTiming&&) = default;
EventTiming& operator=(EventTiming&&) = default;
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I think this stuff being null relied on behavior from the Persistent template, so now you'd have to clear these in a custom implementation of one of these or set a bool or similar to indicate that the destructor shouldn't run?
EventTiming(EventTiming&&) = default;
EventTiming& operator=(EventTiming&&) = default;
Good eye! Scott also pinged me about this and new CL already uploaded to fix it, but appreciate it.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I think this entry is accidental, adding some empty file.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
I think this entry is accidental, adding some empty file.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
LGTM
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
[event-timing] Simplify Event Timing entry tracking during RAII stack
EventTiming creates a performance entry at ProcessingStart, and relies
on a RAII stack based object to track the start->end of event dispatch
processing times. At ProcessingEnd, we need to match to the original
event timing performance entry, in order to update timings.
Previously, we would search through the list of event timings to assume
we found the right match, and would do another similar search to check
if we have nested scopes.
This change simplifies that by just storing a reference in the RAII
object we are already guaranteed to have access to. This is part of a
larger cleanup which moves more work into the RAII scope of the Event.
Also adds better handling (rare) cases of a window becoming detached during
event dispatch of the event being measured.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |