Hammerspoon / hammerspoon

Staggeringly powerful macOS desktop automation with Lua
http://www.hammerspoon.org
MIT License
11.91k stars 579 forks source link

`hs.application.launchOrFocus` seems to cause intermittent hangs #3374

Open hbhargava7 opened 1 year ago

hbhargava7 commented 1 year ago

Experiencing an intermittent (1-2x per day) hang in Hammerspoon that causes:

  1. Beach ball on Hammerspoon icon in taskbar
  2. Hammerspoon doesn't respond
  3. I cannot open new windows of any application
  4. Chrome for some reason will open but not load new tabs (loading indicators spins counterclockwise slowly)
  5. Activity Monitor shows Hammerspoon "Not Responding". Mem usage during the last hang was 182.6Mb.

Since it's an intermittent issue, it's been difficult to debug. My Console doesn't show any red flags.

I believe the issue is triggered when I call my application hotkeys (full init.lua below):

-- [2] APPLICATION HOTKEYS

-- [2A] Hotkeys to open specific applications
function open(name)
    return function()
        hs.application.launchOrFocus(name)
        if name == 'Finder' then
            hs.appfinder.appFromName(name):activate()
        end
    end
end

hs.hotkey.bind({"alt"}, "F", open("Finder"))
hs.hotkey.bind({"alt"}, "C", open("Google Chrome"))
hs.hotkey.bind({"alt"}, "T", open("iTerm"))
hs.hotkey.bind({"alt"}, "V", open("Visual Studio Code"))
hs.hotkey.bind({"alt"}, "S", open("Sublime Text"))
hs.hotkey.bind({"alt"}, "M", open("Messages"))
hs.hotkey.bind({"alt"}, "K", open("Fantastical"))
hs.hotkey.bind({"alt"}, "N", open("Notion"))
hs.hotkey.bind({"alt", "shift"}, "S", open("Spotify"))
hs.hotkey.bind({"alt", "shift"}, "F", open("Figma"))

-- [2B] Hotkeys to open specific Chrome tabs
-- Open specific chrome tabs based on string search within the URL
function chrome_active_tab_with_url(url)
    return function()
        hs.osascript.javascript([[
            // below is javascript code
            (function() {
                var chrome = Application('Google Chrome');
                chrome.activate();

                for (win of chrome.windows()) {
                    var tabIndex = win.tabs().findIndex(tab => tab.url().match(']] .. url .. [['));
                    if (tabIndex != -1) {
                        win.activeTabIndex = (tabIndex + 1);
                        win.index = 1
                    }
                }

            })();
            // end of javascript
        ]])
    end
end

--- Bind
hs.hotkey.bind({"alt"}, "P", chrome_active_tab_with_url("polymail"))
hs.hotkey.bind({"alt"}, "A", chrome_active_tab_with_url("notion.so/hershbhargava/Master-Agenda"))

I ran Activity Monitor -> Sample during a hang. Note that the hangs are detected by Activity Monitor. I'm not sure how to interpret that info, but I saw it was done in #2923 by @n8henrie. Can provide the rest of the file if useful.

Screenshot 2023-01-31 at 6 36 34 PM
Analysis of sampling Hammerspoon (pid 1646) every 1 millisecond
Process:         Hammerspoon [1646]
Path:            /Applications/Hammerspoon.app/Contents/MacOS/Hammerspoon
Load Address:    0x106302000
Identifier:      org.hammerspoon.Hammerspoon
Version:         0.9.97 (6267)
Code Type:       X86-64
Platform:        macOS
Parent Process:  ??? [1]

Date/Time:       2023-01-31 16:42:36.047 -0800
Launch Time:     2023-01-23 23:33:10.479 -0800
OS Version:      macOS 13.2 (22D49)
Report Version:  7
Analysis Tool:   /usr/bin/sample

Physical footprint:         182.5M
Physical footprint (peak):  247.4M
Idle exit: untracked
----

