|
59 | 59 |
|
60 | 60 | --- Fire-and-forget graceful shutdown request to a server with no clients. |
61 | 61 | --- Also force-kills the process if server_pid is available. |
62 | | ---- Uses async operations to avoid blocking Neovim shutdown. |
| 62 | +--- Uses direct process signals via vim.uv.kill which works cross-platform. |
63 | 63 | --- @param port number |
64 | 64 | --- @param server_pid number|nil |
65 | 65 | local function kill_orphaned_server(port, server_pid) |
@@ -88,33 +88,24 @@ local function kill_orphaned_server(port, server_pid) |
88 | 88 | }) |
89 | 89 | end) |
90 | 90 |
|
91 | | - -- If we have a server PID, force kill it using async timers to avoid blocking |
| 91 | + -- If we have a server PID, kill it directly using vim.uv.kill (cross-platform) |
92 | 92 | if server_pid then |
93 | | - -- Schedule async kill sequence without blocking |
94 | | - vim.defer_fn(function() |
95 | | - -- Check for and kill child processes first |
96 | | - local ok, children = pcall(vim.api.nvim_get_proc_children, server_pid) |
97 | | - if ok and children and #children > 0 then |
98 | | - log.debug('port_mapping: server pid=%d has %d children, killing them', server_pid, #children) |
99 | | - for _, child_pid in ipairs(children) do |
100 | | - pcall(vim.uv.kill, child_pid, 15) -- SIGTERM |
101 | | - -- Schedule SIGKILL for stubborn children |
102 | | - vim.defer_fn(function() |
103 | | - pcall(vim.uv.kill, child_pid, 9) |
104 | | - end, 100) |
105 | | - end |
| 93 | + -- Kill child processes first |
| 94 | + local ok, children = pcall(vim.api.nvim_get_proc_children, server_pid) |
| 95 | + if ok and children and #children > 0 then |
| 96 | + log.debug('port_mapping: server pid=%d has %d children, killing them', server_pid, #children) |
| 97 | + for _, child_pid in ipairs(children) do |
| 98 | + pcall(vim.uv.kill, child_pid, 9) -- SIGKILL children immediately |
106 | 99 | end |
107 | | - |
108 | | - -- Kill the main server process |
109 | | - local ok, err = pcall(vim.uv.kill, server_pid, 15) -- SIGTERM |
110 | | - log.debug('port_mapping: SIGTERM server pid=%d ok=%s err=%s', server_pid, tostring(ok), tostring(err)) |
111 | | - |
112 | | - -- Schedule SIGKILL for stubborn processes |
113 | | - vim.defer_fn(function() |
114 | | - local ok, err = pcall(vim.uv.kill, server_pid, 9) -- SIGKILL |
115 | | - log.debug('port_mapping: SIGKILL server pid=%d ok=%s err=%s', server_pid, tostring(ok), tostring(err)) |
116 | | - end, 100) |
117 | | - end, 500) |
| 100 | + end |
| 101 | + |
| 102 | + -- Kill the main server process with SIGTERM first, then SIGKILL |
| 103 | + pcall(vim.uv.kill, server_pid, 15) -- SIGTERM |
| 104 | + log.debug('port_mapping: sent SIGTERM to server pid=%d', server_pid) |
| 105 | + |
| 106 | + -- SIGKILL as backup - can't use vim.defer_fn during shutdown, so just send it immediately |
| 107 | + pcall(vim.uv.kill, server_pid, 9) -- SIGKILL |
| 108 | + log.debug('port_mapping: sent SIGKILL to server pid=%d', server_pid) |
118 | 109 | else |
119 | 110 | log.debug('port_mapping: no server PID available, relying on graceful shutdown only') |
120 | 111 | end |
@@ -292,6 +283,12 @@ function M.unregister(port, server) |
292 | 283 | if server then |
293 | 284 | if server.mode == 'attach' or should_shutdown then |
294 | 285 | log.debug('port_mapping.unregister: shutting down server for port %d', port) |
| 286 | + -- Pass the server_pid to shutdown so it can kill the process even for custom servers |
| 287 | + if should_shutdown and mapping.server_pid and not server.job then |
| 288 | + -- Custom server (no job) but we have tracked PID - kill it directly |
| 289 | + log.debug('port_mapping.unregister: custom server with tracked PID, killing orphaned server') |
| 290 | + kill_orphaned_server(port, mapping.server_pid) |
| 291 | + end |
295 | 292 | server:shutdown() |
296 | 293 | end |
297 | 294 | elseif should_shutdown then |
|
0 commit comments