Conversation
|
Not sure about |
|
It's nice to take the time for an API redesign for backtraces (cc @jhjourdan): the previous design was highly constrained by compatibility concerns. I would have expected to see
Finally, if we can bikeshed, I don't like |
Good point, I will add them.
Yes, I will add these. Thanks for the suggestion.
I agree that the names are not good, but couldn't think of obviously better alternatives right away. |
|
|
OK, what about |
|
Yes, I think that would be nice. |
|
|
|
This would also be fine with me, although I find @nojb's proposal more systematic (it is easy to guess one name from the other). |
stdlib/exn.mli
Outdated
| (** Returns [true] if exception backtraces are currently recorded, [false] if | ||
| not. *) | ||
|
|
||
| module Backtrace : sig |
There was a problem hiding this comment.
TBH I find this design quite messy can't we maybe review the organisation and prune a bit the terminology ?
Do we really need both call stack and backtrace in the vocabulary ? Isn't an "exception backtrace" simply an exception with a call stack ? Wouldn't it make sense to maybe have these Backtraces in their own toplevel module as Callstack ? I mean Exn.current_callstack really doesn't feel satisfactory to me.
If that is done then maybe rather than recording backtraces we can simply trace exceptions and maybe have Exn.{tracing,set_tracing}.
There was a problem hiding this comment.
I agree current_callstack is odd, but where to put it?
Note that while there is only one type here ("backtraces/call stacks") there are two notions of "current": "current backtrace" means the backtrace/call stack of the "last" raise; "current callstack" means the backtrace/call stack at the call site. So in any case we will need some terminology to differentiate these two notions.
As long as backtraces/call stacks remain closely related to exceptions, it doesn't seem strange to me to keep them in a submodule of Exn (the issue of current_callstack notwithstanding). But if we expect them to have a life of their own (are we?) maybe putting them in their own top-level module could be considered.
There was a problem hiding this comment.
Note that while there is only one type here ("backtraces/call stacks") there are two notions of "current": "current backtrace" means the backtrace/call stack of the "last" raise; "current callstack" means the backtrace/call stack at the call site. So in any case we will need some terminology to differentiate these two notions.
Well Exn.current_trace () or even Exn.last_trace () and Callstack.current ().
There was a problem hiding this comment.
As long as backtraces/call stacks remain closely related to exceptions,
Daniel has a point that while backtraces are related to exceptions, call stacks are an independent concept. Currently there is only one primitive type raw_backtrace, but we could have a raw_callstack type (with the same underlying representation), or a less opinionated name shared between the two, such as "stack trace" or "stack fragment".
(I don't have time to devote to these API questions before mid-November, so unfortunately I won't participate in making proposals of my own. Good luck!)
So we have I would rather try to go with the former, then the we can call these things "exception traces" and the whole process "exception tracing" which in turn might allow to get rid of the notion of |
|
I went ahead and added a separate |
stdlib/callstack.mli
Outdated
| @since 4.08 *) | ||
|
|
||
| type t | ||
| (** The abstract type [t] stores a callstack. |
There was a problem hiding this comment.
There's a mix of "callstack" and "call stack" in the text, one should be chosen. Maybe this suggests it should rather be Call_stack; stack already appears separately in Stack_overflow.
There was a problem hiding this comment.
Personally, I find Call_stack a bit ugly. Maybe use callstack everywhere? Another possibility is to use Backtrace instead of Callstack as module name.
There was a problem hiding this comment.
Backtrace is less clear than "call stack" or "stack trace" to me. I don't mind if you want to call it Callstack and if you use "call stack" in the text but at least do it consistently.
That said if people never use "callstack" as a single word, conventions would say that Call_stack is the right identifier.
You could also call it Stack_trace which would, terminlogy-wise, blend better with Stack_overflow exception.
There was a problem hiding this comment.
In fact maybe "stack trace" is a better description of the value than "call stack". If I have a value for a call stack I would expect to be able to actually manipulate the call stack with it, which is not the case.
There was a problem hiding this comment.
OK, let's go with Stack_trace. I pushed a commit with the renaming.
|
I'm not entirely convinced of the name |
It's the name of the type in Pervasives. |
|
I filled out the implementation, updated the manual, and rewrote the implementation of |
3f46a59 to
1bc0233
Compare
| { filename: string; | ||
| line_number: int; | ||
| start_char: int; | ||
| end_char: int } |
There was a problem hiding this comment.
I wouldn't expose the record fields but rather move it to its own module with a creating function and accessors.
There was a problem hiding this comment.
Indeed. Also, I would recommend putting the closing curly brace on its own line (as you have in other places), since it makes it quicker to add another record field.
There was a problem hiding this comment.
Do we want to expose a "create" function, or just accessors?
There was a problem hiding this comment.
I think it would definitively be useful be able to create values or redefine fields e.g. to remap filenames in a given context. I would also rename {start,end}_char to {start,end}_byte since it's likely a better characterization of what it is but that may not be consistent with the rest of the compiler apis.
|
|
||
| val length: t -> int | ||
| (** [length st] returns the number of (non-inlined) slots in the stack trace | ||
| [st]. *) |
There was a problem hiding this comment.
Why would this only return the number of non-inlined frames? Seems a bit odd.
There was a problem hiding this comment.
As far as I can tell, this reflects the runtime representation of stack traces, so it is more efficient than "flattening" the stack traces fully. I wonder if this cost is worth the complication in the API.
stdlib/stack_trace.mli
Outdated
|
|
||
| val slots: t -> Slot.t array option | ||
| (** Returns the slots of a stack trace, or [None] if none of them contain useful | ||
| information. |
There was a problem hiding this comment.
The presence of this option type seems a bit strange. Maybe it would be better to always return the slots, and then have accessor functions that demonstrate what properties they have?
There was a problem hiding this comment.
For one thing, the frames (slots) may not be obtainable if no debug info is available.
There was a problem hiding this comment.
Do we want to distinguish between no debug info available and an empty array though ?
There was a problem hiding this comment.
I don't see why one would want to.
There was a problem hiding this comment.
OK, I threw away the option and and now return an empty array instead of None.
|
I am thinking of getting rid of the notion of "raw" stack trace frames (type What do you think? |
|
Some use cases for callstack slots are performance-sensitive. For example, @jhjourdan uses them in the statistical profiler (you want to collect them for a reasonably high portion of your allocations while keeping a reasonably low overhead), and I would expect that they are traversed in reporting/statistics/aggregation logic which may also traverse non-small amount of data and have to be fast. I haven't followed the recent discussions (slowly coming back to the real world^W^Wpull requests), and don't have a full picture of the API (maybe having a low-level notion of trace, but no low-level notion of slot, could be enough), but we designed the original API exposure with the idea that easy things should be easy and advanced/low-level things possible, and I liked that design choice. |
|
I think @diml also has some performance sensitive use cases for callstacks. Raw traces have to be kept as they are the ones that can be reinstalled with That being said, I would like the normal API to be as simple as possible, the |
|
... I might also add a richer version of stack traces which includes the names and values of locals in the future. |
|
I did some experiment at some point, but right now we have nothing in production that is performance sensitive and relies on callstacks. Currently, the only operations you can do on a |
|
I heard feedback recently (during the MirageOS retreat) that the exception-backtrace API is hard to use, so I still think that there would be user interest in having an |
This PR introduces modules
ExnandStack_trace, corresponding to the built-in typeexn, taking advantage at the same time to reorganize thePrintexcAPI (which would eventually be deprecated).The differences between
PrintexcandExn/Stack_traceare roughly the following:Exn.tforexnis added;PrintexcExn/Stack_traceprintcatchprint_backtraceget_backtracerecord_backtraceExn.set_tracingbacktrace_statusBacktrace.tracingregister_printerExn.register_printerraw_backtraceStack_trace.tget_raw_backtraceExn.last_traceprint_raw_backtraceraw_backtrace_to_stringStack_trace.to_stringraise_with_backtraceExn.raise_with_traceget_callstackStack_trace.currentbacktrace_slotbacktrace_slotsStack_trace.slotsSlot.tStack_trace.Frame.tSlot.is_raiseStack_trace.Frame.is_raiseSlot.is_inlineStack_trace.Frame.is_inlineSlot.formatStack_trace.Frame.formatSlot.locationStack_trace.Frame.locationlocationStack_trace.Frame.locationraw_backtrace_slotStack_trace.Frame.Raw.traw_backtrace_lengthStack_trace.lengthget_raw_backtrace_slotStack_trace.Frame.Raw.getconvert_raw_backtrace_slotStack_trace.Frame.of_rawget_raw_backtrace_next_slotStack_trace.Frame.Raw.nextexn_slot_idExn.idexn_slot_nameExn.namePrintexc.tintroduced in Add paths for built-in types #1876 is removed.