Merge pull request #712 from psychon/protected_calls

Protected calls (gears.protected_call)
This commit is contained in:
Daniel Hahler 2016-02-28 22:16:13 +01:00
commit f874b0ad2e
10 changed files with 128 additions and 55 deletions

View File

@ -20,13 +20,13 @@ local beautiful = require("beautiful")
local dpi = require("beautiful").xresources.apply_dpi local dpi = require("beautiful").xresources.apply_dpi
local object = require("gears.object") local object = require("gears.object")
local surface = require("gears.surface") local surface = require("gears.surface")
local protected_call = require("gears.protected_call")
local cairo = require("lgi").cairo local cairo = require("lgi").cairo
local setmetatable = setmetatable local setmetatable = setmetatable
local tonumber = tonumber local tonumber = tonumber
local string = string local string = string
local ipairs = ipairs local ipairs = ipairs
local pairs = pairs local pairs = pairs
local pcall = pcall
local print = print local print = print
local table = table local table = table
local type = type local type = type
@ -374,12 +374,8 @@ function menu:add(args, index)
local theme = load_theme(args.theme or {}, self.theme) local theme = load_theme(args.theme or {}, self.theme)
args.theme = theme args.theme = theme
args.new = args.new or menu.entry args.new = args.new or menu.entry
local success, item = pcall(args.new, self, args) local item = protected_call(args.new, self, args)
if not success then if (not item) or (not item.widget) then
print("Error while creating menu entry: " .. item)
return
end
if not item.widget then
print("Error while checking menu entry: no property widget found.") print("Error while checking menu entry: no property widget found.")
return return
end end

View File

@ -24,6 +24,7 @@ local lgi = require("lgi")
local Gio = lgi.Gio local Gio = lgi.Gio
local GLib = lgi.GLib local GLib = lgi.GLib
local util = require("awful.util") local util = require("awful.util")
local protected_call = require("gears.protected_call")
local spawn = {} local spawn = {}
@ -205,10 +206,7 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close)
stream:close() stream:close()
end end
if done_callback then if done_callback then
xpcall(done_callback, function(err) protected_call(done_callback)
print(debug.traceback("Error while calling done_callback:"
.. tostring(err), 2))
end)
end end
end end
local start_read, finish_read local start_read, finish_read
@ -226,14 +224,9 @@ function spawn.read_lines(input_stream, line_callback, done_callback, close)
done() done()
else else
-- Read a line -- Read a line
xpcall(function()
-- This needs tostring() for older lgi versions which returned -- This needs tostring() for older lgi versions which returned
-- "GLib.Bytes" instead of Lua strings (I guess) -- "GLib.Bytes" instead of Lua strings (I guess)
line_callback(tostring(line)) protected_call(line_callback, tostring(line))
end, function(err)
print(debug.traceback("Error while calling line_callback: "
.. tostring(err), 2))
end)
-- Read the next line -- Read the next line
start_read() start_read()

View File

@ -18,6 +18,7 @@ local lgi = require("lgi")
local Pango = lgi.Pango local Pango = lgi.Pango
local PangoCairo = lgi.PangoCairo local PangoCairo = lgi.PangoCairo
local gears_debug = require("gears.debug") local gears_debug = require("gears.debug")
local protected_call = require("gears.protected_call")
local xresources = require("beautiful.xresources") local xresources = require("beautiful.xresources")
@ -109,7 +110,6 @@ end
-- containing all the theme values. -- containing all the theme values.
function beautiful.init(config) function beautiful.init(config)
if config then if config then
local success
local homedir = os.getenv("HOME") local homedir = os.getenv("HOME")
-- If config is the path to the theme file, -- If config is the path to the theme file,
@ -118,16 +118,12 @@ function beautiful.init(config)
if type(config) == 'string' then if type(config) == 'string' then
-- Expand the '~' $HOME shortcut -- Expand the '~' $HOME shortcut
config = config:gsub("^~/", homedir .. "/") config = config:gsub("^~/", homedir .. "/")
success, theme = xpcall(function() return dofile(config) end, theme = protected_call(dofile, config)
debug.traceback)
elseif type(config) == 'table' then elseif type(config) == 'table' then
success = true
theme = config theme = config
end end
if not success then if theme then
return gears_debug.print_error("beautiful: error loading theme file " .. theme)
elseif theme then
-- expand '~' -- expand '~'
if homedir then if homedir then
for k, v in pairs(theme) do for k, v in pairs(theme) do

