I need to terminate an existing console input request when a new one is requested. The following code is an attempt to close an existing request using a channel but it does not seem to terminate the input request.
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"time"
)
func main() {
go Confirm("you are a programmer, aint you?")
time.Sleep(2 * time.Second)
Confirm("do you love go?")
}
var cancelChannel chan struct{}
func Confirm(s string) bool {
//check if channel type holds a value then close the channel to remove previous confirmation input
if cancelChannel != nil {
fmt.Println("channel to be closed")
close(cancelChannel)
}
cancelChannel = make(chan struct{})
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s [y/n]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
if _, ok := <-cancelChannel; !ok {
fmt.Println("channel closed")
return false
}
}
}
CodePudding user response:
Go on and explore the simplicity offer by go https://pkg.go.dev/context#WithCancel
You can have a context
that returning CancelFunc
then you use context.WithCancel
.
And execute cancel
func if you want to terminate.
This is the good practice way, you can also do a dirty os.Exit(0)
in another case.
CodePudding user response:
As @JimB mentioned in comment you can't interrupt read on stdin although there is kinda shady trick how you can achieve it. It's possible to duplicate os.Stdin
file descriptor using syscall
(not recommended) and open it as non blocking file.
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"syscall"
"time"
)
func setNonblock(f *os.File) error {
c, err := f.SyscallConn()
if err != nil {
return err
}
var err2 error
err = c.Control(func(fd uintptr) {
err2 = syscall.SetNonblock(int(fd), true)
})
if err != nil {
return err
}
return err2
}
func nonBlockingFile(f *os.File) (*os.File, error) {
if err := setNonblock(f); err != nil {
return nil, err
}
fd, err := syscall.Dup(int(f.Fd()))
if err != nil {
return nil, err
}
f2 := os.NewFile(uintptr(fd), f.Name())
return f2, nil
}
func read(ctx context.Context, f *os.File) (io.Reader, error) {
r, err := nonBlockingFile(f)
if err != nil {
return nil, err
}
go func() {
defer r.Close()
<-ctx.Done()
}()
buff := bytes.NewBuffer([]byte{})
for {
_, err := io.Copy(buff, r)
if err != nil {
if errors.Is(err, fs.ErrClosed) {
break
}
panic(err)
}
}
return buff, nil
}
func main() {
ctx1, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second * 2)
cancel()
}()
buf1, err := read(ctx1, os.Stdin)
if err != nil {
panic(err)
}
ctx2, _ := context.WithTimeout(context.Background(), time.Second*2)
buf2, err := read(ctx2, os.Stdin)
fmt.Println("buf1")
fmt.Println(ioutil.ReadAll(buf1))
fmt.Println("buf2")
fmt.Println(ioutil.ReadAll(buf2))
}