chzyer / readline

Readline is a pure go(golang) implementation for GNU-Readline kind library
MIT License
2.08k stars 275 forks source link

`(*Instance).Close()` hangs if there is a concurrent `(*Instance).ReadLine` call #217

Open slingamn opened 1 year ago

slingamn commented 1 year ago

I'm using the following test case with readline v1.5.1, go version go1.20 linux/amd64:

package main

import (
    "fmt"
    "log"
    "os"
    "strings"
    "time"

    "github.com/chzyer/readline"
)

func main() {
    log.SetFlags(log.LstdFlags | log.Lmicroseconds)

    rl, err := readline.New("> ")
    if err != nil {
        log.Fatal(err)
    }
    defer rl.Close()
    log.SetOutput(rl.Stderr())

    input := make(chan string)

    go func() {
        closed := false
        for {
            line, err := rl.Readline()
            if err != nil {
                fmt.Fprintln(os.Stderr, "error: failed to read new input line:", err.Error())
                return
            }
            if !closed {
                input <- line
            }
            if !closed && strings.ToLower(strings.TrimSpace(line)) == "quit" {
                log.Printf("quit received\n")
                closed = true
                close(input)
            }
        }
    }()

    for {
        line, ok := <- input
        if !ok {
            log.Printf("quit acknowledged, sleeping")
            time.Sleep(time.Second)
            log.Printf("done sleeping, exiting")
            return
        }

        fmt.Fprintf(rl, "received %s\n", strings.TrimRight(line, "\r\n"))
    }
}

If I enter quit at the prompt and wait, I get the following output:

> quit
received quit
2023/02/06 01:19:18.052671 quit received
> 2023/02/06 01:19:18.053079 quit acknowledged, sleeping
2023/02/06 01:19:19.053285 done sleeping, exiting

but then the program hangs without exiting. SIGQUIT shows that the main goroutine is blocked in (*Instance).Close():

https://gist.github.com/slingamn/eb5bea5e623e848cd0903c29b155a0b3

It seems that this Wait() call:

https://github.com/chzyer/readline/blob/7f93d88cd5ffa0e805d58d2f9fc3191be15ec668/terminal.go#L228

is blocked by the failure of this select to terminate:

https://github.com/chzyer/readline/blob/7f93d88cd5ffa0e805d58d2f9fc3191be15ec668/std.go#L120-L125

My diagnosis is that FillableStdin fails to pass the Close() call through to CancelableStdin, which would stop the select. This patch fixes the issue, but may introduce other issues:

https://github.com/slingamn/readline/commit/4c5bb20c9ea7da126b421c8394fbd4cb5d4d59df

Thanks very much for your time.