View File

@ -62,13 +62,13 @@ function debug.dump(data, tag, depth)
print(debug.dump_return(data, tag, depth)) print(debug.dump_return(data, tag, depth))
end end
-- Print an warning message --- Print an warning message
-- @tparam string message The warning message to print -- @tparam string message The warning message to print
function debug.print_warning(message) function debug.print_warning(message)
io.stderr:write(os.date("%Y-%m-%d %T W: ") .. tostring(message) .. "\n") io.stderr:write(os.date("%Y-%m-%d %T W: ") .. tostring(message) .. "\n")
end end
-- Print an error message --- Print an error message
-- @tparam string message The error message to print -- @tparam string message The error message to print
function debug.print_error(message) function debug.print_error(message)
io.stderr:write(os.date("%Y-%m-%d %T E: ") .. tostring(message) .. "\n") io.stderr:write(os.date("%Y-%m-%d %T E: ") .. tostring(message) .. "\n")

View File

@ -17,6 +17,7 @@ return
cache = require("gears.cache"); cache = require("gears.cache");
matrix = require("gears.matrix"); matrix = require("gears.matrix");
shape = require("gears.shape"); shape = require("gears.shape");
protected_call = require("gears.protected_call");
} }
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,58 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2016 Uli Schlachter
-- @release @AWESOME_VERSION@
-- @module gears.protected_call
---------------------------------------------------------------------------
local gdebug = require("gears.debug")
local tostring = tostring
local traceback = debug.traceback
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local xpcall = xpcall
local protected_call = {}
local function error_handler(err)
gdebug.print_error(traceback("Error during a protected call: " .. tostring(err)))
end
local function handle_result(success, ...)
if success then
return ...
end
end
local do_pcall
if _VERSION <= "Lua 5.1" then
-- Lua 5.1 doesn't support arguments in xpcall :-(
do_pcall = function(func, ...)
local args = { ... }
return handle_result(xpcall(function()
return func(unpack(args))
end, error_handler))
end
else
do_pcall = function(func, ...)
return handle_result(xpcall(func, error_handler, ...))
end
end
--- Call a function in protected mode and handle error-reporting.
-- If the function call succeeds, all results of the function are returned.
-- Otherwise, an error message is printed and nothing is returned.
-- @tparam function func The function to call
-- @param ... Arguments to the function
-- @return The result of the given function, or nothing if an error occurred.
function protected_call.call(func, ...)
return do_pcall(func, ...)
end
local pcall_mt = {}
function pcall_mt:__call(...)
return do_pcall(...)
end
return setmetatable(protected_call, pcall_mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -17,6 +17,7 @@ local traceback = debug.traceback
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
local glib = require("lgi").GLib local glib = require("lgi").GLib
local object = require("gears.object") local object = require("gears.object")
local protected_call = require("gears.protected_call")
--- Timer objects. This type of object is useful when triggering events repeatedly. --- Timer objects. This type of object is useful when triggering events repeatedly.
-- The timer will emit the "timeout" signal every N seconds, N being the timeout -- The timer will emit the "timeout" signal every N seconds, N being the timeout
@ -43,11 +44,7 @@ function timer:start()
return return
end end
self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function() self.data.source_id = glib.timeout_add(glib.PRIORITY_DEFAULT, self.data.timeout * 1000, function()
xpcall(function() protected_call(self.emit_signal, self, "timeout")
self:emit_signal("timeout")
end, function(err)
print(debug.traceback("Error during executing timeout handler: "..tostring(err)))
end)
return true return true
end) end)
self:emit_signal("start") self:emit_signal("start")
@ -126,10 +123,8 @@ end
function timer.start_new(timeout, callback) function timer.start_new(timeout, callback)
local t = timer.new({ timeout = timeout }) local t = timer.new({ timeout = timeout })
t:connect_signal("timeout", function() t:connect_signal("timeout", function()
local success, cont = xpcall(callback, function(err) local cont = protected_call(callback)
print(debug.traceback("Error during executing timeout handler: "..tostring(err), 2)) if not cont then
end)
if not success or not cont then
t:stop() t:stop()
end end
end) end)
@ -160,11 +155,7 @@ end
local delayed_calls = {} local delayed_calls = {}
capi.awesome.connect_signal("refresh", function() capi.awesome.connect_signal("refresh", function()
for _, callback in ipairs(delayed_calls) do for _, callback in ipairs(delayed_calls) do
xpcall(function() protected_call(unpack(callback))
callback[1](unpack(callback, 2))
end, function(err)
print(debug.traceback("Error during delayed call: "..tostring(err), 2))
end)
end end
delayed_calls = {} delayed_calls = {}
end) end)

