Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 1 | // Copyright 2014 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 | |
| 5 | package runtime |
| 6 | |
| 7 | import "unsafe" |
| 8 | |
| 9 | // Solaris runtime-integrated network poller. |
| 10 | // |
| 11 | // Solaris uses event ports for scalable network I/O. Event |
| 12 | // ports are level-triggered, unlike epoll and kqueue which |
| 13 | // can be configured in both level-triggered and edge-triggered |
| 14 | // mode. Level triggering means we have to keep track of a few things |
| 15 | // ourselves. After we receive an event for a file descriptor, |
| 16 | // it's our responsibility to ask again to be notified for future |
| 17 | // events for that descriptor. When doing this we must keep track of |
| 18 | // what kind of events the goroutines are currently interested in, |
| 19 | // for example a fd may be open both for reading and writing. |
| 20 | // |
| 21 | // A description of the high level operation of this code |
| 22 | // follows. Networking code will get a file descriptor by some means |
| 23 | // and will register it with the netpolling mechanism by a code path |
| 24 | // that eventually calls runtime·netpollopen. runtime·netpollopen |
| 25 | // calls port_associate with an empty event set. That means that we |
| 26 | // will not receive any events at this point. The association needs |
| 27 | // to be done at this early point because we need to process the I/O |
| 28 | // readiness notification at some point in the future. If I/O becomes |
| 29 | // ready when nobody is listening, when we finally care about it, |
| 30 | // nobody will tell us anymore. |
| 31 | // |
| 32 | // Beside calling runtime·netpollopen, the networking code paths |
| 33 | // will call runtime·netpollarm each time goroutines are interested |
| 34 | // in doing network I/O. Because now we know what kind of I/O we |
Ainar Garipov | 7f9f70e | 2015-06-11 16:49:38 +0300 | [diff] [blame] | 35 | // are interested in (reading/writing), we can call port_associate |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 36 | // passing the correct type of event set (POLLIN/POLLOUT). As we made |
| 37 | // sure to have already associated the file descriptor with the port, |
| 38 | // when we now call port_associate, we will unblock the main poller |
| 39 | // loop (in runtime·netpoll) right away if the socket is actually |
| 40 | // ready for I/O. |
| 41 | // |
| 42 | // The main poller loop runs in its own thread waiting for events |
| 43 | // using port_getn. When an event happens, it will tell the scheduler |
| 44 | // about it using runtime·netpollready. Besides doing this, it must |
| 45 | // also re-associate the events that were not part of this current |
| 46 | // notification with the file descriptor. Failing to do this would |
| 47 | // mean each notification will prevent concurrent code using the |
| 48 | // same file descriptor in parallel. |
| 49 | // |
| 50 | // The logic dealing with re-associations is encapsulated in |
| 51 | // runtime·netpollupdate. This function takes care to associate the |
| 52 | // descriptor only with the subset of events that were previously |
| 53 | // part of the association, except the one that just happened. We |
| 54 | // can't re-associate with that right away, because event ports |
| 55 | // are level triggered so it would cause a busy loop. Instead, that |
| 56 | // association is effected only by the runtime·netpollarm code path, |
| 57 | // when Go code actually asks for I/O. |
| 58 | // |
| 59 | // The open and arming mechanisms are serialized using the lock |
| 60 | // inside PollDesc. This is required because the netpoll loop runs |
Martin Möhrmann | fdd0179 | 2016-02-24 11:55:20 +0100 | [diff] [blame] | 61 | // asynchronously in respect to other Go code and by the time we get |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 62 | // to call port_associate to update the association in the loop, the |
| 63 | // file descriptor might have been closed and reopened already. The |
| 64 | // lock allows runtime·netpollupdate to be called synchronously from |
| 65 | // the loop thread while preventing other threads operating to the |
| 66 | // same PollDesc, so once we unblock in the main loop, until we loop |
| 67 | // again we know for sure we are always talking about the same file |
| 68 | // descriptor and can safely access the data we want (the event set). |
| 69 | |
| 70 | //go:cgo_import_dynamic libc_port_create port_create "libc.so" |
| 71 | //go:cgo_import_dynamic libc_port_associate port_associate "libc.so" |
| 72 | //go:cgo_import_dynamic libc_port_dissociate port_dissociate "libc.so" |
| 73 | //go:cgo_import_dynamic libc_port_getn port_getn "libc.so" |
| 74 | |
| 75 | //go:linkname libc_port_create libc_port_create |
| 76 | //go:linkname libc_port_associate libc_port_associate |
| 77 | //go:linkname libc_port_dissociate libc_port_dissociate |
| 78 | //go:linkname libc_port_getn libc_port_getn |
| 79 | |
| 80 | var ( |
| 81 | libc_port_create, |
| 82 | libc_port_associate, |
| 83 | libc_port_dissociate, |
| 84 | libc_port_getn libcFunc |
| 85 | ) |
| 86 | |
| 87 | func errno() int32 { |
| 88 | return *getg().m.perrno |
| 89 | } |
| 90 | |
| 91 | func fcntl(fd, cmd int32, arg uintptr) int32 { |
Aram Hăvărneanu | c94f1f7 | 2015-03-30 13:52:07 +0200 | [diff] [blame] | 92 | return int32(sysvicall3(&libc_fcntl, uintptr(fd), uintptr(cmd), arg)) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | func port_create() int32 { |
Aram Hăvărneanu | c94f1f7 | 2015-03-30 13:52:07 +0200 | [diff] [blame] | 96 | return int32(sysvicall0(&libc_port_create)) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | func port_associate(port, source int32, object uintptr, events uint32, user uintptr) int32 { |
Aram Hăvărneanu | c94f1f7 | 2015-03-30 13:52:07 +0200 | [diff] [blame] | 100 | return int32(sysvicall5(&libc_port_associate, uintptr(port), uintptr(source), object, uintptr(events), user)) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 101 | } |
| 102 | |
| 103 | func port_dissociate(port, source int32, object uintptr) int32 { |
Aram Hăvărneanu | c94f1f7 | 2015-03-30 13:52:07 +0200 | [diff] [blame] | 104 | return int32(sysvicall3(&libc_port_dissociate, uintptr(port), uintptr(source), object)) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | func port_getn(port int32, evs *portevent, max uint32, nget *uint32, timeout *timespec) int32 { |
Aram Hăvărneanu | c94f1f7 | 2015-03-30 13:52:07 +0200 | [diff] [blame] | 108 | return int32(sysvicall5(&libc_port_getn, uintptr(port), uintptr(unsafe.Pointer(evs)), uintptr(max), uintptr(unsafe.Pointer(nget)), uintptr(unsafe.Pointer(timeout)))) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | var portfd int32 = -1 |
| 112 | |
| 113 | func netpollinit() { |
| 114 | portfd = port_create() |
| 115 | if portfd >= 0 { |
| 116 | fcntl(portfd, _F_SETFD, _FD_CLOEXEC) |
| 117 | return |
| 118 | } |
| 119 | |
Mikio Hara | 91c9b0d | 2017-04-24 18:37:48 +0900 | [diff] [blame] | 120 | print("runtime: port_create failed (errno=", errno(), ")\n") |
| 121 | throw("runtime: netpollinit failed") |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 122 | } |
| 123 | |
Ian Lance Taylor | c05b06a | 2017-02-10 15:17:38 -0800 | [diff] [blame] | 124 | func netpolldescriptor() uintptr { |
| 125 | return uintptr(portfd) |
| 126 | } |
| 127 | |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 128 | func netpollopen(fd uintptr, pd *pollDesc) int32 { |
| 129 | lock(&pd.lock) |
| 130 | // We don't register for any specific type of events yet, that's |
| 131 | // netpollarm's job. We merely ensure we call port_associate before |
Martin Möhrmann | fdd0179 | 2016-02-24 11:55:20 +0100 | [diff] [blame] | 132 | // asynchronous connect/accept completes, so when we actually want |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 133 | // to do any I/O, the call to port_associate (from netpollarm, |
| 134 | // with the interested event set) will unblock port_getn right away |
| 135 | // because of the I/O readiness notification. |
| 136 | pd.user = 0 |
| 137 | r := port_associate(portfd, _PORT_SOURCE_FD, fd, 0, uintptr(unsafe.Pointer(pd))) |
| 138 | unlock(&pd.lock) |
| 139 | return r |
| 140 | } |
| 141 | |
| 142 | func netpollclose(fd uintptr) int32 { |
| 143 | return port_dissociate(portfd, _PORT_SOURCE_FD, fd) |
| 144 | } |
| 145 | |
| 146 | // Updates the association with a new set of interested events. After |
| 147 | // this call, port_getn will return one and only one event for that |
| 148 | // particular descriptor, so this function needs to be called again. |
| 149 | func netpollupdate(pd *pollDesc, set, clear uint32) { |
| 150 | if pd.closing { |
| 151 | return |
| 152 | } |
| 153 | |
| 154 | old := pd.user |
| 155 | events := (old & ^clear) | set |
| 156 | if old == events { |
| 157 | return |
| 158 | } |
| 159 | |
| 160 | if events != 0 && port_associate(portfd, _PORT_SOURCE_FD, pd.fd, events, uintptr(unsafe.Pointer(pd))) != 0 { |
Mikio Hara | 91c9b0d | 2017-04-24 18:37:48 +0900 | [diff] [blame] | 161 | print("runtime: port_associate failed (errno=", errno(), ")\n") |
| 162 | throw("runtime: netpollupdate failed") |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 163 | } |
| 164 | pd.user = events |
| 165 | } |
| 166 | |
| 167 | // subscribe the fd to the port such that port_getn will return one event. |
| 168 | func netpollarm(pd *pollDesc, mode int) { |
| 169 | lock(&pd.lock) |
| 170 | switch mode { |
| 171 | case 'r': |
| 172 | netpollupdate(pd, _POLLIN, 0) |
| 173 | case 'w': |
| 174 | netpollupdate(pd, _POLLOUT, 0) |
| 175 | default: |
Mikio Hara | 91c9b0d | 2017-04-24 18:37:48 +0900 | [diff] [blame] | 176 | throw("runtime: bad mode") |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 177 | } |
| 178 | unlock(&pd.lock) |
| 179 | } |
| 180 | |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 181 | // polls for ready network connections |
| 182 | // returns list of goroutines that become runnable |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 183 | func netpoll(block bool) *g { |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 184 | if portfd == -1 { |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 185 | return nil |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | var wait *timespec |
| 189 | var zero timespec |
| 190 | if !block { |
| 191 | wait = &zero |
| 192 | } |
| 193 | |
| 194 | var events [128]portevent |
| 195 | retry: |
| 196 | var n uint32 = 1 |
| 197 | if port_getn(portfd, &events[0], uint32(len(events)), &n, wait) < 0 { |
Ian Lance Taylor | b6d115a | 2015-07-10 15:28:01 -0700 | [diff] [blame] | 198 | if e := errno(); e != _EINTR { |
Mikio Hara | 91c9b0d | 2017-04-24 18:37:48 +0900 | [diff] [blame] | 199 | print("runtime: port_getn on fd ", portfd, " failed (errno=", e, ")\n") |
| 200 | throw("runtime: netpoll failed") |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 201 | } |
| 202 | goto retry |
| 203 | } |
| 204 | |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 205 | var gp guintptr |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 206 | for i := 0; i < int(n); i++ { |
| 207 | ev := &events[i] |
| 208 | |
| 209 | if ev.portev_events == 0 { |
| 210 | continue |
| 211 | } |
| 212 | pd := (*pollDesc)(unsafe.Pointer(ev.portev_user)) |
| 213 | |
| 214 | var mode, clear int32 |
| 215 | if (ev.portev_events & (_POLLIN | _POLLHUP | _POLLERR)) != 0 { |
| 216 | mode += 'r' |
| 217 | clear |= _POLLIN |
| 218 | } |
| 219 | if (ev.portev_events & (_POLLOUT | _POLLHUP | _POLLERR)) != 0 { |
| 220 | mode += 'w' |
| 221 | clear |= _POLLOUT |
| 222 | } |
| 223 | // To effect edge-triggered events, we need to be sure to |
| 224 | // update our association with whatever events were not |
| 225 | // set with the event. For example if we are registered |
| 226 | // for POLLIN|POLLOUT, and we get POLLIN, besides waking |
| 227 | // the goroutine interested in POLLIN we have to not forget |
| 228 | // about the one interested in POLLOUT. |
| 229 | if clear != 0 { |
| 230 | lock(&pd.lock) |
| 231 | netpollupdate(pd, 0, uint32(clear)) |
| 232 | unlock(&pd.lock) |
| 233 | } |
| 234 | |
| 235 | if mode != 0 { |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 236 | netpollready(&gp, pd, mode) |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 237 | } |
| 238 | } |
| 239 | |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 240 | if block && gp == 0 { |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 241 | goto retry |
| 242 | } |
Russ Cox | 181e26b | 2015-04-17 00:21:30 -0400 | [diff] [blame] | 243 | return gp.ptr() |
Aram Hăvărneanu | e088e16 | 2014-11-13 16:07:10 +0100 | [diff] [blame] | 244 | } |