Walking along the stack frames

Often, an exception is raised from a function but it is not handled in the right away. The exception then travels to the parent calling function. If that function does not catch the exception either, it again travels to the next parent calling function. This process continues until a try-catch block catches the exception. At this point, the program's current stack frame  an execution context of where the code is currently running  handles the exception.

It would be tremendously useful if we can see where the exception was originally raised. To do that, let's first try to understand how to retrieve a stack trace that is an array of stack frames. Let's create a simple set of nested function calls such that they throw an error at the end. Consider the following code:

function foo1()
foo2()
end

function foo2()
foo3()
end

function foo3()
throw(ErrorException("bad things happened"))
end

Now, if we execute the foo1 function, we should get an error, as follows:

As you can see, the stack trace shows the execution sequence in reversed order. At the top of the stack trace is the foo3 function. Because we're doing this in the REPL, we do not see a source filename; however, the number 2, as in REPL[17]:2, indicates that an error was thrown from line 2 of the foo3 function.

Let's introduce the stacktrace function now. This function is part of the Base package and it can be used to obtain the current stack trace. As the stacktrace function returns an array of StackFrame, it would be nice if we could create a function to display it nicely. We can define a function to print the stack trace, as follows:

function pretty_print_stacktrace(trace)
for (i,v) in enumerate(trace)
println(i, " => ", v)
end
end

As we want to handle exceptions properly, we will now update the foo1 function by wrapping the call to foo2 with a try-catch block. In the catch block, we will also print the stack trace so that we can further debug the issue:

function foo1()
try
foo2()
catch
println("handling error gracefully")
pretty_print_stacktrace(stacktrace())
end
end

Let's run the foo1 function now:

Oops! What happened to foo2 and foo3? The exception was thrown from foo3 but we can no longer see them in the stack trace. This is because we have caught the exception, and from Julia's perspective, it is already handled and the current execution context is in foo1 already.

In order to address this issue, there is another function in the Base package called catch_backtrace. It gives us the backtrace of the current exception so we know where the exception was originally raised. We just need to update the foo1 function as follows:

function foo1()
try
foo2()
catch
println("handling error gracefully")
pretty_print_stacktrace(stacktrace(catch_backtrace()))
end
end

Then, if we run foo1 again, we get the following results, where foo3 and foo2 are back to the stack trace:

Note that the use of catch_backtrace must be within the catch block. If it is called outside of a catch block, it would return an empty backtrace.

Next, we will look at a different aspect of exception handling  performance impact.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.114.38