【问题标题】:Force to close SSH client session强制关闭 SSH 客户端会话
【发布时间】:2020-02-15 22:37:28
【问题描述】:

我已经编写了一个 SSH 客户端来连接到网络设备,一旦运行命令超过 25 秒,我就会通过“选择”设置超时。我注意到一些设备有另一个 IOS,一旦触发超时,它就无法通过 Close() 方法丢弃与它们的 SSH 会话,这会导致 goroutinge 泄漏。我需要跟上客户端并断开会话以准备下一个命令。看起来 goroutine 在那个时候不会永远终止!你们有什么想法吗?

    go func() {
       r <- s.Run(cmd)
    }()

    select {
       case err := <-r:
         return err
       case <-time.After(time.Duration(timeout) * time.Second):
         s.Close()
         return fmt.Errorf("timeout after %d seconds", timeout)
    }

通过堆分析,我看到了以下内容: 2.77GB 99.44% 99.44% 2.77GB 99.44% bytes.makeSlice

     0     0% 99.44%     2.77GB 99.44%  bytes.(*Buffer).ReadFrom

     0     0% 99.44%     2.77GB 99.44%  golang.org/x/crypto/ssh.(*Session).start.func1

     0     0% 99.44%     2.77GB 99.44%  golang.org/x/crypto/ssh.(*Session).stdout.func1

     0     0% 99.44%     2.77GB 99.44%  io.Copy

     0     0% 99.44%     2.77GB 99.44%  io.copyBuffer

     0     0% 99.44%     2.78GB 99.93%  runtime.goexit

ROUTINE ======================== runtime.goexit in /usr/local/go/src/runtime/asm_amd64.s

     0     2.78GB (flat, cum) 99.93% of Total

     .          .   1993:   RET

     .          .   1994:

     .          .   1995:// The top-most function running on a goroutine

     .          .   1996:// returns to goexit+PCQuantum.

     .          .   1997:TEXT runtime·goexit(SB),NOSPLIT,$0-0

     .     2.78GB   1998:   BYTE    $0x90   // NOP

     .          .   1999:   CALL    runtime·goexit1(SB) // does not return

     .          .   2000:   // traceback from goexit1 must hit code range of goexit

     .          .   2001:   BYTE    $0x90   // NOP

     .          .   2002:


     .          .   2003:TEXT runtime·prefetcht0(SB),NOSPLIT,$0-8

【问题讨论】:

  • r 通道是否被缓冲?当您超时时,没有接收者可以接受来自s.Run 的结果。您可能仍然需要实际终止远程进程;您是否尝试过在超时时使用s.Signal 杀死它?
  • 再次,r 是否已缓冲?
  • 需要缓冲,否则goroutine无法返回。
  • 不要关闭会话以中止,杀死进程并等待它(运行正在调用等待你)
  • 我相信 s.Run 触发的进程没有正确停止,因此它无法释放资源。您是否尝试将 sigterm/sig kill 发送到该进程? godoc.org/golang.org/x/crypto/ssh#Session.Signal 而不是运行,而是使用开始并等待 stackoverflow.com/questions/11886531/…

标签: go ssh


【解决方案1】:

Channel r 阻止 Go 例程返回,因为它没有被清空。我已经编写了您的代码的改编版本并插入了一个等待组来演示该问题:

func main() {
    var wg sync.WaitGroup // This is only added for demonstration purposes
    s := new(clientSession)

    r := make(chan error)

    go func(s *clientSession) {
        wg.Add(1)
        r <- s.Run()
        wg.Done() // Will only be called after s.Run() is able to return
    }(s)

    fmt.Println("Client has been opened")

    select {
    case err := <-r:
        fmt.Println(err)
    case <-time.After(1 * time.Second):
        s.Close()
        fmt.Println("Timed out, closing")
    }

    wg.Wait() // Waits until wg.Done() is called.

    fmt.Println("Main finished successfully")
}

Go playground 似乎终止了程序,所以我创建了一个带有完整可运行代码的gist。当我们运行incorrect.go:

$ go run incorrect.go
Client has been opened
Timed out, closing
fatal error: all goroutines are asleep - deadlock!
....

那是因为我们的代码在wg.Wait() 行上死锁了。这表明 Go 例程中的 wg.Done() 从未到达。

正如 cmets 所指出的,缓冲通道可以提供帮助。但前提是你不再关心错误,在调用s.Close()

r := make(chan error, 1)

buffered.go运行正确,但错误丢失:

$ go run buffered.go
Client has been opened
Timed out, closing
Main finished successfully

另一种选择是准确地排空通道 1 次:

select {
    case err := <-r:
        fmt.Println(err)
    case <-time.After(1 * time.Second):
        s.Close()
        fmt.Println("Timed out, closing")
        fmt.Println(<-r)
    }

或者通过将select 包装在for 循环中(没有缓冲通道):

X:
    for {
        select {
        case err := <-r:
            fmt.Println(err)
            break X // because we are in main(). Normally `return err`
        case <-time.After(1 * time.Second):
            s.Close()
            fmt.Println("Timed out, closing")
        }
    }

当我们运行 drain.go 时,我们看到错误也打印出来了:

$ go run incorrect.go
Client has been opened
Timed out, closing
Run() closed
Main finished successfully

在现实世界中,会运行多个 Go 例程。因此,您需要在 for 循环中使用一些计数器或进一步利用等待组功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 2020-09-30
    • 1970-01-01
    相关资源
    最近更新 更多