Hammerspoon / hammerspoon

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

hs.task blocks #3106

Open n8henrie opened 2 years ago

n8henrie commented 2 years ago

Trying to work out https://github.com/Hammerspoon/hammerspoon/issues/2923#issuecomment-1030488609, I decided that hs.task would probably be the best way to launch MacVim so as to not block.

However, it seems to block.

Am I doing something wrong?

hs.hotkey.bind(
  hyper,
  "V",
  nil,
  function()
    hs.task.new(
      "/Applications/MacVim.app/Contents/bin/mvim",
      nil,
      { "/tmp/foo.txt", "/tmp/bar.txt" }
    ):start()
  end
)

When I push HyperV, MacVim launches with the two files open... but then Hammerspoon stops responding until I quit MacVim. Isn't this supposed to be running in the background?

(I've tried /usr/bin/open -a MacVim --args /tmp/foo.txt /tmp/bar.txt; unfortunately it opens the files in separate windows instead of tabs, which is undesirable.)

Relevant: https://github.com/Hammerspoon/hammerspoon/issues/841

n8henrie commented 2 years ago

Something odd is going on.

The binding below works as expected -- shows an alert before, sleeps for 10, then shows after. Hammerspoon remains responsive during the 10 seconds and does not block like I'm seeing above.

hs.hotkey.bind(
  hyper,
  "V",
  function()
    hs.alert.show("before")
    hs.task.new("/bin/sleep", function(...)
      hs.alert.show("after")
    end, {
      "10",
    }):start()
  end
)
n8henrie commented 2 years ago

With the below, after is not called until after I quit MacVim, during which time Hammerspoon is unresponsive.

hs.hotkey.bind(hyper, "V", function()
  hs.task.new("/Applications/MacVim.app/Contents/bin/mvim", function(...)
    hs.alert.show("after")
  end, {
    "/tmp/foo.txt",
    "/tmp/bar.txt",
  }):start()
end)

Looks like it's an issue with forking?

I noticed that running /Applications/MacVim.app/Contents/bin/mvim exits immediately (even though the GUI window remains open), and wondered if this were messing things up somehow. Adding the --nofork flag resolves my issue:

hs.hotkey.bind(hyper, "V", function()
  hs.task.new("/Applications/MacVim.app/Contents/bin/mvim", function(...)
    hs.alert.show("after")
  end, {
    "--nofork",
    "/tmp/foo.txt",
    "/tmp/bar.txt",
  }):start()
end)

Before i close this, is this known or documented behavior?

I'm seeing similar behavior with this MRE; Hammerspoon is blocked while the script is sleeping.

#!/bin/bash
# test.sh
{
  echo foo
  sleep 10
  echo bar
} &
hs.hotkey.bind(hyper, "V", function()
  hs.alert.show("before")
  hs.task.new(
    "/tmp/test.sh",
    function(...)
      hs.alert.show("after")
    end,
    {}
  ):start()
end)

If I change nothing other than remove the & from test.sh, it remains responsive during the sleep.

n8henrie commented 2 years ago

Adding a stream handler also resolves the blocking issue, but results in an error being logged:

hs.hotkey.bind(hyper, "V", function()
  hs.task.new(
    "/Applications/MacVim.app/Contents/bin/mvim",
    nil,
    function(...) end,
    {
      "/tmp/foo.txt",
      "/tmp/bar.txt",
    }
  ):start()
end)
2022-02-08 09:29:38: 09:29:38 ** Warning:   LuaSkin: hs.task terminationHandler block encountered an exception: *** -[NSConcreteFileHandle readDataOfLength:]: Resource temporarily unavailable

EDIT: Rereading the docs, adding return false (or true) to streamCallbackFn doesn't fix the error.

n8henrie commented 2 years ago

I eventually got open -a to work by removing --args; I'm still using lua to call JS to call a shell script so that I can open the currently selected files in Finder, but it's working and not blocking with this approach.

n8henrie commented 2 years ago

So to summarize, I think the issue is that hs.task seems to unexpectedly block when the spawned task is forked or backgrounded.

nishizhen commented 1 year ago

I have the same issue when i run hs.task.new

-- Start the MQTT client loop hs.task.new("/opt/homebrew/bin/lua", function() mqtt.run_ioloop(client) end):start()