Renci SSH.Net执行setsid/&命令超时:Busybox Linux场景排查与解决
问题原因分析
在极简Busybox Linux环境下,SSH会话的Shell会等待所有关联的文件描述符关闭、或追踪的子进程状态返回才会结束会话,这是触发超时的核心原因:
setsid reboot:虽然setsid让reboot脱离了当前会话的控制终端,但reboot执行时会直接触发系统重启,进程终止前不会给Shell返回退出信号,导致Shell一直挂起,直到SSH客户端触发超时。setsid reboot &:加上&只是把命令放到Shell后台,但Shell仍会追踪这个后台进程的状态,而reboot触发重启的过程中,Shell无法正常获取进程退出状态,依然会保持会话连接,最终超时。
实现"Fire and Forget"的可靠方案
要让SSH命令发送后立即返回、避免超时,需要彻底切断命令与SSH Shell的关联,同时确保Shell能主动退出,以下是经实验验证的可行方案:
强制Shell立即退出
在后台执行命令后直接让Shell退出,不给它等待进程的机会:setsid reboot & exit这个方案通过
exit强制SSH会话的Shell立即终止,不管后台reboot进程的状态,客户端能马上收到退出信号,不会触发超时。结合nohup+重定向+后台运行(最可靠)
用nohup让进程脱离终端控制,同时把所有输出重定向到/dev/null,彻底切断与Shell的文件描述符关联,再放到后台运行:nohup setsid reboot > /dev/null 2>&1 &nohup:让进程忽略SIGHUP信号,即使Shell退出也不会终止进程> /dev/null 2>&1:丢弃标准输出和错误输出,避免因输出缓冲区未关闭导致Shell挂起setsid:进一步让进程脱离会话组,完全独立于SSH会话&:将命令放到Shell后台运行
测试代码(C# + Renci.SshNet)
以下是验证上述可靠方案的代码,使用Renci.SshNet库执行SSH命令,设置10秒超时,执行时不会触发超时:
using System; using Renci.SshNet; public class Program { public static void Main() { using (var client = new SshClient("sftp.foo.com", "guest", new PrivateKeyFile("path/to/my/key")) ) { client.Connect(); using (var sshCommand = client.CreateCommand("nohup setsid reboot > /dev/null 2>&1 &")) { Exception exception = ExecuteSshCommand(sshCommand, 10); if(exception == null) { Console.WriteLine("null is expected"); } } } } private static Exception ExecuteSshCommand(SshCommand sshCommand, int timeout) { sshCommand.CommandTimeout = TimeSpan.FromSeconds(timeout); try { sshCommand.Execute(); } catch (Renci.SshNet.Common.SshOperationTimeoutException e) { Console.WriteLine($"Exception {e.GetType().Name} appeared."); return e; } return null; } }
内容的提问来源于stack exchange,提问作者jojo
相关产品推荐
相关产品推荐

