There was one huge pain point during the development of gotobed: I would put in bugs without knowing and things would go bad. A plugin is effectively a smaller application that is embedded into Koreader but Koreader did very little to point out what went wrong in case something goes wrong.

This behavior is actually implemented by the following piece of code. At plugin load time the function sandboxPluginEventHandlers would be called with the plugin table. This table is inspected for key,value pairs that match an event handler: the key matches the prefix "on" and the value is of type function. For each pair that matches, the event handler is wrapped into an anonymous function that executes a protected call. Errors are logged as is without further processing.

-- koreader/frontend/pluginloader.lua

local function sandboxPluginEventHandlers(plugin)
    for key, value in pairs(plugin) do
        if key:sub(1, 2) == "on" and type(value) == "function" then
            plugin[key] = function(self, ...)
                local ok, re = pcall(value, self, ...)
                if ok then
                    return re
                else
                    logger.err("failed to call event handler", key, re)
                    return false
                end
            end
        end
    end
end

This implementation works but does not make me, the plugin developer, happy. Let’s break it down.

This is the behavior of pcall.

If there are no errors, pcall returns true, plus any values returned by the call. Otherwise, it returns false, plus the error message.

Programming in Lua - Error Handling and Exceptions

Moreover, anything can be an error but it’s most likely an error message. If it’s an internal error, Lua will generate the error message. As an example consider the following errors.

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> 1 + "a"
stdin:1: unexpected symbol near '1'
> error ("I am a naughty bug") 
stdin:1: I am a naughty bug
stack traceback:
	[C]: in function 'error'
	stdin:1: in main chunk
	[C]: ?

The first error message is quite simple but the second is much more detailed: there is a stack-trace that tells me what happened and where, meaning I can actually debug this.

If I could get a description for the first error that is as detailed as the second one I would be happy. Internal errors have been the majority during my delelopment, which means that having good stack-traces would improve much my experience. Moreover a short investigation reveals that, at the time of writing, there are only 6 instances of error calls in the plugin shipped with Koreader. This means that they are on the same boat and would benefit from stack-traces on internal errors.

Because the handlers in the plugin are only invocked in a protected call, errors raised in the plugin will not bubble up to Koreader. Our problem is the following:

Frequently, when an error happens, we want more debug information than only the location where the error occurred. At least, we want a traceback, showing the complete stack of calls leading to the error. When pcall returns its error message, it destroys part of the stack (the part that went from it to the error point). Consequently, if we want a traceback, we must build it before pcall returns.

Programming in Lua - Error Messages and Tracebacks

So either I, the plugin developer, build the stack-trace and stuff it in the error message myself or there is no stack-trace. Lucky me that error does this implicitly. Unlucky me that internal Lua errors do not do so.

Turns out that if we ask nicely Lua will not throw away the stack information.

To do that, Lua provides the xpcall function. Besides the function to be called, it receives a second argument, an error handler function. In case of errors, Lua calls that error handler before the stack unwinds, so that it can use the debug library to gather any extra information it wants about the error.

Programming in Lua - Error Messages and Tracebacks

So that’s what I implemented recently in plugin handlers tracebacks.

Have a better debugging experience!