forked from toolshed/abra
15
vendor/github.com/muesli/cancelreader/.gitignore
generated
vendored
Normal file
15
vendor/github.com/muesli/cancelreader/.gitignore
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
47
vendor/github.com/muesli/cancelreader/.golangci-soft.yml
generated
vendored
Normal file
47
vendor/github.com/muesli/cancelreader/.golangci-soft.yml
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
run:
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0005
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# - dupl
|
||||
- exhaustive
|
||||
# - exhaustivestruct
|
||||
- goconst
|
||||
- godot
|
||||
- godox
|
||||
- gomnd
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- ifshort
|
||||
# - lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- wrapcheck
|
||||
|
||||
# disable default linters, they are already enabled in .golangci.yml
|
||||
disable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
29
vendor/github.com/muesli/cancelreader/.golangci.yml
generated
vendored
Normal file
29
vendor/github.com/muesli/cancelreader/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
run:
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0005
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- exportloopref
|
||||
- goimports
|
||||
- gosec
|
||||
- nilerr
|
||||
- predeclared
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
21
vendor/github.com/muesli/cancelreader/LICENSE
generated
vendored
Normal file
21
vendor/github.com/muesli/cancelreader/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Erik Geiser and Christian Muehlhaeuser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
64
vendor/github.com/muesli/cancelreader/README.md
generated
vendored
Normal file
64
vendor/github.com/muesli/cancelreader/README.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# CancelReader
|
||||
|
||||
[](https://github.com/muesli/cancelreader/releases)
|
||||
[](https://pkg.go.dev/github.com/muesli/cancelreader)
|
||||
[](/LICENSE)
|
||||
[](https://github.com/muesli/cancelreader/actions)
|
||||
[](https://goreportcard.com/report/muesli/cancelreader)
|
||||
|
||||
A cancelable reader for Go
|
||||
|
||||
This package is based on the fantastic work of [Erik Geiser](https://github.com/erikgeiser)
|
||||
in Charm's [Bubble Tea](https://github.com/charmbracelet/bubbletea) framework.
|
||||
|
||||
## Usage
|
||||
|
||||
`NewReader` returns a reader with a `Cancel` function. If the input reader is a
|
||||
`File`, the cancel function can be used to interrupt a blocking `Read` call.
|
||||
In this case, the cancel function returns true if the call was canceled
|
||||
successfully. If the input reader is not a `File`, the cancel function does
|
||||
nothing and always returns false.
|
||||
|
||||
```go
|
||||
r, err := cancelreader.NewReader(file)
|
||||
if err != nil {
|
||||
// handle error
|
||||
...
|
||||
}
|
||||
|
||||
// cancel after five seconds
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
r.Cancel()
|
||||
}()
|
||||
|
||||
// keep reading
|
||||
for {
|
||||
var buf [1024]byte
|
||||
_, err := r.Read(buf[:])
|
||||
|
||||
if errors.Is(err, cancelreader.ErrCanceled) {
|
||||
fmt.Println("canceled!")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// handle other errors
|
||||
...
|
||||
}
|
||||
|
||||
// handle data
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Implementations
|
||||
|
||||
- The Linux implementation is based on the epoll mechanism
|
||||
- The BSD and macOS implementation is based on the kqueue mechanism
|
||||
- The generic Unix implementation is based on the posix select syscall
|
||||
|
||||
## Caution
|
||||
|
||||
The Windows implementation is based on WaitForMultipleObject with overlapping
|
||||
reads from CONIN$. At this point it only supports canceling reads from
|
||||
`os.Stdin`.
|
93
vendor/github.com/muesli/cancelreader/cancelreader.go
generated
vendored
Normal file
93
vendor/github.com/muesli/cancelreader/cancelreader.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrCanceled gets returned when trying to read from a canceled reader.
|
||||
var ErrCanceled = fmt.Errorf("read canceled")
|
||||
|
||||
// CancelReader is a io.Reader whose Read() calls can be canceled without data
|
||||
// being consumed. The cancelReader has to be closed.
|
||||
type CancelReader interface {
|
||||
io.ReadCloser
|
||||
|
||||
// Cancel cancels ongoing and future reads an returns true if it succeeded.
|
||||
Cancel() bool
|
||||
}
|
||||
|
||||
// File represents an input/output resource with a file descriptor.
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
|
||||
// Fd returns its file descriptor
|
||||
Fd() uintptr
|
||||
|
||||
// Name returns its file name.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// fallbackCancelReader implements cancelReader but does not actually support
|
||||
// cancelation during an ongoing Read() call. Thus, Cancel() always returns
|
||||
// false. However, after calling Cancel(), new Read() calls immediately return
|
||||
// errCanceled and don't consume any data anymore.
|
||||
type fallbackCancelReader struct {
|
||||
r io.Reader
|
||||
cancelMixin
|
||||
}
|
||||
|
||||
// newFallbackCancelReader is a fallback for NewReader that cannot actually
|
||||
// cancel an ongoing read but will immediately return on future reads if it has
|
||||
// been canceled.
|
||||
func newFallbackCancelReader(reader io.Reader) (CancelReader, error) {
|
||||
return &fallbackCancelReader{r: reader}, nil
|
||||
}
|
||||
|
||||
func (r *fallbackCancelReader) Read(data []byte) (int, error) {
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
n, err := r.r.Read(data)
|
||||
/*
|
||||
If the underlying reader is a blocking reader (e.g. an open connection),
|
||||
it might happen that 1 goroutine cancels the reader while its stuck in
|
||||
the read call waiting for something.
|
||||
If that happens, we should still cancel the read.
|
||||
*/
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
return n, err // nolint: wrapcheck
|
||||
}
|
||||
|
||||
func (r *fallbackCancelReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *fallbackCancelReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cancelMixin represents a goroutine-safe cancelation status.
|
||||
type cancelMixin struct {
|
||||
unsafeCanceled bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (c *cancelMixin) isCanceled() bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
return c.unsafeCanceled
|
||||
}
|
||||
|
||||
func (c *cancelMixin) setCanceled() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.unsafeCanceled = true
|
||||
}
|
146
vendor/github.com/muesli/cancelreader/cancelreader_bsd.go
generated
vendored
Normal file
146
vendor/github.com/muesli/cancelreader/cancelreader_bsd.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
//go:build darwin || freebsd || netbsd || openbsd || dragonfly
|
||||
// +build darwin freebsd netbsd openbsd dragonfly
|
||||
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewReader returns a reader and a cancel function. If the input reader is a
|
||||
// File, the cancel function can be used to interrupt a blocking read call.
|
||||
// In this case, the cancel function returns true if the call was canceled
|
||||
// successfully. If the input reader is not a File, the cancel function
|
||||
// does nothing and always returns false. The BSD and macOS implementation is
|
||||
// based on the kqueue mechanism.
|
||||
func NewReader(reader io.Reader) (CancelReader, error) {
|
||||
file, ok := reader.(File)
|
||||
if !ok {
|
||||
return newFallbackCancelReader(reader)
|
||||
}
|
||||
|
||||
// kqueue returns instantly when polling /dev/tty so fallback to select
|
||||
if file.Name() == "/dev/tty" {
|
||||
return newSelectCancelReader(reader)
|
||||
}
|
||||
|
||||
kQueue, err := unix.Kqueue()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create kqueue: %w", err)
|
||||
}
|
||||
|
||||
r := &kqueueCancelReader{
|
||||
file: file,
|
||||
kQueue: kQueue,
|
||||
}
|
||||
|
||||
r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe()
|
||||
if err != nil {
|
||||
_ = unix.Close(kQueue)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unix.SetKevent(&r.kQueueEvents[0], int(file.Fd()), unix.EVFILT_READ, unix.EV_ADD)
|
||||
unix.SetKevent(&r.kQueueEvents[1], int(r.cancelSignalReader.Fd()), unix.EVFILT_READ, unix.EV_ADD)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type kqueueCancelReader struct {
|
||||
file File
|
||||
cancelSignalReader File
|
||||
cancelSignalWriter File
|
||||
cancelMixin
|
||||
kQueue int
|
||||
kQueueEvents [2]unix.Kevent_t
|
||||
}
|
||||
|
||||
func (r *kqueueCancelReader) Read(data []byte) (int, error) {
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
err := r.wait()
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCanceled) {
|
||||
// remove signal from pipe
|
||||
var b [1]byte
|
||||
_, errRead := r.cancelSignalReader.Read(b[:])
|
||||
if errRead != nil {
|
||||
return 0, fmt.Errorf("reading cancel signal: %w", errRead)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return r.file.Read(data)
|
||||
}
|
||||
|
||||
func (r *kqueueCancelReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
|
||||
// send cancel signal
|
||||
_, err := r.cancelSignalWriter.Write([]byte{'c'})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (r *kqueueCancelReader) Close() error {
|
||||
var errMsgs []string
|
||||
|
||||
// close kqueue
|
||||
err := unix.Close(r.kQueue)
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing kqueue: %v", err))
|
||||
}
|
||||
|
||||
// close pipe
|
||||
err = r.cancelSignalWriter.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err))
|
||||
}
|
||||
|
||||
err = r.cancelSignalReader.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err))
|
||||
}
|
||||
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf(strings.Join(errMsgs, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *kqueueCancelReader) wait() error {
|
||||
events := make([]unix.Kevent_t, 1)
|
||||
|
||||
for {
|
||||
_, err := unix.Kevent(r.kQueue, r.kQueueEvents[:], events, nil)
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue // try again if the syscall was interrupted
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kevent: %w", err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
ident := uint64(events[0].Ident)
|
||||
switch ident {
|
||||
case uint64(r.file.Fd()):
|
||||
return nil
|
||||
case uint64(r.cancelSignalReader.Fd()):
|
||||
return ErrCanceled
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
12
vendor/github.com/muesli/cancelreader/cancelreader_default.go
generated
vendored
Normal file
12
vendor/github.com/muesli/cancelreader/cancelreader_default.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build !darwin && !windows && !linux && !solaris && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||
// +build !darwin,!windows,!linux,!solaris,!freebsd,!netbsd,!openbsd,!dragonfly
|
||||
|
||||
package cancelreader
|
||||
|
||||
import "io"
|
||||
|
||||
// NewReader returns a fallbackCancelReader that satisfies the CancelReader but
|
||||
// does not actually support cancellation.
|
||||
func NewReader(reader io.Reader) (CancelReader, error) {
|
||||
return newFallbackCancelReader(reader)
|
||||
}
|
154
vendor/github.com/muesli/cancelreader/cancelreader_linux.go
generated
vendored
Normal file
154
vendor/github.com/muesli/cancelreader/cancelreader_linux.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// NewReader returns a reader and a cancel function. If the input reader is a
|
||||
// File, the cancel function can be used to interrupt a blocking read call.
|
||||
// In this case, the cancel function returns true if the call was canceled
|
||||
// successfully. If the input reader is not a File, the cancel function
|
||||
// does nothing and always returns false. The Linux implementation is based on
|
||||
// the epoll mechanism.
|
||||
func NewReader(reader io.Reader) (CancelReader, error) {
|
||||
file, ok := reader.(File)
|
||||
if !ok {
|
||||
return newFallbackCancelReader(reader)
|
||||
}
|
||||
|
||||
epoll, err := unix.EpollCreate1(0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create epoll: %w", err)
|
||||
}
|
||||
|
||||
r := &epollCancelReader{
|
||||
file: file,
|
||||
epoll: epoll,
|
||||
}
|
||||
|
||||
r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe()
|
||||
if err != nil {
|
||||
_ = unix.Close(epoll)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(file.Fd()), &unix.EpollEvent{
|
||||
Events: unix.EPOLLIN,
|
||||
Fd: int32(file.Fd()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = unix.Close(epoll)
|
||||
return nil, fmt.Errorf("add reader to epoll interest list")
|
||||
}
|
||||
|
||||
err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(r.cancelSignalReader.Fd()), &unix.EpollEvent{
|
||||
Events: unix.EPOLLIN,
|
||||
Fd: int32(r.cancelSignalReader.Fd()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = unix.Close(epoll)
|
||||
return nil, fmt.Errorf("add reader to epoll interest list")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type epollCancelReader struct {
|
||||
file File
|
||||
cancelSignalReader File
|
||||
cancelSignalWriter File
|
||||
cancelMixin
|
||||
epoll int
|
||||
}
|
||||
|
||||
func (r *epollCancelReader) Read(data []byte) (int, error) {
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
err := r.wait()
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrCanceled) {
|
||||
// remove signal from pipe
|
||||
var b [1]byte
|
||||
_, readErr := r.cancelSignalReader.Read(b[:])
|
||||
if readErr != nil {
|
||||
return 0, fmt.Errorf("reading cancel signal: %w", readErr)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return r.file.Read(data)
|
||||
}
|
||||
|
||||
func (r *epollCancelReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
|
||||
// send cancel signal
|
||||
_, err := r.cancelSignalWriter.Write([]byte{'c'})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (r *epollCancelReader) Close() error {
|
||||
var errMsgs []string
|
||||
|
||||
// close kqueue
|
||||
err := unix.Close(r.epoll)
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing epoll: %v", err))
|
||||
}
|
||||
|
||||
// close pipe
|
||||
err = r.cancelSignalWriter.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err))
|
||||
}
|
||||
|
||||
err = r.cancelSignalReader.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err))
|
||||
}
|
||||
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf(strings.Join(errMsgs, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *epollCancelReader) wait() error {
|
||||
events := make([]unix.EpollEvent, 1)
|
||||
|
||||
for {
|
||||
_, err := unix.EpollWait(r.epoll, events, -1)
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue // try again if the syscall was interrupted
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("kevent: %w", err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
switch events[0].Fd {
|
||||
case int32(r.file.Fd()):
|
||||
return nil
|
||||
case int32(r.cancelSignalReader.Fd()):
|
||||
return ErrCanceled
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
136
vendor/github.com/muesli/cancelreader/cancelreader_select.go
generated
vendored
Normal file
136
vendor/github.com/muesli/cancelreader/cancelreader_select.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
//go:build solaris || darwin || freebsd || netbsd || openbsd || dragonfly
|
||||
// +build solaris darwin freebsd netbsd openbsd dragonfly
|
||||
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// newSelectCancelReader returns a reader and a cancel function. If the input
|
||||
// reader is a File, the cancel function can be used to interrupt a
|
||||
// blocking call read call. In this case, the cancel function returns true if
|
||||
// the call was canceled successfully. If the input reader is not a File or
|
||||
// the file descriptor is 1024 or larger, the cancel function does nothing and
|
||||
// always returns false. The generic unix implementation is based on the posix
|
||||
// select syscall.
|
||||
func newSelectCancelReader(reader io.Reader) (CancelReader, error) {
|
||||
file, ok := reader.(File)
|
||||
if !ok || file.Fd() >= unix.FD_SETSIZE {
|
||||
return newFallbackCancelReader(reader)
|
||||
}
|
||||
r := &selectCancelReader{file: file}
|
||||
|
||||
var err error
|
||||
|
||||
r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type selectCancelReader struct {
|
||||
file File
|
||||
cancelSignalReader File
|
||||
cancelSignalWriter File
|
||||
cancelMixin
|
||||
}
|
||||
|
||||
func (r *selectCancelReader) Read(data []byte) (int, error) {
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
for {
|
||||
err := waitForRead(r.file, r.cancelSignalReader)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue // try again if the syscall was interrupted
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrCanceled) {
|
||||
// remove signal from pipe
|
||||
var b [1]byte
|
||||
_, readErr := r.cancelSignalReader.Read(b[:])
|
||||
if readErr != nil {
|
||||
return 0, fmt.Errorf("reading cancel signal: %w", readErr)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return r.file.Read(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *selectCancelReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
|
||||
// send cancel signal
|
||||
_, err := r.cancelSignalWriter.Write([]byte{'c'})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (r *selectCancelReader) Close() error {
|
||||
var errMsgs []string
|
||||
|
||||
// close pipe
|
||||
err := r.cancelSignalWriter.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err))
|
||||
}
|
||||
|
||||
err = r.cancelSignalReader.Close()
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err))
|
||||
}
|
||||
|
||||
if len(errMsgs) > 0 {
|
||||
return fmt.Errorf(strings.Join(errMsgs, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForRead(reader, abort File) error {
|
||||
readerFd := int(reader.Fd())
|
||||
abortFd := int(abort.Fd())
|
||||
|
||||
maxFd := readerFd
|
||||
if abortFd > maxFd {
|
||||
maxFd = abortFd
|
||||
}
|
||||
|
||||
// this is a limitation of the select syscall
|
||||
if maxFd >= unix.FD_SETSIZE {
|
||||
return fmt.Errorf("cannot select on file descriptor %d which is larger than 1024", maxFd)
|
||||
}
|
||||
|
||||
fdSet := &unix.FdSet{}
|
||||
fdSet.Set(int(reader.Fd()))
|
||||
fdSet.Set(int(abort.Fd()))
|
||||
|
||||
_, err := unix.Select(maxFd+1, fdSet, nil, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select: %w", err)
|
||||
}
|
||||
|
||||
if fdSet.IsSet(abortFd) {
|
||||
return ErrCanceled
|
||||
}
|
||||
|
||||
if fdSet.IsSet(readerFd) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("select returned without setting a file descriptor")
|
||||
}
|
18
vendor/github.com/muesli/cancelreader/cancelreader_unix.go
generated
vendored
Normal file
18
vendor/github.com/muesli/cancelreader/cancelreader_unix.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// NewReader returns a reader and a cancel function. If the input reader is a
|
||||
// File, the cancel function can be used to interrupt a blocking read call.
|
||||
// In this case, the cancel function returns true if the call was canceled
|
||||
// successfully. If the input reader is not a File or the file descriptor
|
||||
// is 1024 or larger, the cancel function does nothing and always returns false.
|
||||
// The generic unix implementation is based on the posix select syscall.
|
||||
func NewReader(reader io.Reader) (CancelReader, error) {
|
||||
return newSelectCancelReader(reader)
|
||||
}
|
244
vendor/github.com/muesli/cancelreader/cancelreader_windows.go
generated
vendored
Normal file
244
vendor/github.com/muesli/cancelreader/cancelreader_windows.go
generated
vendored
Normal file
@ -0,0 +1,244 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package cancelreader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var fileShareValidFlags uint32 = 0x00000007
|
||||
|
||||
// NewReader returns a reader and a cancel function. If the input reader is a
|
||||
// File with the same file descriptor as os.Stdin, the cancel function can
|
||||
// be used to interrupt a blocking read call. In this case, the cancel function
|
||||
// returns true if the call was canceled successfully. If the input reader is
|
||||
// not a File with the same file descriptor as os.Stdin, the cancel
|
||||
// function does nothing and always returns false. The Windows implementation
|
||||
// is based on WaitForMultipleObject with overlapping reads from CONIN$.
|
||||
func NewReader(reader io.Reader) (CancelReader, error) {
|
||||
if f, ok := reader.(File); !ok || f.Fd() != os.Stdin.Fd() {
|
||||
return newFallbackCancelReader(reader)
|
||||
}
|
||||
|
||||
// it is necessary to open CONIN$ (NOT windows.STD_INPUT_HANDLE) in
|
||||
// overlapped mode to be able to use it with WaitForMultipleObjects.
|
||||
conin, err := windows.CreateFile(
|
||||
&(utf16.Encode([]rune("CONIN$\x00"))[0]), windows.GENERIC_READ|windows.GENERIC_WRITE,
|
||||
fileShareValidFlags, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open CONIN$ in overlapping mode: %w", err)
|
||||
}
|
||||
|
||||
resetConsole, err := prepareConsole(conin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare console: %w", err)
|
||||
}
|
||||
|
||||
// flush input, otherwise it can contain events which trigger
|
||||
// WaitForMultipleObjects but which ReadFile cannot read, resulting in an
|
||||
// un-cancelable read
|
||||
err = flushConsoleInputBuffer(conin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("flush console input buffer: %w", err)
|
||||
}
|
||||
|
||||
cancelEvent, err := windows.CreateEvent(nil, 0, 0, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create stop event: %w", err)
|
||||
}
|
||||
|
||||
return &winCancelReader{
|
||||
conin: conin,
|
||||
cancelEvent: cancelEvent,
|
||||
resetConsole: resetConsole,
|
||||
blockingReadSignal: make(chan struct{}, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type winCancelReader struct {
|
||||
conin windows.Handle
|
||||
cancelEvent windows.Handle
|
||||
cancelMixin
|
||||
|
||||
resetConsole func() error
|
||||
blockingReadSignal chan struct{}
|
||||
}
|
||||
|
||||
func (r *winCancelReader) Read(data []byte) (int, error) {
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
err := r.wait()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if r.isCanceled() {
|
||||
return 0, ErrCanceled
|
||||
}
|
||||
|
||||
// windows.Read does not work on overlapping windows.Handles
|
||||
return r.readAsync(data)
|
||||
}
|
||||
|
||||
// Cancel cancels ongoing and future Read() calls and returns true if the
|
||||
// cancelation of the ongoing Read() was successful. On Windows Terminal,
|
||||
// WaitForMultipleObjects sometimes immediately returns without input being
|
||||
// available. In this case, graceful cancelation is not possible and Cancel()
|
||||
// returns false.
|
||||
func (r *winCancelReader) Cancel() bool {
|
||||
r.setCanceled()
|
||||
|
||||
select {
|
||||
case r.blockingReadSignal <- struct{}{}:
|
||||
err := windows.SetEvent(r.cancelEvent)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
<-r.blockingReadSignal
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// Read() hangs in a GetOverlappedResult which is likely due to
|
||||
// WaitForMultipleObjects returning without input being available
|
||||
// so we cannot cancel this ongoing read.
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *winCancelReader) Close() error {
|
||||
err := windows.CloseHandle(r.cancelEvent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing cancel event handle: %w", err)
|
||||
}
|
||||
|
||||
err = r.resetConsole()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = windows.Close(r.conin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing CONIN$")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *winCancelReader) wait() error {
|
||||
event, err := windows.WaitForMultipleObjects([]windows.Handle{r.conin, r.cancelEvent}, false, windows.INFINITE)
|
||||
switch {
|
||||
case windows.WAIT_OBJECT_0 <= event && event < windows.WAIT_OBJECT_0+2:
|
||||
if event == windows.WAIT_OBJECT_0+1 {
|
||||
return ErrCanceled
|
||||
}
|
||||
|
||||
if event == windows.WAIT_OBJECT_0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected wait object is ready: %d", event-windows.WAIT_OBJECT_0)
|
||||
case windows.WAIT_ABANDONED <= event && event < windows.WAIT_ABANDONED+2:
|
||||
return fmt.Errorf("abandoned")
|
||||
case event == uint32(windows.WAIT_TIMEOUT):
|
||||
return fmt.Errorf("timeout")
|
||||
case event == windows.WAIT_FAILED:
|
||||
return fmt.Errorf("failed")
|
||||
default:
|
||||
return fmt.Errorf("unexpected error: %w", error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// readAsync is necessary to read from a windows.Handle in overlapping mode.
|
||||
func (r *winCancelReader) readAsync(data []byte) (int, error) {
|
||||
hevent, err := windows.CreateEvent(nil, 0, 0, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("create event: %w", err)
|
||||
}
|
||||
|
||||
overlapped := windows.Overlapped{
|
||||
HEvent: hevent,
|
||||
}
|
||||
|
||||
var n uint32
|
||||
|
||||
err = windows.ReadFile(r.conin, data, &n, &overlapped)
|
||||
if err != nil && err != windows.ERROR_IO_PENDING {
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
r.blockingReadSignal <- struct{}{}
|
||||
err = windows.GetOverlappedResult(r.conin, &overlapped, &n, true)
|
||||
if err != nil {
|
||||
return int(n), nil
|
||||
}
|
||||
<-r.blockingReadSignal
|
||||
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func prepareConsole(input windows.Handle) (reset func() error, err error) {
|
||||
var originalMode uint32
|
||||
|
||||
err = windows.GetConsoleMode(input, &originalMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get console mode: %w", err)
|
||||
}
|
||||
|
||||
var newMode uint32
|
||||
newMode &^= windows.ENABLE_ECHO_INPUT
|
||||
newMode &^= windows.ENABLE_LINE_INPUT
|
||||
newMode &^= windows.ENABLE_MOUSE_INPUT
|
||||
newMode &^= windows.ENABLE_WINDOW_INPUT
|
||||
newMode &^= windows.ENABLE_PROCESSED_INPUT
|
||||
|
||||
newMode |= windows.ENABLE_EXTENDED_FLAGS
|
||||
newMode |= windows.ENABLE_INSERT_MODE
|
||||
newMode |= windows.ENABLE_QUICK_EDIT_MODE
|
||||
|
||||
// Enabling virtual terminal input is necessary for processing certain
|
||||
// types of input like X10 mouse events and arrows keys with the current
|
||||
// bytes-based input reader. It does, however, prevent cancelReader from
|
||||
// being able to cancel input. The planned solution for this is to read
|
||||
// Windows events in a more native fashion, rather than the current simple
|
||||
// bytes-based input reader which works well on unix systems.
|
||||
newMode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||
|
||||
err = windows.SetConsoleMode(input, newMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set console mode: %w", err)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
err := windows.SetConsoleMode(input, originalMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reset console mode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procFlushConsoleInputBuffer = modkernel32.NewProc("FlushConsoleInputBuffer")
|
||||
)
|
||||
|
||||
func flushConsoleInputBuffer(consoleInput windows.Handle) error {
|
||||
r, _, e := syscall.Syscall(procFlushConsoleInputBuffer.Addr(), 1,
|
||||
uintptr(consoleInput), 0, 0)
|
||||
if r == 0 {
|
||||
return error(e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user