Unexpected termination leaves zombie threads and open file handles in towergit
0 reputation · 22 Nov 2023, 10:07 UTC
0 reputation · 22 Nov 2023, 10:07 UTC
When a user interrupts a long-running git operation in towergit via Ctrl-C, the process terminates but background threads and file handles remain open, creating resource leaks that persist beyond the terminated process. This behavior was observed during manual testing of push operations against slow repositories.
The CLI lacks dedicated cancellation or timeout flags. JGit throws SocketTimeoutException on network delays, but towergit only surfaces a generic timeout message without canceling the underlying process. The public documentation marks cancellation as a future enhancement with no current configuration properties or API endpoints to enable timeout settings or signal handling.
Run towergit --help to confirm absence of --timeout or --cancel flags in the usage output. Execute a long push against a slow repository, press Ctrl-C, and verify that the process terminates promptly without leaving zombie threads. Inspect the towergit source or compiled binary for handling of SIGINT or CancellationToken; absence indicates the cancellation logic is incomplete.
35775 reputation · 22 Nov 2023, 15:11 UTC
Towergit terminates abruptly on Ctrl-C without releasing background threads or file handles. The CLI currently exposes no --timeout or --cancel flags, and signal handling for graceful shutdown appears incomplete in the underlying JGit layer.
towergit --help 2>&1 | grep -E -- '--(timeout|cancel)'. Absence confirms no native cancellation support.ps aux | grep '[t]owergit'. Zombie processes indicate incomplete cleanup.lsof -p $(pgrep -f towergit) | wc -l. Compare count after termination to detect handle leaks.For long-running operations, avoid Ctrl-C interruption. Instead:
git directly with --config settings to increase http.postBuffer or core.compression for network resilience.timeout --signal=KILL 300 towergit push. The KILL signal forces immediate termination but may still leak resources.Without source code access, signal handler presence cannot be confirmed. The JGit SocketTimeoutException may propagate but not trigger process-level cancellation. OS-level handle cleanup often occurs during process group termination, masking some leaks at the system level while leaving zombie threads. Further inspection of the compiled binary via strings towergit | grep -i signal could clarify signal handling support.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 22 Nov 2023, 21:39 UTC
The core issue is that JVM shutdown hooks only run after all non-daemon threads finish. JGit's network I/O runs on threads that don't periodically check Thread.interrupted(), so a SIGINT (Ctrl-C) sets the interrupt flag but the thread stays blocked in a socket read. The shutdown hook never executes because the JVM waits for that thread, leaving file descriptors and temporary pack files open.
Verification: after Ctrl-C, run jcmd <pid> Thread.print and look for threads in RUNNABLE state inside SocketInputStream.socketRead0 — those are the stuck I/O threads blocking shutdown. If towergit source is available, search for addShutdownHook and Thread.interrupted() in network loops; absence confirms the cleanup gap.
Workaround: use timeout --signal=TERM 60s towergit push ... (SIGTERM, not SIGKILL) and ensure the towergit process doesn't daemonize. This gives the JVM a chance to run hooks if the socket read eventually times out at the OS TCP layer (often minutes).