[erlang-questions] EEP 47: Syntax in try/catch to retrieve the stacktrace directly

Richard Carlsson carlsson.richard@REDACTED
Mon Dec 18 15:33:38 CET 2017


A discussion at work prompted me to think a bit more about this, so I
thought I should share the following reasoning for posterity:

First, as a general principle we don't want patterns to carry a surprising
cost, especially in the case they don't match. For example, it's possible
to match on "abc"++Rest, but not on Prefix++"abc" or Prefix++"abc"++Rest,
since that would imply a linear search through an arbitrary-length string.
The same goes for binary patterns: you can't have a field of unknown
length, except when it's the whole remainder of the binary.

For exceptions, the cost of building the symbolic form of the stack trace
lies in 1) allocating and filling in list cells and tuples on the heap, and
2) traversing tables to map the program pointers on the actual stack to
find the corresponding source file and line number. This is definitely
non-negligible. When an exception happens, only the raw pointers from the
topmost N stack frames are saved, which is pretty cheap, but if someone
wants to look at this, they will need to get the symbolic form that
get_stacktrace() returns (and which will in the future be available in the
optional Trace variable of the exception pattern). If an exception happens
but nobody looks at the stack trace information, only a single small binary
is created and becomes garbage again.

The problem is not when the pattern matches, and you catch the exception.
In that case you were probably willing to take the performance hit anyway,
if you were matching on stack trace info. The problem is when someone
introduces a seemingly innocent try...catch...end that is rarely expected
to actually match, in a control flow where exceptions do happen a lot as
part of the general execution - perhaps using throw for nonlocal returns
out of deep recursion. If you have code like this, being called a lot,
perhaps in a tight loop:

    find(Key) ->
        try search(Key)
        catch
            throw:aborted -> []
        end.

that seems perfectly fine, right? Just a wrapper.  Maybe you use it in a
call to flatmap() and you want empty lists instead of exceptions when
nothing is  found. But now someone else tweaks the search() function (or
you have made the search function a parameter, and someone passes a new
fun) so that it does the following:

    search(Key) ->
        try
            ...  % main body of search
        catch
            _Class:_Term:[{foobar,f,2,_} | _] -> []
        end.

This is also straightforward looking. Just a monkey-patch to fix a known
error case returning an empty list instead. Expected to occur very rarely.

But what happens now, is that every time the called code throws 'aborted' -
which might be often - the inner try/catch will be expanding the exception
to its symbolic form, so that you can check whether it comes from a
particular module/function. Even if this never actually matches, you will
take the penalty of traversing the tables and allocating data on the heap.
When the check has been made and the clause didn't match, the exception
will be re-thrown to the outer try/catch which handles it instead as
expected. The only observed difference is suddenly increased cpu usage and
garbage creation.

We (famously) had a bug in our code some years back that made the system
very unstable, garbage collecting a lot and being generally unresponsive.
It was hard to diagnose, but it was just because of this anti-pattern,
albeit implemented by calling get_stacktrace, ignoring the result and then
rethrowing the exception explicitly. Making it even easier to accidentally
cause this sort of problems by writing a simple little pattern in a
catch-clause would be a worse thing than artificially restricting the
expressiveness of catch-patterns.

That's why the Trace pattern should only be allowed to be an unbound
variable.

        /Richard

2017-11-27 15:31 GMT+01:00 Jesper Louis Andersen <
jesper.louis.andersen@REDACTED>:

> On Mon, Nov 27, 2017 at 3:22 PM Björn Gustavsson <bjorn@REDACTED> wrote:
>
>> On Sat, Nov 25, 2017 at 3:17 PM, Jesper Louis Andersen
>> <jesper.louis.andersen@REDACTED> wrote:
>> [...]
>> >
>> > My major gripe with it is the fact that you cannot pattern match on the
>> > stack trace.
>>
>> Yes, I don't like that inconsistency myself, but I
>> think that the alternatives are worse.
>>
>>
> Yes, I think so too. In a typed language, you would probably declare an
> abstract type for the stack and not provide any kind of matching pattern
> for it. This would force people to handle the stack by printing, and there
> would be no matching on it at all.
>
> Mimicking this behavior in Erlang is probably the sane behavior in this
> case.
>
> I also like Richard's point: matching on the stack will eventually get you
> into trouble.
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20171218/625758d4/attachment.htm>


More information about the erlang-questions mailing list