luvit / luv

Bare libuv bindings for lua
Apache License 2.0
823 stars 185 forks source link

Question: Is `shutdown` and `close` (use new_pipe with spawn) sync or async? #676

Closed linrongbin16 closed 1 year ago

linrongbin16 commented 1 year ago

Hi,

I am writing a Neovim plugin with luv, spawn a command line and wait for all the output, then close the stdout/stderr pipe, here's the close code:

local function on_exit()
  out_pipe:shutdown()
  err_pipe:shutdown()
  out_pipe:close()
  err_pipe:close()
  vim.loop.close() -- `vim.loop` is `uv`
end

This is because I read the docs - L28, there's a sample code:

local uv = require("luv") -- "luv" when stand-alone, "uv" in luvi apps

local server = uv.new_tcp()
server:bind("127.0.0.1", 1337)
server:listen(128, function (err)
  assert(not err, err)
  local client = uv.new_tcp()
  server:accept(client)
  client:read_start(function (err, chunk)
    assert(not err, err)
    if chunk then
      client:write(chunk)
    else
      client:shutdown()
      client:close()
    end
  end)
end)
print("TCP server listening at 127.0.0.1 port 1337")
uv.run() -- an explicit run call is necessary outside of luvit

It looks like these 2 APIs are sync, e.g., I first call shutdown, then close.

Somehow when I read the API signature of shutdown and close, I'm thinking I should write it like this:

local function on_exit()
  out_pipe:shutdown(function() 
    out_pipe:close(function()
      if out_pipe:is_closing() and err_pipe:is_closing() then
        vim.loop.stop()
      end
    end)
  end)
  err_pipe:shutdown(function() 
    err_pipe:close(function()
      if out_pipe:is_closing() and err_pipe:is_closing() then
        vim.loop.stop()
      end
    end)
  end)
end

In my practicing, both of them seems working correctly, (maybe it's related to how I consumed the output of the command line) there's no data loss from command line in the 1st way.

But would you please educate me, which one should I use? or is there a best-practice when I use spawn?

linrongbin16 commented 1 year ago

I think I find the correct way to use spawn API in Neovim, here's a small sample:


local cmds = {'ls', '-lha', '~'}
local process_handler, process_id

local function on_exit(code, signal)
  if process_handler and not process_handler:is_closing() then
    process_handler:close(function(err)
      vim.uv.stop() -- stop uv loop after close spawn process handler
    end)
  end
end

local function on_stdout(err, data)
  -- process output here
end

local function on_stderr(err, data)
  if err then
    io.write(string.format("err:%s, data:%s\n", vim.inspect(err), vim.inspect(data)))
    on_exit(1)
  end
end

local out_pipe = vim.uv.new_pipe(false)
local err_pipe = vim.uv.new_pipe(false)

process_handler, process_id = vim.uv.spawn(cmds[1], {
  args = vim.list_slice(cmds, 2),
  stdio = { nil, out_pipe, err_pipe },
}, function(code, signal)
  out_pipe:read_stop()
  err_pipe:read_stop()
  out_pipe:shutdown()
  err_pipe:shutdown()
  on_exit(code)
end)

out_pipe:read_start(on_stdout)
err_pipe:read_start(on_stderr)

vim.uv.run() -- start uv loop here, it will block the main thread until the command ends