cancelreader_bsd.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. //go:build darwin || freebsd || netbsd || openbsd || dragonfly
  2. // +build darwin freebsd netbsd openbsd dragonfly
  3. package cancelreader
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strings"
  10. "golang.org/x/sys/unix"
  11. )
  12. // NewReader returns a reader and a cancel function. If the input reader is a
  13. // File, the cancel function can be used to interrupt a blocking read call.
  14. // In this case, the cancel function returns true if the call was canceled
  15. // successfully. If the input reader is not a File, the cancel function
  16. // does nothing and always returns false. The BSD and macOS implementation is
  17. // based on the kqueue mechanism.
  18. func NewReader(reader io.Reader) (CancelReader, error) {
  19. file, ok := reader.(File)
  20. if !ok {
  21. return newFallbackCancelReader(reader)
  22. }
  23. // kqueue returns instantly when polling /dev/tty so fallback to select
  24. if file.Name() == "/dev/tty" {
  25. return newSelectCancelReader(reader)
  26. }
  27. kQueue, err := unix.Kqueue()
  28. if err != nil {
  29. return nil, fmt.Errorf("create kqueue: %w", err)
  30. }
  31. r := &kqueueCancelReader{
  32. file: file,
  33. kQueue: kQueue,
  34. }
  35. r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe()
  36. if err != nil {
  37. _ = unix.Close(kQueue)
  38. return nil, err
  39. }
  40. unix.SetKevent(&r.kQueueEvents[0], int(file.Fd()), unix.EVFILT_READ, unix.EV_ADD)
  41. unix.SetKevent(&r.kQueueEvents[1], int(r.cancelSignalReader.Fd()), unix.EVFILT_READ, unix.EV_ADD)
  42. return r, nil
  43. }
  44. type kqueueCancelReader struct {
  45. file File
  46. cancelSignalReader File
  47. cancelSignalWriter File
  48. cancelMixin
  49. kQueue int
  50. kQueueEvents [2]unix.Kevent_t
  51. }
  52. func (r *kqueueCancelReader) Read(data []byte) (int, error) {
  53. if r.isCanceled() {
  54. return 0, ErrCanceled
  55. }
  56. err := r.wait()
  57. if err != nil {
  58. if errors.Is(err, ErrCanceled) {
  59. // remove signal from pipe
  60. var b [1]byte
  61. _, errRead := r.cancelSignalReader.Read(b[:])
  62. if errRead != nil {
  63. return 0, fmt.Errorf("reading cancel signal: %w", errRead)
  64. }
  65. }
  66. return 0, err
  67. }
  68. return r.file.Read(data)
  69. }
  70. func (r *kqueueCancelReader) Cancel() bool {
  71. r.setCanceled()
  72. // send cancel signal
  73. _, err := r.cancelSignalWriter.Write([]byte{'c'})
  74. return err == nil
  75. }
  76. func (r *kqueueCancelReader) Close() error {
  77. var errMsgs []string
  78. // close kqueue
  79. err := unix.Close(r.kQueue)
  80. if err != nil {
  81. errMsgs = append(errMsgs, fmt.Sprintf("closing kqueue: %v", err))
  82. }
  83. // close pipe
  84. err = r.cancelSignalWriter.Close()
  85. if err != nil {
  86. errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal writer: %v", err))
  87. }
  88. err = r.cancelSignalReader.Close()
  89. if err != nil {
  90. errMsgs = append(errMsgs, fmt.Sprintf("closing cancel signal reader: %v", err))
  91. }
  92. if len(errMsgs) > 0 {
  93. return fmt.Errorf(strings.Join(errMsgs, ", "))
  94. }
  95. return nil
  96. }
  97. func (r *kqueueCancelReader) wait() error {
  98. events := make([]unix.Kevent_t, 1)
  99. for {
  100. _, err := unix.Kevent(r.kQueue, r.kQueueEvents[:], events, nil)
  101. if errors.Is(err, unix.EINTR) {
  102. continue // try again if the syscall was interrupted
  103. }
  104. if err != nil {
  105. return fmt.Errorf("kevent: %w", err)
  106. }
  107. break
  108. }
  109. ident := uint64(events[0].Ident)
  110. switch ident {
  111. case uint64(r.file.Fd()):
  112. return nil
  113. case uint64(r.cancelSignalReader.Fd()):
  114. return ErrCanceled
  115. }
  116. return fmt.Errorf("unknown error")
  117. }