diff --git a/nvim/child_process_stream.lua b/nvim/child_process_stream.lua index 5125a0e..3d1fc1d 100644 --- a/nvim/child_process_stream.lua +++ b/nvim/child_process_stream.lua @@ -19,7 +19,9 @@ function ChildProcessStream.spawn(argv, env) stdio = {self._child_stdin, self._child_stdout, 2}, args = args, env = env, - }, function() + }, function(code, signal) + self.exitcode = code + self.exitsignal = signal self:close() end) @@ -59,8 +61,11 @@ function ChildProcessStream:close(signal) if type(signal) == 'string' then self._proc:kill('sig'..signal) end - self._proc:close() - uv.run('nowait') + + while not self.exitcode do + uv.run('once') + end + assert(self.exitcode) native.pid_wait(self._pid) end diff --git a/nvim/session.lua b/nvim/session.lua index 25574dc..ff4c5f5 100644 --- a/nvim/session.lua +++ b/nvim/session.lua @@ -135,6 +135,12 @@ function Session:close(signal) if not self._timer:is_closing() then self._timer:close() end if not self._prepare:is_closing() then self._prepare:close() end self._msgpack_rpc_stream:close(signal) + + if not self.child_exit then + uv.run('nowait') -- run the loop to get exitcode from child process. + self.child_exit = self._msgpack_rpc_stream._stream.exitcode + self.child_signal = self._msgpack_rpc_stream._stream.exitsignal + end end function Session:_yielding_request(method, args) @@ -175,7 +181,12 @@ function Session:_run(request_cb, notification_cb, timeout) self._prepare:stop() end) end - self._msgpack_rpc_stream:read_start(request_cb, notification_cb, uv.stop) + self._msgpack_rpc_stream:read_start(request_cb, notification_cb, function() + uv.run() -- run the loop to get exitcode from child process. + self.child_exit = self._msgpack_rpc_stream._stream.exitcode + self.child_signal = self._msgpack_rpc_stream._stream.exitsignal + uv.stop() + end) uv.run() self._prepare:stop() self._timer:stop() diff --git a/test/session_spec.lua b/test/session_spec.lua index 73fbfef..6c51ab1 100644 --- a/test/session_spec.lua +++ b/test/session_spec.lua @@ -153,6 +153,8 @@ local function test_session(description, session_factory, session_destroy) session_destroy() else session:close() + assert.are.equal(0, session.child_exit) + assert.are.equal(0, session.child_signal) end closed = true end) @@ -179,6 +181,8 @@ test_session(string.format("Session using SocketStream [%s]", socket_file), func return socket_session end, function () child_session:close() + assert.are.equal(0, child_session.child_exit) + assert.are.equal(0, child_session.child_signal) socket_session:close() -- clean up leftovers if something goes wrong local fd = io.open(socket_file) @@ -218,6 +222,8 @@ test_session("Session using TcpStream", function () return tcp_session end, function () child_session:close() + assert.are.equal(0, child_session.child_exit) + assert.are.equal(0, child_session.child_signal) tcp_session:close() end) @@ -257,3 +263,11 @@ describe('stdio', function() assert.are.same({'notification', 'd', {6, 7}}, session:next_message()) end) end) + +it('closing session does not hang with active loop', function() + local cmd = {nvim_prog, '-u', 'NONE', '--embed', '--headless'} + local session1 = Session.new(ChildProcessStream.spawn(cmd)) + local session2 = Session.new(ChildProcessStream.spawn(cmd)) + session1:close() + session2:close() +end)