Call graph:
    2571 Thread_17883   DispatchQueue_1: com.apple.main-thread  (serial)
    + 2571 ???  (in dyld)  load address 0x7ff80283c000 + 0x6310  [0x7ff802842310]
    +   2571 ???  (in AppKit)  load address 0x7ff805d61000 + 0x3797  [0x7ff805d64797]
    +     2571 ???  (in AppKit)  load address 0x7ff805d61000 + 0x2f757  [0x7ff805d90757]
    +       2571 ???  (in AppKit)  load address 0x7ff805d61000 + 0x3d114  [0x7ff805d9e114]
    +         2571 ???  (in AppKit)  load address 0x7ff805d61000 + 0x3e293  [0x7ff805d9f293]
    +           2571 ???  (in HIToolbox)  load address 0x7ff80c56d000 + 0x2f2b3  [0x7ff80c59c2b3]
    +             2571 ???  (in HIToolbox)  load address 0x7ff80c56d000 + 0x2f576  [0x7ff80c59c576]
    +               2571 ???  (in HIToolbox)  load address 0x7ff80c56d000 + 0x2f766  [0x7ff80c59c766]
    +                 2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7db60  [0x7ff802c4eb60]
    +                   2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7e72a  [0x7ff802c4f72a]
    +                     2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7fcbe  [0x7ff802c50cbe]
    +                       2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x18a8  [0x7ff802b368a8]
    +                         2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x8635  [0x7ff802b3d635]
    +                           2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0xf604  [0x7ff802b44604]
    +                             2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x15c2  [0x7ff802b365c2]
    2571 Thread_18920: com.apple.NSEventThread
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c7b  [0x7ff802b70c7b]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x6259  [0x7ff802b75259]
    +     2571 ???  (in AppKit)  load address 0x7ff805d61000 + 0x19f129  [0x7ff805f00129]
    +       2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7db60  [0x7ff802c4eb60]
    +         2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7e72a  [0x7ff802c4f72a]
    +           2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7fcbe  [0x7ff802c50cbe]
    +             2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x18a8  [0x7ff802b368a8]
    +               2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x8635  [0x7ff802b3d635]
    +                 2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0xf604  [0x7ff802b44604]
    +                   2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x15c2  [0x7ff802b365c2]
    2571 Thread_259479
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c7b  [0x7ff802b70c7b]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x6259  [0x7ff802b75259]
    +     2571 ???  (in Foundation)  load address 0x7ff803a4c000 + 0x583bc  [0x7ff803aa43bc]
    +       2571 ???  (in JavaScriptAppleEvents)  load address 0x7ffb0ce8e000 + 0x35d55  [0x7ffb0cec3d55]
    +         2571 ???  (in Foundation)  load address 0x7ff803a4c000 + 0xe1c3c  [0x7ff803b2dc3c]
    +           2571 ???  (in Foundation)  load address 0x7ff803a4c000 + 0x5f02a  [0x7ff803aab02a]
    +             2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7db60  [0x7ff802c4eb60]
    +               2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7e72a  [0x7ff802c4f72a]
    +                 2571 ???  (in CoreFoundation)  load address 0x7ff802bd1000 + 0x7fcbe  [0x7ff802c50cbe]
    +                   2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x18a8  [0x7ff802b368a8]
    +                     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x8635  [0x7ff802b3d635]
    +                       2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0xf604  [0x7ff802b44604]
    +                         2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x15c2  [0x7ff802b365c2]
    2571 Thread_6141582
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142840
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142841
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142842
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   1612 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +   ! 1612 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    +   959 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142843
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142844
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142845
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142846
    + 2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
    +   2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
    +     2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
    2571 Thread_6142865
      2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
        2571 ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]
          2571 ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]

Total number in stack (recursive counted multiple, when >=5):
        10       ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]
        9       ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x1c67  [0x7ff802b70c67]
        9       ???  (in libsystem_pthread.dylib)  load address 0x7ff802b6f000 + 0x2d47  [0x7ff802b71d47]

Sort by top of stack, same collapsed (when >= 5):
        ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x2cb2  [0x7ff802b37cb2]        23139
        ???  (in libsystem_kernel.dylib)  load address 0x7ff802b35000 + 0x15c2  [0x7ff802b365c2]        7713
hbhargava7 commented 1 year ago

Full config:

-- [1] WINDOW MANAGEMENT

