Skip to content

Commit 3ee646e

Browse files
fix: Fixing opencode server process cleanup for static port
1 parent 5d52e40 commit 3ee646e

1 file changed

Lines changed: 23 additions & 26 deletions

File tree

lua/opencode/port_mapping.lua

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ end
5959

6060
--- Fire-and-forget graceful shutdown request to a server with no clients.
6161
--- 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.
6363
--- @param port number
6464
--- @param server_pid number|nil
6565
local function kill_orphaned_server(port, server_pid)
@@ -88,33 +88,24 @@ local function kill_orphaned_server(port, server_pid)
8888
})
8989
end)
9090

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)
9292
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
10699
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)
118109
else
119110
log.debug('port_mapping: no server PID available, relying on graceful shutdown only')
120111
end
@@ -292,6 +283,12 @@ function M.unregister(port, server)
292283
if server then
293284
if server.mode == 'attach' or should_shutdown then
294285
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
295292
server:shutdown()
296293
end
297294
elseif should_shutdown then

0 commit comments

Comments
 (0)