From: "jeremyevans0 (Jeremy Evans)" Date: 2022-01-03T18:16:41+00:00 Subject: [ruby-core:106954] [Ruby master Feature#16663] Add block or filtered forms of Kernel#caller to allow early bail-out Issue #16663 has been updated by jeremyevans0 (Jeremy Evans). headius (Charles Nutter) wrote in #note-15: > Yes I botched that part of my example. The Enumerable would have to be yielded to a block and used within that block. My example done right would be more like: > > ```ruby > Kernel.caller_locations do |enum| > enum.drop_while {|frame| test_library_frame?(frame)} > .keep_until {|frame| test_library_frame?(frame)} > .to_a > end > ``` > > If the enum is used outside of this context, it would immediately error or StopIteration as it can no longer materialize lost frames. Some combination of Lazy and eager Enumerator logic could be used here to materialize required frames at the point that `caller_locations` returns. I think this approach of yielding an enumerable would be much more complex to implement, and it would be challenging to detect misuse and raise. Considering this feature is only needed rarely, I think the cost of the additional complexity is higher than the benefit of the nicer API. > Your example, with a simple `each_backtrace` does cover all the use cases I'm interested in, however. The only thing we lose over my version is the ability to apply other lazy Enumerator filtering in a more fluent style of code. My version would apply more naturally to the existing API, also, and would work the same for `caller`, `backtrace` and `*_locations` APIs. Since a simpler `each_backtrace` that yields caller locations will cover all use cases you are interested in, I think it makes sense to use that approach. I'll add this ticket to the next developer meeting, so we can hopefully get approval to move forward. ---------------------------------------- Feature #16663: Add block or filtered forms of Kernel#caller to allow early bail-out https://bugs.ruby-lang.org/issues/16663#change-95777 * Author: headius (Charles Nutter) * Status: Open * Priority: Normal ---------------------------------------- There are many libraries that use `caller` or `caller_locations` to gather stack information for logging or instrumentation. These methods generate an array of informational stack frames based on the current call stack. Both methods accept parameters for `level` (skip some number of Ruby frames) and `length` (only return this many frames). However many use cases are unable to provide one or both of these. Instrumentation uses, for example, may need to skip an unknown number of frames at the top of the trace, such as to dig out of rspec plumbing or active_record internals and report the first line of user code. In such cases, the typical pattern is to simply request *all* frames and then filter out the one that is desired. This leads to a great deal of wasted work gathering those frames and constructing objects to carry them to the user. On optimizing runtimes like JRuby and TruffleRuby, it can have a tremendous impact on performance, since each frame has a much higher cost than on CRuby. I propose that we need a new form of `caller` that takes a block for processing each element. ```ruby def find_matching_frame(regex) caller do |frame| return frame if frame.file =~ regex end end ``` An alternative API would be to allow passing a query object as a keyword argument, avoiding the block dispatch by performing the match internally: ```ruby def find_matching_frame(regex) caller(file: regex) end ``` This API would provide a middle ground between explicitly specifying a maximum number of stack frames and asking for all frames. Most common, hot-path uses of `caller` could be replaced by these forms, reducing overhead on all Ruby implementations and drastically reducing it where stack traces are expensive. -- https://bugs.ruby-lang.org/ Unsubscribe: