Skip to content

fix(system): redo signal forwarding for processes#125

Merged
water-sucks merged 2 commits intonix-community:mainfrom
water-sucks:fix-signals-fr
Nov 5, 2025
Merged

fix(system): redo signal forwarding for processes#125
water-sucks merged 2 commits intonix-community:mainfrom
water-sucks:fix-signals-fr

Conversation

@water-sucks
Copy link
Copy Markdown
Collaborator

@water-sucks water-sucks commented Nov 5, 2025

Description

Signal forwarding was not occurring for spawned processes (i.e. switch-to-configuration). This was because of two bugs:

  1. Local signal forwarding was not implemented at all in the LocalSystem abstraction.
  2. SSH signal forwarding was done incorrectly, by stringifying an os.Signal type directly to its debug representation rather than using the SSH package constants that don't have the prefixed SIG.

This PR fixes both those issues.

Summary by CodeRabbit

  • Bug Fixes
    • Improved subprocess interruption handling so external interrupts are reliably forwarded to child processes and lifecycle is better managed.
    • Enhanced SSH-side signal translation and forwarding for more consistent remote command interruption.
    • More reliable reporting of process termination status and added logging for signal-forwarding failures.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 5, 2025

Walkthrough

Refactors signal forwarding for subprocesses: local.go now uses Start()/Wait() and a goroutine to forward SIGINT/SIGTERM to the child; ssh.go replaces done-channel sync with a for-range over the signal channel and adds osSignalToSSHSignal() to map OS signals to SSH signal names.

Changes

Cohort / File(s) Summary
Local process lifecycle & signal forwarding
internal/system/local.go
Replaced Cmd.Run() with Cmd.Start() + Cmd.Wait(); added goroutine to forward SIGINT and SIGTERM to child process; installs and cleans up OS signal handler; returns child's exit status when available.
SSH session signal routing & mapper
internal/system/ssh.go
Removed done channel and select loop; uses for range over sigCh to forward signals to SSH session with logging on failures; added osSignalToSSHSignal() helper mapping common OS signals to SSH signal strings (returns empty string for unknown signals).

Sequence Diagram

sequenceDiagram
    participant Main
    participant SigHandler as Signal Handler (goroutine)
    participant Child as Child Process / SSH Session

    Main->>Child: Start() / Start SSH session
    Main->>SigHandler: spawn signal-forwarding goroutine
    Note over Main,SigHandler: SigHandler listens on OS signal channel
    SigHandler->>SigHandler: receive OS signal
    alt local process
        SigHandler->>Child: send syscall signal (SIGINT/SIGTERM)
    else ssh session
        SigHandler->>SigHandler: map via osSignalToSSHSignal()
        SigHandler->>Child: send SSH signal string (session.Signal)
    end
    Child->>Main: Exit status / session end
    Main->>SigHandler: close signal channel (stop forwarding)
    SigHandler->>SigHandler: loop terminates
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify correct Start()/Wait() error handling and process reaping in local.go.
  • Confirm signal installation/cleanup avoids races and double-handling.
  • Validate osSignalToSSHSignal() mappings and behavior for unknown signals.
  • Check for potential channel closure or goroutine leaks in ssh.go.

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing proper signal forwarding for spawned processes by fixing both local and SSH signal forwarding mechanisms.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2166b2f and 066b98f.

📒 Files selected for processing (2)
  • internal/system/local.go (2 hunks)
  • internal/system/ssh.go (2 hunks)
🔇 Additional comments (4)
internal/system/local.go (2)

8-8: LGTM: Necessary imports for signal handling.

The addition of os/signal and syscall packages enables the signal forwarding functionality implemented below.

Also applies to: 11-11


42-63: Excellent implementation of signal forwarding for local processes.

The Start()/Wait() pattern with signal forwarding correctly addresses the bug described in the PR:

  • Signal channel is properly set up with cleanup deferred
  • Goroutine forwards SIGINT and SIGTERM to the child process
  • Nil check prevents errors if the process hasn't started
  • Channel closure via defer ensures the goroutine terminates cleanly

To confirm signal forwarding works as expected, you can test with a long-running process:

internal/system/ssh.go (2)

336-341: Cleaner signal forwarding with correct SSH signal names.

The range-based loop simplifies the signal forwarding logic, and the new osSignalToSSHSignal() mapping ensures that SSH signal names are correctly formatted (without the "SIG" prefix). This fixes the bug where debug strings were previously used.


356-387: Well-implemented signal mapping function.

The osSignalToSSHSignal() function correctly maps OS signals to SSH signal names by stripping the "SIG" prefix. All 13 mappings are accurate.

While only SIGINT and SIGTERM are currently registered (line 329), the comprehensive mapping provides good extensibility for future enhancements.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
internal/system/local.go (2)

42-61: Signal forwarding implementation looks correct.

The approach of using Start() followed by Wait() with a signal-forwarding goroutine properly addresses the missing local signal forwarding. The cleanup via defer ensures the signal handler is unregistered and the goroutine exits cleanly.

One minor refinement: registering the signal handler before calling Start() would eliminate a small race window where signals arriving between Start() and Notify() might not be forwarded. The existing nil-check on command.Process already handles this safely.

Optional: Eliminate signal race

Move the signal setup before Start():

+	sigs := make(chan os.Signal, 1)
+	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+	defer func() {
+		signal.Stop(sigs)
+		close(sigs)
+	}()
+
 	if err := command.Start(); err != nil {
 		return 0, err
 	}

-	sigs := make(chan os.Signal, 1)
-	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
-	defer func() {
-		signal.Stop(sigs)
-		close(sigs)
-	}()
-
 	go func() {
 		for sig := range sigs {
 			if command.Process != nil {
 				_ = command.Process.Signal(sig)
 			}
 		}
 	}()

56-56: Consider logging signal forwarding errors.

While ignoring the error from Process.Signal() is reasonable for signal forwarding, logging failures would aid debugging if signal delivery issues arise.

-				_ = command.Process.Signal(sig)
+				if err := command.Process.Signal(sig); err != nil {
+					l.logger.Debugf("failed to forward signal %v: %v", sig, err)
+				}
internal/system/ssh.go (1)

353-384: Signal mapping correctly implements SSH protocol requirements.

The osSignalToSSHSignal() function properly maps OS signals to SSH signal names (without the "SIG" prefix), fixing the bug described in the PR. The mapped signals align with RFC 4254 section 6.10.

The default case returning an empty string for unmapped signals is reasonable since not all OS signals have SSH protocol equivalents. However, logging unmapped signals would help identify unexpected signal types during debugging.

 	default:
+		// Log unmapped signals for debugging
+		// Most signals (SIGSTOP, SIGCONT, SIGCHLD, etc.) cannot be forwarded via SSH
 		return ""
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 844429f and 2166b2f.

📒 Files selected for processing (2)
  • internal/system/local.go (2 hunks)
  • internal/system/ssh.go (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build/Test
🔇 Additional comments (1)
internal/system/ssh.go (1)

336-339: Simplified signal forwarding is cleaner.

The direct for sig := range sigCh loop is more straightforward than the previous approach and correctly terminates when the channel is closed by the defer on line 332.

@water-sucks water-sucks merged commit ee29017 into nix-community:main Nov 5, 2025
2 checks passed
@water-sucks water-sucks deleted the fix-signals-fr branch November 5, 2025 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant