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.
9 // Solaris runtime-integrated network poller.
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.
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.
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
35 // are interested in (reading/writing), we can call port_associate
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
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.
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.
59 // The open and arming mechanisms are serialized using the lock
60 // inside PollDesc. This is required because the netpoll loop runs
61 // asynchronously in respect to other Go code and by the time we get
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).
70 //extern __go_fcntl_uintptr
71 func fcntlUintptr(fd
, cmd
, arg
uintptr) (uintptr, uintptr)
73 func fcntl(fd
, cmd
int32, arg
uintptr) int32 {
74 r
, _
:= fcntlUintptr(uintptr(fd
), uintptr(cmd
), arg
)
79 func port_create() int32
81 //extern port_associate
82 func port_associate(port
, source
int32, object
uintptr, events
uint32, user
uintptr) int32
84 //extern port_dissociate
85 func port_dissociate(port
, source
int32, object
uintptr) int32
88 func port_getn(port
int32, evs
*portevent
, max
uint32, nget
*uint32, timeout
*timespec
) int32
93 portfd
= port_create()
95 fcntl(portfd
, _F_SETFD
, _FD_CLOEXEC
)
99 print("netpollinit: failed to create port (", errno(), ")\n")
100 throw("netpollinit: failed to create port")
103 func netpollopen(fd
uintptr, pd
*pollDesc
) int32 {
105 // We don't register for any specific type of events yet, that's
106 // netpollarm's job. We merely ensure we call port_associate before
107 // asynchronous connect/accept completes, so when we actually want
108 // to do any I/O, the call to port_associate (from netpollarm,
109 // with the interested event set) will unblock port_getn right away
110 // because of the I/O readiness notification.
112 r
:= port_associate(portfd
, _PORT_SOURCE_FD
, fd
, 0, uintptr(unsafe
.Pointer(pd
)))
115 return int32(errno())
120 func netpollclose(fd
uintptr) int32 {
121 if port_dissociate(portfd
, _PORT_SOURCE_FD
, fd
) < 0 {
122 return int32(errno())
127 // Updates the association with a new set of interested events. After
128 // this call, port_getn will return one and only one event for that
129 // particular descriptor, so this function needs to be called again.
130 func netpollupdate(pd
*pollDesc
, set
, clear
uint32) {
136 events
:= (old
& ^clear
) | set
141 if events
!= 0 && port_associate(portfd
, _PORT_SOURCE_FD
, pd
.fd
, events
, uintptr(unsafe
.Pointer(pd
))) != 0 {
142 print("netpollupdate: failed to associate (", errno(), ")\n")
143 throw("netpollupdate: failed to associate")
148 // subscribe the fd to the port such that port_getn will return one event.
149 func netpollarm(pd
*pollDesc
, mode
int) {
153 netpollupdate(pd
, _POLLIN
, 0)
155 netpollupdate(pd
, _POLLOUT
, 0)
157 throw("netpollarm: bad mode")
162 // polls for ready network connections
163 // returns list of goroutines that become runnable
164 func netpoll(block
bool) *g
{
175 var events
[128]portevent
178 if port_getn(portfd
, &events
[0], uint32(len(events
)), &n
, wait
) < 0 {
179 if e
:= errno(); e
!= _EINTR
{
180 print("runtime: port_getn on fd ", portfd
, " failed with ", e
, "\n")
181 throw("port_getn failed")
187 for i
:= 0; i
< int(n
); i
++ {
190 if ev
.portev_events
== 0 {
193 pd
:= (*pollDesc
)(unsafe
.Pointer(ev
.portev_user
))
195 var mode
, clear
int32
196 if (ev
.portev_events
& (_POLLIN | _POLLHUP | _POLLERR
)) != 0 {
200 if (ev
.portev_events
& (_POLLOUT | _POLLHUP | _POLLERR
)) != 0 {
204 // To effect edge-triggered events, we need to be sure to
205 // update our association with whatever events were not
206 // set with the event. For example if we are registered
207 // for POLLIN|POLLOUT, and we get POLLIN, besides waking
208 // the goroutine interested in POLLIN we have to not forget
209 // about the one interested in POLLOUT.
212 netpollupdate(pd
, 0, uint32(clear
))
217 netpollready(&gp
, pd
, mode
)
221 if block
&& gp
== 0 {