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:
Mahmoud-Emad
2025-08-31 19:26:45 +03:00
parent b957394d2a
commit 97760cfe87
4 changed files with 168 additions and 5 deletions

View File

@@ -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}'

View File

@@ -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}'

View File

@@ -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 {