1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime
6
7import "unsafe"
8
9// This is based on the former libgo/runtime/netpoll_select.c implementation
10// except that it uses poll instead of select and is written in Go.
11// It's also based on Solaris implementation for the arming mechanisms
12
13//go:noescape
14//extern poll
15func libc_poll(pfds *pollfd, npfds uintptr, timeout uintptr) int32
16
17// pollfd represents the poll structure for AIX operating system.
18type pollfd struct {
19	fd      int32
20	events  int16
21	revents int16
22}
23
24const _POLLIN = 0x0001
25const _POLLOUT = 0x0002
26const _POLLHUP = 0x2000
27const _POLLERR = 0x4000
28
29var (
30	pfds           []pollfd
31	pds            []*pollDesc
32	mtxpoll        mutex
33	mtxset         mutex
34	rdwake         int32
35	wrwake         int32
36	pendingUpdates int32
37)
38
39func netpollinit() {
40	// Create the pipe we use to wakeup poll.
41	r, w, errno := nonblockingPipe()
42	if errno != 0 {
43		throw("netpollinit: failed to create pipe")
44	}
45	rdwake = r
46	wrwake = w
47
48	// Pre-allocate array of pollfd structures for poll.
49	pfds = make([]pollfd, 1, 128)
50
51	// Poll the read side of the pipe.
52	pfds[0].fd = rdwake
53	pfds[0].events = _POLLIN
54
55	pds = make([]*pollDesc, 1, 128)
56	pds[0] = nil
57}
58
59func netpollIsPollDescriptor(fd uintptr) bool {
60	return fd == uintptr(rdwake) || fd == uintptr(wrwake)
61}
62
63// netpollwakeup writes on wrwake to wakeup poll before any changes.
64func netpollwakeup() {
65	if pendingUpdates == 0 {
66		pendingUpdates = 1
67		b := [1]byte{0}
68		write(uintptr(wrwake), unsafe.Pointer(&b[0]), 1)
69	}
70}
71
72func netpollopen(fd uintptr, pd *pollDesc) int32 {
73	lock(&mtxpoll)
74	netpollwakeup()
75
76	lock(&mtxset)
77	unlock(&mtxpoll)
78
79	pd.user = uint32(len(pfds))
80	pfds = append(pfds, pollfd{fd: int32(fd)})
81	pds = append(pds, pd)
82	unlock(&mtxset)
83	return 0
84}
85
86func netpollclose(fd uintptr) int32 {
87	lock(&mtxpoll)
88	netpollwakeup()
89
90	lock(&mtxset)
91	unlock(&mtxpoll)
92
93	for i := 0; i < len(pfds); i++ {
94		if pfds[i].fd == int32(fd) {
95			pfds[i] = pfds[len(pfds)-1]
96			pfds = pfds[:len(pfds)-1]
97
98			pds[i] = pds[len(pds)-1]
99			pds[i].user = uint32(i)
100			pds = pds[:len(pds)-1]
101			break
102		}
103	}
104	unlock(&mtxset)
105	return 0
106}
107
108func netpollarm(pd *pollDesc, mode int) {
109	lock(&mtxpoll)
110	netpollwakeup()
111
112	lock(&mtxset)
113	unlock(&mtxpoll)
114
115	switch mode {
116	case 'r':
117		pfds[pd.user].events |= _POLLIN
118	case 'w':
119		pfds[pd.user].events |= _POLLOUT
120	}
121	unlock(&mtxset)
122}
123
124// netpollBreak interrupts an epollwait.
125func netpollBreak() {
126	netpollwakeup()
127}
128
129// netpoll checks for ready network connections.
130// Returns list of goroutines that become runnable.
131// delay < 0: blocks indefinitely
132// delay == 0: does not block, just polls
133// delay > 0: block for up to that many nanoseconds
134//go:nowritebarrierrec
135func netpoll(delay int64) gList {
136	var timeout uintptr
137	if delay < 0 {
138		timeout = ^uintptr(0)
139	} else if delay == 0 {
140		// TODO: call poll with timeout == 0
141		return gList{}
142	} else if delay < 1e6 {
143		timeout = 1
144	} else if delay < 1e15 {
145		timeout = uintptr(delay / 1e6)
146	} else {
147		// An arbitrary cap on how long to wait for a timer.
148		// 1e9 ms == ~11.5 days.
149		timeout = 1e9
150	}
151retry:
152	lock(&mtxpoll)
153	lock(&mtxset)
154	pendingUpdates = 0
155	unlock(&mtxpoll)
156
157	n := libc_poll(&pfds[0], uintptr(len(pfds)), timeout)
158	if n < 0 {
159		e := errno()
160		if e != _EINTR {
161			println("errno=", e, " len(pfds)=", len(pfds))
162			throw("poll failed")
163		}
164		unlock(&mtxset)
165		// If a timed sleep was interrupted, just return to
166		// recalculate how long we should sleep now.
167		if timeout > 0 {
168			return gList{}
169		}
170		goto retry
171	}
172	// Check if some descriptors need to be changed
173	if n != 0 && pfds[0].revents&(_POLLIN|_POLLHUP|_POLLERR) != 0 {
174		if delay != 0 {
175			// A netpollwakeup could be picked up by a
176			// non-blocking poll. Only clear the wakeup
177			// if blocking.
178			var b [1]byte
179			for read(rdwake, unsafe.Pointer(&b[0]), 1) == 1 {
180			}
181		}
182		// Still look at the other fds even if the mode may have
183		// changed, as netpollBreak might have been called.
184		n--
185	}
186	var toRun gList
187	for i := 1; i < len(pfds) && n > 0; i++ {
188		pfd := &pfds[i]
189
190		var mode int32
191		if pfd.revents&(_POLLIN|_POLLHUP|_POLLERR) != 0 {
192			mode += 'r'
193			pfd.events &= ^_POLLIN
194		}
195		if pfd.revents&(_POLLOUT|_POLLHUP|_POLLERR) != 0 {
196			mode += 'w'
197			pfd.events &= ^_POLLOUT
198		}
199		if mode != 0 {
200			pds[i].everr = false
201			if pfd.revents == _POLLERR {
202				pds[i].everr = true
203			}
204			netpollready(&toRun, pds[i], mode)
205			n--
206		}
207	}
208	unlock(&mtxset)
209	return toRun
210}
211