-- [1A] arrow key window management via MiroWindowsManager
local hyper = {"cmd"}

hs.loadSpoon("MiroWindowsManager")
hs.window.animationDuration = 0
spoon.MiroWindowsManager.sizes_x = {2}

spoon.MiroWindowsManager:bindHotkeys({
  up = {hyper, "up"},
  right = {hyper, "right"},
  down = {hyper, "down"},
  left = {hyper, "left"},
  fullscreen = {hyper, "up"}
})

-- [1B] cmd + option + hjkl window management

positions = {
  maximized = hs.layout.maximized,
  centered = {x=0.15, y=0.15, w=0.7, h=0.7},
  centered33 = {x=(1-0.333)/2, y=0, w=0.333, h=1},

  left33 = {x=0, y=0, w=0.333, h=1},
  left50 = hs.layout.left50,
  left66 = {x=0, y=0, w=0.666, h=1},

  right33 = {x=0.666, y=0, w=0.333, h=1},
  right50 = hs.layout.right50,
  right66 = {x=0.34, y=0, w=0.666, h=1},

  upper50 = {x=0, y=0, w=1, h=0.5},
  upper50Left50 = {x=0, y=0, w=0.5, h=0.5},
  upper50Right50 = {x=0.5, y=0, w=0.5, h=0.5},

  lower50 = {x=0, y=0.5, w=1, h=0.5},
  lower50Left50 = {x=0, y=0.5, w=0.5, h=0.5},
  lower50Right50 = {x=0.5, y=0.5, w=0.5, h=0.5}
}

function bindKey(key, fn)
  hs.hotkey.bind({"alt", "shift"}, key, fn)
end

grid = {
  {key="h", units={positions.left50, positions.left66, positions.left33}},
  {key="k", units={positions.maximized, positions.centered33}},
  {key="j", units={positions.lower50, positions.maximized}},
  {key="l", units={positions.right50, positions.right66, positions.right33}},
}

hs.fnutils.each(grid, function(entry)
  bindKey(entry.key, function()
    local units = entry.units
    local screen = hs.screen.mainScreen()
    local window = hs.window.focusedWindow()
    local windowGeo = window:frame()

    local index = 0
    hs.fnutils.find(units, function(unit)
      index = index + 1

      local geo = hs.geometry.new(unit):fromUnitRect(screen:frame())

      local x_within_tolerance = math.abs(geo.x - windowGeo.x) < 0.03 * windowGeo.w
      local y_within_tolerance = math.abs(geo.y - windowGeo.y) < 0.03 * windowGeo.h
      local w_within_tolerance = math.abs(geo.w - windowGeo.w) < 0.03 * windowGeo.w
      local h_within_tolerance = math.abs(geo.h - windowGeo.h) < 0.03 * windowGeo.h

      return x_within_tolerance and y_within_tolerance and w_within_tolerance and h_within_tolerance

    end)

    if index == #units then index = 0 end

    window:moveToUnit(units[index + 1])
  end)
end)

-- [1C] switch focus left or right
function focus_right()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  -- Check if the window is already at the right edge of the screen
  if f.x + f.w == max.x + max.w then
    -- If it is, move the focus to the window to the right of the current screen
    local newScreen = screen:toEast()
    if newScreen then
      -- Get the list of windows for the new screen
      local windows = hs.fnutils.filter(hs.window.orderedWindows(), function(w) return w:screen() == newScreen end)
      -- If there are any windows on the new screen, focus the last one
      if #windows > 0 then windows[#windows]:focus() end
    end
  else
    -- If the window is not at the right edge of the screen, get the list of windows to the right
    local windows = hs.fnutils.filter(hs.window.orderedWindows(), function(w)
      local wf = w:frame()
      return wf.x > f.x and wf.y == f.y
    end)
    -- If there are any windows to the right, focus the frontmost one
    if #windows > 0 then windows[1]:focus() end
  end
end

