TimingDistributionMetricType.measure can end without calling `stop` and `cancel` if non-local returns used
Categories
(Data Platform and Tools :: Glean: SDK, defect, P1)
Tracking
(Not tracked)
People
(Reporter: mcomella, Assigned: janerik)
Details
Attachments
(1 file)
In the following code, stopAndAccumulate and cancel will not be called:
fun onCreateView() = PerfStartup.homeActivityOnCreateView.measure {
val layout = LayoutInflater.inflate(...)
return layout
}
The problem is that measure is inlined. When inlined, the code looks like the following (I believe, I'm not knowledgeable about inline but you can decompile the code for a more accurate look):
fun onCreateView() {
val timerId = start()
val returnValue = try {
val layout = LayoutInflater.inflate(...)
return layout
} catch (e: Exception) {
cancel(timerId)
throw e
}
// Note: we returned earlier so code below here is never called
stopAndAccumulate(timerId)
return returnValue
}
This is problematic: users will probably not expect an early return (especially if it looks like the final return) to cause stop to not be called.
I think there are two solutions:
- remove
inline: this prevents confusing function call semantics - add
crossinlineto the lambda: AIUI, this only allows returns to the lambda rather than the global function but I'm not sure
inline is a performance optimization that (according to IDE warnings) is only non-negligible when code is executed in a loop (it's removing maybe two functional calls and an allocation). The trade-off is that the binary increases in size for each call used.
For our specific use case, I prefer 1): I don't think measure will frequently be called in a tight loop (we rarely write such loops in FE dev and it's unclear we'd ever want to measure them individually) and we have the option of calling start/stop directly if we identify the additional function calls/allocation as a performance bottleneck. Without measure being called from a loop, the positive/negative impact of both of these is pretty vague until the function is called enough – then the binary size increase will be noticeable. So, to me, it seems like we're more likely to hit the negative consequences of inlineing this than gain the benefit.
fwiw, I skimmed this article to help me understand the semantics of inline functions: https://blog.mindorks.com/understanding-inline-noinline-and-crossinline-in-kotlin
Comment 1•5 years ago
|
||
| Assignee | ||
Updated•5 years ago
|
Comment 2•5 years ago
|
||
(In reply to Michael Comella (:mcomella) [needinfo or I won't see it] from comment #0)
In the following code,
stopAndAccumulateandcancelwill not be called:fun onCreateView() = PerfStartup.homeActivityOnCreateView.measure { val layout = LayoutInflater.inflate(...) return layout }
Woha Michael, you find quite a good pearl here! Great catch and thanks for the detailed bug report!
| Assignee | ||
Comment 3•5 years ago
|
||
Description
•