moul/gotty-client

[BUG] Should handle EINTR

eubnara opened this issue · 5 comments

It keeps disconnected when using go1.14.11 or go1.15.4.
I think since golang/go#38033, golang handles EINTR.

To Reproduce
Steps to reproduce the behavior:

  1. Using Golang >= 1.15
  2. It keeps disconnected.

Expected behavior
Keep connected

Versions (please complete the following information, if relevant):

  • Software version: 1.8.0
  • OS: Ubuntu
  • Golang version: 1.15.4

Additional context
Add any other context about the problem here.

I'm suffering a problem.
(And also I failed to run latest version with go get command so I just run go run on source code =>

gotty-client (master) $ pwd
/home/eub/go/src/github.com/moul/gotty-client/cmd/gotty-client
gotty-client (master) $ go run main.go -D http://****

)

I tried to print out the error in the writeLoop.
And I found interrupted system call message.

$ go run main.go -D http://****
DEBU[0000] Fetching auth token auth-token: "http://****/auth_token.js" 
DEBU[0000] Auth-token: "****"                   
DEBU[0000] Connecting to websocket: "ws://****/ws" 
DEBU[0000] Sending arguments and auth-token             
DEBU[0000] Sending ping                                 
DEBU[0000] Unhandled protocol message: json pref: {}    
$ interrupted system call
DEBU[0000] writeLoop suicide                            
DEBU[0000] termsizeLoop died                            
DEBU[0000] readLoop died                                
DEBU[0000] Client.Loop() exiting

I just ignore EINTR and the connection is not disconnected.

gotty-client.go

import (
        "crypto/tls"
        "encoding/base64"
        "encoding/json"
        "fmt"
        "io"
        "io/ioutil"
        "net/http"
        "net/url"
        "os"
        "regexp"
+        "syscall"
        "strings"
        "sync"
        "time"

        "github.com/containerd/console"
        "github.com/creack/goselect"
        "github.com/gorilla/websocket"
        "github.com/sirupsen/logrus"
)

...
...
func (c *Client) writeLoop(wg *sync.WaitGroup) poisonReason {
        defer wg.Done()
        fname := "writeLoop"

        buff := make([]byte, 128)

        rdfs := &goselect.FDSet{}
        reader := io.ReadCloser(os.Stdin)

        pr := NewEscapeProxy(reader, c.EscapeKeys)
        defer reader.Close()

        for {
                select {
                case <-c.poison:
                        /* Somebody poisoned the well; die */
                        return die(fname, c.poison)
                default:
                }

                rdfs.Zero()
                rdfs.Set(reader.(exposeFd).Fd())
                err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
                if err != nil {
-                         return openPoison(fname, c.poison)
+                        fmt.Println(err)
+                        if err != syscall.EINTR {
+                                return openPoison(fname, c.poison)
+                        }
                }

But I don't think it is right solution.

Alternative solution

using RetrySelect() instead of Select?

func (c *Client) writeLoop(wg *sync.WaitGroup) poisonReason {
...
...
-               err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
+               err := goselect.RetrySelect(1, rdfs, nil, nil, 50*time.Millisecond, 3, 50*time.Millisecond)

creack/goselect#3

I think this interrupted system call happen after golang/go#38033.

go1.13.15 => no interrupted system call
go1.14.11 => interrupted system call error found
go1.15.4 => interrupted system call error found

I'll make a PR

moul commented

fixed by #99 (thank you @eubnara)