View File

@ -10,6 +10,7 @@
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local matrix = require("gears.matrix") local matrix = require("gears.matrix")
local protected_call = require("gears.protected_call")
local cairo = require("lgi").cairo local cairo = require("lgi").cairo
local base = require("wibox.widget.base") local base = require("wibox.widget.base")
local no_parent = base.no_parent_I_know_what_I_am_doing local no_parent = base.no_parent_I_know_what_I_am_doing
@ -274,17 +275,10 @@ function hierarchy:draw(context, cr)
local opacity = widget.opacity local opacity = widget.opacity
local function call(func, extra_arg1, extra_arg2) local function call(func, extra_arg1, extra_arg2)
if not func then return end if not func then return end
local function error_function(err)
print(debug.traceback("Error while drawing widget: " .. tostring(err), 2))
end
if not extra_arg2 then if not extra_arg2 then
xpcall(function() protected_call(func, widget, context, cr, self:get_size())
func(widget, context, cr, self:get_size())
end, error_function)
else else
xpcall(function() protected_call(func, widget, context, extra_arg1, extra_arg2, cr, self:get_size())
func(widget, context, extra_arg1, extra_arg2, cr, self:get_size())
end, error_function)
end end
end end

View File

@ -8,6 +8,7 @@
local object = require("gears.object") local object = require("gears.object")
local cache = require("gears.cache") local cache = require("gears.cache")
local matrix = require("gears.matrix") local matrix = require("gears.matrix")
local protected_call = require("gears.protected_call")
local util = require("awful.util") local util = require("awful.util")
local setmetatable = setmetatable local setmetatable = setmetatable
local pairs = pairs local pairs = pairs
@ -144,7 +145,7 @@ local widget_dependencies = setmetatable({}, { __mode = "kv" })
local function get_cache(widget, kind) local function get_cache(widget, kind)
if not widget._widget_caches[kind] then if not widget._widget_caches[kind] then
widget._widget_caches[kind] = cache.new(function(...) widget._widget_caches[kind] = cache.new(function(...)
return widget[kind](widget, ...) return protected_call(widget[kind], widget, ...)
end) end)
end end
return widget._widget_caches[kind] return widget._widget_caches[kind]
@ -222,9 +223,9 @@ function base.fit_widget(parent, context, widget, width, height)
end end
end end
-- Apply forced size -- Apply forced size and handle nil's
w = widget._forced_width or w w = widget._forced_width or w or 0
h = widget._forced_height or h h = widget._forced_height or h or 0
-- Also sanitize the output. -- Also sanitize the output.
w = math.max(0, math.min(w, width)) w = math.max(0, math.min(w, width))

View File

@ -0,0 +1,43 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2016 Uli Schlachter
---------------------------------------------------------------------------
local gdebug = require("gears.debug")
local protected_call = require("gears.protected_call")
describe("gears.protected_call", function()
-- Stop the error reporting during tests
local orig_print_error = gdebug.print_error
local errors
before_each(function()
errors = {}
gdebug.print_error = function(err)
table.insert(errors, err)
end
end)
after_each(function()
gdebug.print_error = orig_print_error
end)
it("Call with arguments and result", function()
local called = false
local function f(...)
called = true
assert.is_same({ ... }, { 1, "second" })
return "first", 2
end
local results = { protected_call(f, 1, "second") }
assert.is_true(called)
assert.is_same({ "first", 2 }, results)
assert.is_same(errors, {})
end)
it("Call with error", function()
assert.is_same({}, { protected_call(error, "I was called") })
assert.is_same(#errors, 1)
assert.is_truthy(string.find(errors[1], "I was called"))
end)
end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80