awesomeWM / awesome

awesome window manager

Home Page:https://awesomewm.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

BUG: `gears.timer {call_now=true}` doesn't pass `self` (`ret`)

aarondill opened this issue · comments

Output of awesome --version:

Tested in both 4.3 and HEAD
> awesome --version
awesome v4.3 (Too long)
 • Compiled against Lua 5.3.6 (running with Lua 5.3)
 • D-Bus support: ✔
 • execinfo support: ✔
 • xcb-randr version: 1.6
 • LGI version: 0.9.2
> ./build/awesome --version
awesome v4.3-1647-ge6f5c798-dirty (Too long)
 • Compiled against Lua 5.4.6 (running with 0.9.2)
 • API level: 4
 • D-Bus support: yes
 • xcb-errors support: no
 • execinfo support: yes
 • xcb-randr version: 1.6
 • LGI version: /usr/share/lua/5.4/lgi/version.lua
 • Transparency enabled: yes
 • Custom search paths: no

How to reproduce the issue:

local gtimer = require("gears.timer")
local naughty = require("naughty")
local function notify(val) --- A simple shim around naughty to support the V5 or V4 apis
  if naughty.notification then return naughty.notification({ message = tostring(val) }) end
  return naughty.notify({ text = tostring(val) })
end

gtimer.new({
  call_now = true, -- This introduces the broken behavior
  autostart = true, -- Call :start() for me, required to show the correct behavior (through :emit_signal('timeout'))
  timeout = 1, -- A short timeout to easily reproduce the issue
  single_shot = true, -- Only run this once, not needed, but simplifies cleanup after the demonstration
  callback = notify, -- Call notify on timeout, just notifies the first value
})

Actual result:

A notification is received nil from the call_now
A second notification is received 1 second later (due to timeout) table: <address> (the timer)

Expected result:

call_now passes the timer to the callback like :emit_signal('timeout') does, resulting in two identical notifications both displaying the timer's memory address.

Possible implementation:
The code currently looks like this:

    if args.callback then
        if args.call_now then
            args.callback()
        end
        ret:connect_signal("timeout", args.callback)
    end

    if args.single_shot then
        ret:connect_signal("timeout", function() ret:stop() end)
    end

It would be simpler and avoid this sort of issue in the future to do this:

    if args.callback then
        ret:connect_signal("timeout", args.callback)
    end
    if args.call_now then
            ret:emit_signal("timeout")
    end
    if args.single_shot then
        ret:connect_signal("timeout", function() ret:stop() end)
    end

This makes call_now call :emit_signal("timeout") which would pass the timer to args.callback (the only connected listener)

This would also be more future-proof to potential changes to :emit_signal(), as it would make the actual timer behavior (in :start()) and the .call_now behavior match exactly.

alternatively, keep the current code and just call args.callback(ret)

The current hack to avoid this is:

-gtimer.new({
-  call_now = true,
+local timer = gtimer.new({
+  call_now = false,
  autostart = true,
  timeout = 1,
  single_shot = true,
  callback = notify,
})
+notify(timer)

which adds an additional variable declaration and error-prone call to callback, which call_now is intended to avoid.