okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman Bagabasfix(poll): linux: improve error handling in Close methodf434aad

main
3.6 KiB175 linesraw
1//go:build linux
2// +build linux
3
4package uv
5
6import (
7	"errors"
8	"fmt"
9	"io"
10	"os"
11	"sync"
12	"time"
13
14	"golang.org/x/sys/unix"
15)
16
17// newPollReader creates a new pollReader for the given io.Reader.
18func newPollReader(reader io.Reader) (pollReader, error) {
19	file, ok := reader.(File)
20	if !ok {
21		return newFallbackReader(reader)
22	}
23
24	epoll, err := unix.EpollCreate1(0)
25	if err != nil {
26		return nil, fmt.Errorf("create epoll: %w", err)
27	}
28
29	r := &epollReader{
30		reader: reader,
31		file:   file,
32		epoll:  epoll,
33	}
34
35	r.cancelSignalReader, r.cancelSignalWriter, err = os.Pipe()
36	if err != nil {
37		_ = unix.Close(epoll)
38		return nil, err
39	}
40
41	err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(file.Fd()), &unix.EpollEvent{
42		Events: unix.EPOLLIN,
43		Fd:     int32(file.Fd()),
44	})
45	if err != nil {
46		_ = unix.Close(epoll)
47		_ = r.cancelSignalReader.Close()
48		_ = r.cancelSignalWriter.Close()
49		return nil, fmt.Errorf("add reader to epoll interest list: %w", err)
50	}
51
52	err = unix.EpollCtl(epoll, unix.EPOLL_CTL_ADD, int(r.cancelSignalReader.Fd()), &unix.EpollEvent{
53		Events: unix.EPOLLIN,
54		Fd:     int32(r.cancelSignalReader.Fd()),
55	})
56	if err != nil {
57		_ = unix.Close(epoll)
58		_ = r.cancelSignalReader.Close()
59		_ = r.cancelSignalWriter.Close()
60		return nil, fmt.Errorf("add cancel signal to epoll interest list: %w", err)
61	}
62
63	return r, nil
64}
65
66// epollReader implements pollReader using the Linux epoll API.
67type epollReader struct {
68	reader             io.Reader
69	file               File
70	cancelSignalReader *os.File
71	cancelSignalWriter *os.File
72	epoll              int
73	mu                 sync.Mutex
74	canceled           bool
75}
76
77// Read reads data from the underlying reader.
78func (r *epollReader) Read(p []byte) (int, error) {
79	r.mu.Lock()
80	if r.canceled {
81		r.mu.Unlock()
82		return 0, ErrCanceled
83	}
84	r.mu.Unlock()
85
86	return r.reader.Read(p)
87}
88
89// Poll waits for data to be available to read with the given timeout.
90func (r *epollReader) Poll(timeout time.Duration) (bool, error) {
91	r.mu.Lock()
92	if r.canceled {
93		r.mu.Unlock()
94		return false, ErrCanceled
95	}
96	r.mu.Unlock()
97
98	events := make([]unix.EpollEvent, 1)
99
100	timeoutMs := -1
101	if timeout >= 0 {
102		timeoutMs = int(timeout.Milliseconds())
103	}
104
105	for {
106		n, err := unix.EpollWait(r.epoll, events, timeoutMs)
107		if errors.Is(err, unix.EINTR) {
108			continue // try again if the syscall was interrupted
109		}
110
111		if err != nil {
112			return false, fmt.Errorf("epoll wait: %w", err)
113		}
114
115		if n == 0 {
116			return false, nil // timeout
117		}
118
119		break
120	}
121
122	switch events[0].Fd {
123	case int32(r.file.Fd()):
124		return true, nil
125	case int32(r.cancelSignalReader.Fd()):
126		// remove signal from pipe
127		var b [1]byte
128		_, readErr := r.cancelSignalReader.Read(b[:])
129		if readErr != nil {
130			return false, fmt.Errorf("reading cancel signal: %w", readErr)
131		}
132		return false, ErrCanceled
133	}
134
135	return false, fmt.Errorf("unknown error")
136}
137
138// Cancel cancels any ongoing poll or read operations.
139func (r *epollReader) Cancel() bool {
140	r.mu.Lock()
141	r.canceled = true
142	r.mu.Unlock()
143
144	// send cancel signal
145	_, err := r.cancelSignalWriter.Write([]byte{'c'})
146	return err == nil
147}
148
149// Close closes the reader and releases any resources.
150func (r *epollReader) Close() error {
151	var errMsgs []error
152
153	// close epoll
154	err := unix.Close(r.epoll)
155	if err != nil {
156		errMsgs = append(errMsgs, fmt.Errorf("closing epoll: %w", err))
157	}
158
159	// close pipe
160	err = r.cancelSignalWriter.Close()
161	if err != nil {
162		errMsgs = append(errMsgs, fmt.Errorf("closing cancel signal writer: %w", err))
163	}
164
165	err = r.cancelSignalReader.Close()
166	if err != nil {
167		errMsgs = append(errMsgs, fmt.Errorf("closing cancel signal reader: %w", err))
168	}
169
170	if len(errMsgs) > 0 {
171		return errors.Join(errMsgs...)
172	}
173
174	return nil
175}