Blog

Elixir: no se muestran algunas funciones al imprimir stacktrace

Elixir: no se muestran algunas funciones al imprimir stacktrace

Tracing de Erlyberly (izquierda) vs stacktrace (derecha)

En la imagen de arriba se puede ver una comparativa de tracking y stacktrace para una misma llamada.

Imprimo el stracktrace con:

IO.inspect(Process.info(self(), :current_stacktrace), label: "STACKTRACE")

Se puede observar que hay algunas llamadas que no están presentes en el stacktrace:

  • enrich_opts
  • get_data_structure_versions
  • get_field_degree

NOTA: enrich/4 no aparece en Erlyberly porque no la seleccioné antes de empezar la traza.

El motivo es que Elixir hace tail call optimization:

This happens because of tail call optimisation.

Normally, a function has a stack frame containing its local variables and the address of the caller. When it calls another function, another stack frame is added on top, and when the function returns, it discards the stack frame and jumps back to the caller.

However, when the very last thing that a function does is calling another function, there is no need to keep the stack frame, since it’s going to be discarded right after the inner function returns. Instead, the stack frame for the inner function replaces the stack frame for the outer function.

This has the advantage that recursive functions use a constant amount of stack space, but as you’ve noticed it has the disadvantage that stack traces in errors are incomplete.

https://stackoverflow.com/a/56019718

Ver también aquí:

This is a consequence of the last call optimisation. Every function call in the tail position means the stack frame for the calling function is destroyed. I’m not sure there can be anything done about it.

https://github.com/elixir-lang/elixir/issues/6357#issuecomment-316147888

Any recursive Elixir process (which is the majority of them) rely on this behaviour to avoid stack growth. It is more powerful than tail call optimization because you should be able to jump between functions and not be stuck on a single name/arity. And, as @michalmuskala said, it is not something we can change, this behaviour is part of the VM.

I personally think it could be interesting to enable it for some processes, for example the test process, but even doing so means function calls become more expensive in the whole VM, as we now need to check if this feature is enabled or not. I personally don’t know how to implement this feature without incurring performance penalties in the VM.

On the positive side, the VM does provide tools though to address this issue. For example, someone could implement a trace/1 macro that receives an expression and traces all functions called and their arguments:

trace Foo.bar(1, 2, 3)

https://github.com/elixir-lang/elixir/issues/6357#issuecomment-316154723