function focus_left()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  -- Check if the window is already at the left edge of the screen
  if f.x == max.x then
    -- If it is, move the focus to the window to the left of the current screen
    local newScreen = screen:toWest()
    if newScreen then
      -- Get the list of windows for the new screen
      local windows = hs.fnutils.filter(hs.window.orderedWindows(), function(w) return w:screen() == newScreen end)
      -- If there are any windows on the new screen, focus the last one
      if #windows > 0 then windows[#windows]:focus() end
    end
  else
    -- If the window is not at the left edge of the screen, get the list of windows to the left
    local windows = hs.fnutils.filter(hs.window.orderedWindows(), function(w)
      local wf = w:frame()
      return wf.x < f.x and wf.y == f.y
    end)
    -- If there are any windows to the left, focus the frontmost one
    if #windows > 0 then windows[1]:focus() end
  end
end

hs.hotkey.bind({'cmd', 'alt'}, 'l', focus_right)
hs.hotkey.bind({'cmd', 'alt'}, 'h', focus_left)

-- [2] APPLICATION HOTKEYS

-- [2A] Hotkeys to open specific applications
function open(name)
    return function()
        hs.application.launchOrFocus(name)
        if name == 'Finder' then
            hs.appfinder.appFromName(name):activate()
        end
    end
end

hs.hotkey.bind({"alt"}, "F", open("Finder"))
hs.hotkey.bind({"alt"}, "C", open("Google Chrome"))
hs.hotkey.bind({"alt"}, "T", open("iTerm"))
hs.hotkey.bind({"alt"}, "V", open("Visual Studio Code"))
hs.hotkey.bind({"alt"}, "S", open("Sublime Text"))
hs.hotkey.bind({"alt"}, "M", open("Messages"))
hs.hotkey.bind({"alt"}, "K", open("Fantastical"))
hs.hotkey.bind({"alt"}, "N", open("Notion"))
hs.hotkey.bind({"alt", "shift"}, "S", open("Spotify"))
hs.hotkey.bind({"alt", "shift"}, "F", open("Figma"))

-- [2B] Hotkeys to open specific Chrome tabs
-- Open specific chrome tabs based on string search within the URL
function chrome_active_tab_with_url(url)
    return function()
        hs.osascript.javascript([[
            // below is javascript code
            (function() {
                var chrome = Application('Google Chrome');
                chrome.activate();

                for (win of chrome.windows()) {
                    var tabIndex = win.tabs().findIndex(tab => tab.url().match(']] .. url .. [['));
                    if (tabIndex != -1) {
                        win.activeTabIndex = (tabIndex + 1);
                        win.index = 1
                    }
                }

            })();
            // end of javascript
        ]])
    end
end

--- Bind
hs.hotkey.bind({"alt"}, "P", chrome_active_tab_with_url("polymail"))
hs.hotkey.bind({"alt"}, "A", chrome_active_tab_with_url("notion.so"))

-- 3 EMOJI SELECTOR
-- Build the list of emojis to be displayed.
local choices = {}
for _, emoji in ipairs(hs.json.decode(io.open("emojis/emojis.json"):read())) do
    table.insert(choices,
        {text=emoji['name'],
            subText=table.concat(emoji['kwds'], ", "),
            image=hs.image.imageFromPath("emojis/" .. emoji['id'] .. ".png"),
            chars=emoji['chars']
        })
end

-- Focus the last used window.
local function focusLastFocused()
    local wf = hs.window.filter
    local lastFocused = wf.defaultCurrentSpace:getWindows(wf.sortByFocusedLast)
    if #lastFocused > 0 then lastFocused[1]:focus() end
end

-- Create the chooser.
-- On selection, copy the emoji and type it into the focused application.
local chooser = hs.chooser.new(function(choice)
    if not choice then focusLastFocused(); return end
    hs.pasteboard.setContents(choice["chars"])
    focusLastFocused()
    hs.eventtap.keyStrokes(hs.pasteboard.getContents())
end)

chooser:searchSubText(true)
chooser:choices(choices)

chooser:rows(5)
chooser:bgDark(true)

hs.hotkey.bind({"cmd", "alt"}, "E", function() chooser:show() end)
saltycrane commented 1 year ago

I'm also getting hangs (beach ball on Hammerspoon icon in taskbar) after upgrading macOS to Ventura.

brandonkboswell commented 8 months ago

Also running into this.