feat: Implement comprehensive process cleanup for tmux
- Add `Pane.kill_processes` for main and child processes - Include fallback process group cleanup for panes - Implement window-level process cleanup - Integrate session-level process cleanup - Add tmux process cleanup test scripts
This commit is contained in:
@@ -2,6 +2,7 @@ module tmux
|
||||
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import time
|
||||
|
||||
@[heap]
|
||||
@@ -151,12 +152,83 @@ pub fn (mut p Pane) send_keys(keys string) ! {
|
||||
osal.execute_silent(cmd) or { return error('Cannot send keys to pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Kill this specific pane
|
||||
// Kill this specific pane with comprehensive process cleanup
|
||||
pub fn (mut p Pane) kill() ! {
|
||||
// First, kill all processes running in this pane
|
||||
p.kill_processes()!
|
||||
|
||||
// Then kill the tmux pane itself
|
||||
cmd := 'tmux kill-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
|
||||
osal.execute_silent(cmd) or { return error('Cannot kill pane %${p.id}: ${err}') }
|
||||
}
|
||||
|
||||
// Kill all processes associated with this pane (main process and all children)
|
||||
pub fn (mut p Pane) kill_processes() ! {
|
||||
if p.pid == 0 {
|
||||
console.print_debug('Pane %${p.id} has no associated process (pid is 0)')
|
||||
return
|
||||
}
|
||||
|
||||
console.print_debug('Killing all processes for pane %${p.id} (main PID: ${p.pid})')
|
||||
|
||||
// Use the recursive process killer to terminate the main process and all its children
|
||||
osal.process_kill_recursive(pid: p.pid) or {
|
||||
console.print_debug('Failed to kill processes for pane %${p.id}: ${err}')
|
||||
// Continue anyway - the process might already be dead
|
||||
}
|
||||
|
||||
// Also try to kill any processes that might be running in the pane's process group
|
||||
// This handles cases where processes might have detached from the main process tree
|
||||
p.kill_pane_process_group()!
|
||||
}
|
||||
|
||||
// Kill processes in the pane's process group (fallback cleanup)
|
||||
fn (mut p Pane) kill_pane_process_group() ! {
|
||||
// Get all processes and find ones that might be related to this pane
|
||||
_ := osal.processmap_get() or {
|
||||
console.print_debug('Could not get process map for pane cleanup')
|
||||
return
|
||||
}
|
||||
|
||||
// Look for processes that might be children of the pane's shell
|
||||
// or processes running commands that were sent to this pane
|
||||
mut pane_processes := []int{}
|
||||
|
||||
// First, collect the main process and its direct children
|
||||
if p.pid > 0 && osal.process_exists(p.pid) {
|
||||
children_map := osal.processinfo_children(p.pid) or {
|
||||
console.print_debug('Could not get children for PID ${p.pid}')
|
||||
return
|
||||
}
|
||||
|
||||
for child in children_map.processes {
|
||||
pane_processes << child.pid
|
||||
}
|
||||
}
|
||||
|
||||
// Kill any remaining processes with SIGTERM first, then SIGKILL
|
||||
for pid in pane_processes {
|
||||
if osal.process_exists(pid) {
|
||||
// Try SIGTERM first (graceful shutdown)
|
||||
osal.execute_silent('kill -TERM ${pid}') or {
|
||||
console.print_debug('Could not send SIGTERM to PID ${pid}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a moment for graceful shutdown
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Force kill any remaining processes with SIGKILL
|
||||
for pid in pane_processes {
|
||||
if osal.process_exists(pid) {
|
||||
osal.execute_silent('kill -KILL ${pid}') or {
|
||||
console.print_debug('Could not send SIGKILL to PID ${pid}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select/activate this pane
|
||||
pub fn (mut p Pane) select() ! {
|
||||
cmd := 'tmux select-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
|
||||
|
||||
@@ -282,13 +282,17 @@ pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! {
|
||||
for mut w in s.windows {
|
||||
if w.name == args.name {
|
||||
if (args.id > 0 && w.id == args.id) || args.id == 0 {
|
||||
// Enhanced cleanup: kill all processes before stopping the window
|
||||
w.kill_all_processes() or {
|
||||
console.print_debug('Failed to kill processes in window ${w.name}: ${err}')
|
||||
}
|
||||
w.stop()!
|
||||
break
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
s.windows.delete(i) // i is now the one in the list which needs to be removed
|
||||
s.windows.delete(i) // i is now the one in the list which needs to be removed
|
||||
}
|
||||
|
||||
pub fn (mut s Session) restart() ! {
|
||||
@@ -297,11 +301,31 @@ pub fn (mut s Session) restart() ! {
|
||||
}
|
||||
|
||||
pub fn (mut s Session) stop() ! {
|
||||
// First, kill all processes in all windows and panes of this session
|
||||
s.kill_all_processes()!
|
||||
|
||||
// Then kill the tmux session itself
|
||||
osal.execute_silent('tmux kill-session -t ${s.name}') or {
|
||||
return error("Can't delete session ${s.name} - This may happen when session is not found: ${err}")
|
||||
}
|
||||
}
|
||||
|
||||
// Kill all processes in all windows and panes of this session
|
||||
pub fn (mut s Session) kill_all_processes() ! {
|
||||
console.print_debug('Killing all processes in session ${s.name}')
|
||||
|
||||
// Refresh session information to get current state
|
||||
s.scan()!
|
||||
|
||||
// Kill processes in each window
|
||||
for mut window in s.windows {
|
||||
window.kill_all_processes() or {
|
||||
console.print_debug('Failed to kill processes in window ${window.name}: ${err}')
|
||||
// Continue with other windows even if one fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run ttyd for this session so it can be accessed in the browser
|
||||
pub fn (mut s Session) run_ttyd(args TtydArgs) ! {
|
||||
target := '${s.name}'
|
||||
|
||||
@@ -2,8 +2,6 @@ module tmux
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@@ -145,8 +143,12 @@ pub fn (mut w Window) create(cmd_ string) ! {
|
||||
w.id = wid.replace('@', '').int()
|
||||
}
|
||||
|
||||
// stop the window
|
||||
// stop the window with comprehensive process cleanup
|
||||
pub fn (mut w Window) kill() ! {
|
||||
// First, kill all processes in all panes of this window
|
||||
w.kill_all_processes()!
|
||||
|
||||
// Then kill the tmux window itself
|
||||
osal.exec(
|
||||
cmd: 'tmux kill-window -t @${w.id}'
|
||||
stdout: false
|
||||
@@ -156,6 +158,22 @@ pub fn (mut w Window) kill() ! {
|
||||
w.active = false // Window is no longer active
|
||||
}
|
||||
|
||||
// Kill all processes in all panes of this window
|
||||
pub fn (mut w Window) kill_all_processes() ! {
|
||||
console.print_debug('Killing all processes in window ${w.name} (ID: ${w.id})')
|
||||
|
||||
// Refresh pane information to get current state
|
||||
w.scan()!
|
||||
|
||||
// Kill processes in each pane
|
||||
for mut pane in w.panes {
|
||||
pane.kill_processes() or {
|
||||
console.print_debug('Failed to kill processes in pane %${pane.id}: ${err}')
|
||||
// Continue with other panes even if one fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (window Window) str() string {
|
||||
mut out := ' - name:${window.name} wid:${window.id} active:${window.active}'
|
||||
for pane in window.panes {
|
||||
|
||||
Reference in New Issue
Block a user