Commit 812ba636 by Ian Lance Taylor

runtime: copy netpoll code from Go 1.7 runtime

    
    Reviewed-on: https://go-review.googlesource.com/31325

From-SVN: r241307
parent f5de494c
0a49b1dadd862215bdd38b9725a6e193b0d8fd0b 68bb6a9875499037d3eccb79a1f92e1c7a476d58
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -427,16 +427,6 @@ endif ...@@ -427,16 +427,6 @@ endif
endif endif
endif endif
if LIBGO_IS_LINUX
runtime_netpoll_files = runtime/netpoll_epoll.c
else
if LIBGO_IS_SOLARIS
runtime_netpoll_files = runtime/netpoll_select.c
else
runtime_netpoll_files = runtime/netpoll_kqueue.c
endif
endif
runtime_files = \ runtime_files = \
runtime/go-append.c \ runtime/go-append.c \
runtime/go-assert.c \ runtime/go-assert.c \
...@@ -500,7 +490,6 @@ runtime_files = \ ...@@ -500,7 +490,6 @@ runtime_files = \
runtime/mgc0.c \ runtime/mgc0.c \
runtime/mheap.c \ runtime/mheap.c \
runtime/msize.c \ runtime/msize.c \
$(runtime_netpoll_files) \
runtime/panic.c \ runtime/panic.c \
runtime/parfor.c \ runtime/parfor.c \
runtime/print.c \ runtime/print.c \
...@@ -514,7 +503,6 @@ runtime_files = \ ...@@ -514,7 +503,6 @@ runtime_files = \
go-iface.c \ go-iface.c \
lfstack.c \ lfstack.c \
malloc.c \ malloc.c \
netpoll.c \
reflect.c \ reflect.c \
runtime1.c \ runtime1.c \
sigqueue.c \ sigqueue.c \
...@@ -530,14 +518,6 @@ malloc.c: $(srcdir)/runtime/malloc.goc goc2c ...@@ -530,14 +518,6 @@ malloc.c: $(srcdir)/runtime/malloc.goc goc2c
./goc2c $< > $@.tmp ./goc2c $< > $@.tmp
mv -f $@.tmp $@ mv -f $@.tmp $@
mprof.c: $(srcdir)/runtime/mprof.goc goc2c
./goc2c $< > $@.tmp
mv -f $@.tmp $@
netpoll.c: $(srcdir)/runtime/netpoll.goc goc2c
./goc2c $< > $@.tmp
mv -f $@.tmp $@
reflect.c: $(srcdir)/runtime/reflect.goc goc2c reflect.c: $(srcdir)/runtime/reflect.goc goc2c
./goc2c $< > $@.tmp ./goc2c $< > $@.tmp
mv -f $@.tmp $@ mv -f $@.tmp $@
...@@ -546,18 +526,10 @@ runtime1.c: $(srcdir)/runtime/runtime1.goc goc2c ...@@ -546,18 +526,10 @@ runtime1.c: $(srcdir)/runtime/runtime1.goc goc2c
./goc2c $< > $@.tmp ./goc2c $< > $@.tmp
mv -f $@.tmp $@ mv -f $@.tmp $@
sema.c: $(srcdir)/runtime/sema.goc goc2c
./goc2c $< > $@.tmp
mv -f $@.tmp $@
sigqueue.c: $(srcdir)/runtime/sigqueue.goc goc2c sigqueue.c: $(srcdir)/runtime/sigqueue.goc goc2c
./goc2c --go-pkgpath os_signal $< > $@.tmp ./goc2c --go-pkgpath os_signal $< > $@.tmp
mv -f $@.tmp $@ mv -f $@.tmp $@
time.c: $(srcdir)/runtime/time.goc goc2c
./goc2c $< > $@.tmp
mv -f $@.tmp $@
%.c: $(srcdir)/runtime/%.goc goc2c %.c: $(srcdir)/runtime/%.goc goc2c
./goc2c $< > $@.tmp ./goc2c $< > $@.tmp
mv -f $@.tmp $@ mv -f $@.tmp $@
......
...@@ -186,6 +186,9 @@ ...@@ -186,6 +186,9 @@
/* Define to 1 if you have the `pipe2' function. */ /* Define to 1 if you have the `pipe2' function. */
#undef HAVE_PIPE2 #undef HAVE_PIPE2
/* Define to 1 if you have the <port.h> header file. */
#undef HAVE_PORT_H
/* Define to 1 if you have the `removexattr' function. */ /* Define to 1 if you have the `removexattr' function. */
#undef HAVE_REMOVEXATTR #undef HAVE_REMOVEXATTR
...@@ -259,6 +262,9 @@ ...@@ -259,6 +262,9 @@
/* Define to 1 if you have the <sys/epoll.h> header file. */ /* Define to 1 if you have the <sys/epoll.h> header file. */
#undef HAVE_SYS_EPOLL_H #undef HAVE_SYS_EPOLL_H
/* Define to 1 if you have the <sys/event.h> header file. */
#undef HAVE_SYS_EVENT_H
/* Define to 1 if you have the <sys/file.h> header file. */ /* Define to 1 if you have the <sys/file.h> header file. */
#undef HAVE_SYS_FILE_H #undef HAVE_SYS_FILE_H
......
...@@ -14714,7 +14714,7 @@ $as_echo "#define HAVE_GETIPINFO 1" >>confdefs.h ...@@ -14714,7 +14714,7 @@ $as_echo "#define HAVE_GETIPINFO 1" >>confdefs.h
fi fi
for ac_header in sched.h semaphore.h sys/file.h sys/mman.h syscall.h sys/epoll.h sys/inotify.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h net/if_arp.h net/route.h netpacket/packet.h sys/prctl.h sys/mount.h sys/vfs.h sys/statfs.h sys/timex.h sys/sysinfo.h utime.h linux/ether.h linux/fs.h linux/reboot.h netinet/icmp6.h netinet/in_syst.h netinet/ip.h netinet/ip_mroute.h netinet/if_ether.h for ac_header in port.h sched.h semaphore.h sys/file.h sys/mman.h syscall.h sys/epoll.h sys/event.h sys/inotify.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h net/if_arp.h net/route.h netpacket/packet.h sys/prctl.h sys/mount.h sys/vfs.h sys/statfs.h sys/timex.h sys/sysinfo.h utime.h linux/ether.h linux/fs.h linux/reboot.h netinet/icmp6.h netinet/in_syst.h netinet/ip.h netinet/ip_mroute.h netinet/if_ether.h
do : do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
......
...@@ -570,7 +570,7 @@ AC_C_BIGENDIAN ...@@ -570,7 +570,7 @@ AC_C_BIGENDIAN
GCC_CHECK_UNWIND_GETIPINFO GCC_CHECK_UNWIND_GETIPINFO
AC_CHECK_HEADERS(sched.h semaphore.h sys/file.h sys/mman.h syscall.h sys/epoll.h sys/inotify.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h net/if_arp.h net/route.h netpacket/packet.h sys/prctl.h sys/mount.h sys/vfs.h sys/statfs.h sys/timex.h sys/sysinfo.h utime.h linux/ether.h linux/fs.h linux/reboot.h netinet/icmp6.h netinet/in_syst.h netinet/ip.h netinet/ip_mroute.h netinet/if_ether.h) AC_CHECK_HEADERS(port.h sched.h semaphore.h sys/file.h sys/mman.h syscall.h sys/epoll.h sys/event.h sys/inotify.h sys/ptrace.h sys/syscall.h sys/user.h sys/utsname.h sys/select.h sys/socket.h net/if.h net/if_arp.h net/route.h netpacket/packet.h sys/prctl.h sys/mount.h sys/vfs.h sys/statfs.h sys/timex.h sys/sysinfo.h utime.h linux/ether.h linux/fs.h linux/reboot.h netinet/icmp6.h netinet/in_syst.h netinet/ip.h netinet/ip_mroute.h netinet/if_ether.h)
AC_CHECK_HEADERS([linux/filter.h linux/if_addr.h linux/if_ether.h linux/if_tun.h linux/netlink.h linux/rtnetlink.h], [], [], AC_CHECK_HEADERS([linux/filter.h linux/if_addr.h linux/if_ether.h linux/if_tun.h linux/netlink.h linux/rtnetlink.h], [], [],
[#ifdef HAVE_SYS_SOCKET_H [#ifdef HAVE_SYS_SOCKET_H
......
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package runtime
import "unsafe"
//extern epoll_create
func epollcreate(size int32) int32
//extern epoll_create1
func epollcreate1(flags int32) int32
//go:noescape
//extern epoll_ctl
func epollctl(epfd, op, fd int32, ev *epollevent) int32
//go:noescape
//extern epoll_wait
func epollwait(epfd int32, ev *epollevent, nev, timeout int32) int32
//extern __go_fcntl_uintptr
func fcntlUintptr(fd, cmd, arg uintptr) (uintptr, uintptr)
func closeonexec(fd int32) {
fcntlUintptr(uintptr(fd), _F_SETFD, _FD_CLOEXEC)
}
var (
epfd int32 = -1 // epoll descriptor
)
func netpollinit() {
epfd = epollcreate1(_EPOLL_CLOEXEC)
if epfd >= 0 {
return
}
epfd = epollcreate(1024)
if epfd >= 0 {
closeonexec(epfd)
return
}
println("netpollinit: failed to create epoll descriptor", errno())
throw("netpollinit: failed to create descriptor")
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
var ev epollevent
ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET
*(**pollDesc)(unsafe.Pointer(&ev.data)) = pd
if epollctl(epfd, _EPOLL_CTL_ADD, int32(fd), &ev) < 0 {
return int32(errno())
}
return 0
}
func netpollclose(fd uintptr) int32 {
var ev epollevent
if epollctl(epfd, _EPOLL_CTL_DEL, int32(fd), &ev) < 0 {
return int32(errno())
}
return 0
}
func netpollarm(pd *pollDesc, mode int) {
throw("unused")
}
// polls for ready network connections
// returns list of goroutines that become runnable
func netpoll(block bool) *g {
if epfd == -1 {
return nil
}
waitms := int32(-1)
if !block {
waitms = 0
}
var events [128]epollevent
retry:
n := epollwait(epfd, &events[0], int32(len(events)), waitms)
if n < 0 {
e := errno()
if e != _EINTR {
println("runtime: epollwait on fd", epfd, "failed with", e)
throw("epollwait failed")
}
goto retry
}
var gp guintptr
for i := int32(0); i < n; i++ {
ev := &events[i]
if ev.events == 0 {
continue
}
var mode int32
if ev.events&(_EPOLLIN|_EPOLLRDHUP|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'r'
}
if ev.events&(_EPOLLOUT|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'w'
}
if mode != 0 {
pd := *(**pollDesc)(unsafe.Pointer(&ev.data))
netpollready(&gp, pd, mode)
}
}
if block && gp == 0 {
goto retry
}
return gp.ptr()
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd netbsd openbsd
package runtime
// Integrated network poller (kqueue-based implementation).
import "unsafe"
//extern kqueue
func kqueue() int32
//go:noescape
//extern kevent
func kevent(kq int32, ch *keventt, nch uintptr, ev *keventt, nev uintptr, ts *timespec) int32
//extern __go_fcntl_uintptr
func fcntlUintptr(fd, cmd, arg uintptr) (uintptr, uintptr)
func closeonexec(fd int32) {
fcntlUintptr(uintptr(fd), _F_SETFD, _FD_CLOEXEC)
}
var (
kq int32 = -1
)
func netpollinit() {
kq = kqueue()
if kq < 0 {
println("netpollinit: kqueue failed with", errno())
throw("netpollinit: kqueue failed")
}
closeonexec(kq)
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
// Arm both EVFILT_READ and EVFILT_WRITE in edge-triggered mode (EV_CLEAR)
// for the whole fd lifetime. The notifications are automatically unregistered
// when fd is closed.
var ev [2]keventt
*(*uintptr)(unsafe.Pointer(&ev[0].ident)) = fd
ev[0].filter = _EVFILT_READ
ev[0].flags = _EV_ADD | _EV_CLEAR
ev[0].fflags = 0
ev[0].data = 0
ev[0].udata = (*byte)(unsafe.Pointer(pd))
ev[1] = ev[0]
ev[1].filter = _EVFILT_WRITE
n := kevent(kq, &ev[0], 2, nil, 0, nil)
if n < 0 {
return int32(errno())
}
return 0
}
func netpollclose(fd uintptr) int32 {
// Don't need to unregister because calling close()
// on fd will remove any kevents that reference the descriptor.
return 0
}
func netpollarm(pd *pollDesc, mode int) {
throw("unused")
}
// Polls for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(block bool) *g {
if kq == -1 {
return nil
}
var tp *timespec
var ts timespec
if !block {
tp = &ts
}
var events [64]keventt
retry:
n := kevent(kq, nil, 0, &events[0], uintptr(len(events)), tp)
if n < 0 {
e := errno()
if e != _EINTR {
println("runtime: kevent on fd", kq, "failed with", e)
throw("kevent failed")
}
goto retry
}
var gp guintptr
for i := 0; i < int(n); i++ {
ev := &events[i]
var mode int32
if ev.filter == _EVFILT_READ {
mode += 'r'
}
if ev.filter == _EVFILT_WRITE {
mode += 'w'
}
if mode != 0 {
netpollready(&gp, (*pollDesc)(unsafe.Pointer(ev.udata)), mode)
}
}
if block && gp == 0 {
goto retry
}
return gp.ptr()
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Fake network poller for NaCl.
// Should never be used, because NaCl network connections do not honor "SetNonblock".
package runtime
func netpollinit() {
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
return 0
}
func netpollclose(fd uintptr) int32 {
return 0
}
func netpollarm(pd *pollDesc, mode int) {
}
func netpoll(block bool) *g {
return nil
}
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import "unsafe"
// Solaris runtime-integrated network poller.
//
// Solaris uses event ports for scalable network I/O. Event
// ports are level-triggered, unlike epoll and kqueue which
// can be configured in both level-triggered and edge-triggered
// mode. Level triggering means we have to keep track of a few things
// ourselves. After we receive an event for a file descriptor,
// it's our responsibility to ask again to be notified for future
// events for that descriptor. When doing this we must keep track of
// what kind of events the goroutines are currently interested in,
// for example a fd may be open both for reading and writing.
//
// A description of the high level operation of this code
// follows. Networking code will get a file descriptor by some means
// and will register it with the netpolling mechanism by a code path
// that eventually calls runtime·netpollopen. runtime·netpollopen
// calls port_associate with an empty event set. That means that we
// will not receive any events at this point. The association needs
// to be done at this early point because we need to process the I/O
// readiness notification at some point in the future. If I/O becomes
// ready when nobody is listening, when we finally care about it,
// nobody will tell us anymore.
//
// Beside calling runtime·netpollopen, the networking code paths
// will call runtime·netpollarm each time goroutines are interested
// in doing network I/O. Because now we know what kind of I/O we
// are interested in (reading/writing), we can call port_associate
// passing the correct type of event set (POLLIN/POLLOUT). As we made
// sure to have already associated the file descriptor with the port,
// when we now call port_associate, we will unblock the main poller
// loop (in runtime·netpoll) right away if the socket is actually
// ready for I/O.
//
// The main poller loop runs in its own thread waiting for events
// using port_getn. When an event happens, it will tell the scheduler
// about it using runtime·netpollready. Besides doing this, it must
// also re-associate the events that were not part of this current
// notification with the file descriptor. Failing to do this would
// mean each notification will prevent concurrent code using the
// same file descriptor in parallel.
//
// The logic dealing with re-associations is encapsulated in
// runtime·netpollupdate. This function takes care to associate the
// descriptor only with the subset of events that were previously
// part of the association, except the one that just happened. We
// can't re-associate with that right away, because event ports
// are level triggered so it would cause a busy loop. Instead, that
// association is effected only by the runtime·netpollarm code path,
// when Go code actually asks for I/O.
//
// The open and arming mechanisms are serialized using the lock
// inside PollDesc. This is required because the netpoll loop runs
// asynchronously in respect to other Go code and by the time we get
// to call port_associate to update the association in the loop, the
// file descriptor might have been closed and reopened already. The
// lock allows runtime·netpollupdate to be called synchronously from
// the loop thread while preventing other threads operating to the
// same PollDesc, so once we unblock in the main loop, until we loop
// again we know for sure we are always talking about the same file
// descriptor and can safely access the data we want (the event set).
//extern __go_fcntl_uintptr
func fcntlUintptr(fd, cmd, arg uintptr) (uintptr, uintptr)
func fcntl(fd, cmd int32, arg uintptr) int32 {
r, _ := fcntlUintptr(uintptr(fd), uintptr(cmd), arg)
return int32(r)
}
//extern port_create
func port_create() int32
//extern port_associate
func port_associate(port, source int32, object uintptr, events uint32, user uintptr) int32
//extern port_dissociate
func port_dissociate(port, source int32, object uintptr) int32
//extern port_getn
func port_getn(port int32, evs *portevent, max uint32, nget *uint32, timeout *timespec) int32
var portfd int32 = -1
func netpollinit() {
portfd = port_create()
if portfd >= 0 {
fcntl(portfd, _F_SETFD, _FD_CLOEXEC)
return
}
print("netpollinit: failed to create port (", errno(), ")\n")
throw("netpollinit: failed to create port")
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
lock(&pd.lock)
// We don't register for any specific type of events yet, that's
// netpollarm's job. We merely ensure we call port_associate before
// asynchronous connect/accept completes, so when we actually want
// to do any I/O, the call to port_associate (from netpollarm,
// with the interested event set) will unblock port_getn right away
// because of the I/O readiness notification.
pd.user = 0
r := port_associate(portfd, _PORT_SOURCE_FD, fd, 0, uintptr(unsafe.Pointer(pd)))
unlock(&pd.lock)
if r < 0 {
return int32(errno())
}
return 0
}
func netpollclose(fd uintptr) int32 {
if port_dissociate(portfd, _PORT_SOURCE_FD, fd) < 0 {
return int32(errno())
}
return 0
}
// Updates the association with a new set of interested events. After
// this call, port_getn will return one and only one event for that
// particular descriptor, so this function needs to be called again.
func netpollupdate(pd *pollDesc, set, clear uint32) {
if pd.closing {
return
}
old := pd.user
events := (old & ^clear) | set
if old == events {
return
}
if events != 0 && port_associate(portfd, _PORT_SOURCE_FD, pd.fd, events, uintptr(unsafe.Pointer(pd))) != 0 {
print("netpollupdate: failed to associate (", errno(), ")\n")
throw("netpollupdate: failed to associate")
}
pd.user = events
}
// subscribe the fd to the port such that port_getn will return one event.
func netpollarm(pd *pollDesc, mode int) {
lock(&pd.lock)
switch mode {
case 'r':
netpollupdate(pd, _POLLIN, 0)
case 'w':
netpollupdate(pd, _POLLOUT, 0)
default:
throw("netpollarm: bad mode")
}
unlock(&pd.lock)
}
// polls for ready network connections
// returns list of goroutines that become runnable
func netpoll(block bool) *g {
if portfd == -1 {
return nil
}
var wait *timespec
var zero timespec
if !block {
wait = &zero
}
var events [128]portevent
retry:
var n uint32 = 1
if port_getn(portfd, &events[0], uint32(len(events)), &n, wait) < 0 {
if e := errno(); e != _EINTR {
print("runtime: port_getn on fd ", portfd, " failed with ", e, "\n")
throw("port_getn failed")
}
goto retry
}
var gp guintptr
for i := 0; i < int(n); i++ {
ev := &events[i]
if ev.portev_events == 0 {
continue
}
pd := (*pollDesc)(unsafe.Pointer(ev.portev_user))
var mode, clear int32
if (ev.portev_events & (_POLLIN | _POLLHUP | _POLLERR)) != 0 {
mode += 'r'
clear |= _POLLIN
}
if (ev.portev_events & (_POLLOUT | _POLLHUP | _POLLERR)) != 0 {
mode += 'w'
clear |= _POLLOUT
}
// To effect edge-triggered events, we need to be sure to
// update our association with whatever events were not
// set with the event. For example if we are registered
// for POLLIN|POLLOUT, and we get POLLIN, besides waking
// the goroutine interested in POLLIN we have to not forget
// about the one interested in POLLOUT.
if clear != 0 {
lock(&pd.lock)
netpollupdate(pd, 0, uint32(clear))
unlock(&pd.lock)
}
if mode != 0 {
netpollready(&gp, pd, mode)
}
}
if block && gp == 0 {
goto retry
}
return gp.ptr()
}
...@@ -4,23 +4,16 @@ ...@@ -4,23 +4,16 @@
// +build plan9 // +build plan9
#include "runtime.h" package runtime
#include "malloc.h"
// Polls for ready network connections. // Polls for ready network connections.
// Returns list of goroutines that become runnable. // Returns list of goroutines that become runnable.
G* func netpoll(block bool) (gp *g) {
runtime_netpoll(bool block)
{
// Implementation for platforms that do not support // Implementation for platforms that do not support
// integrated network poller. // integrated network poller.
USED(block); return
return nil;
} }
void func netpollinited() bool {
runtime_netpoll_scan(struct Workbuf** wbufp, void (*enqueue1)(struct Workbuf**, Obj)) return false
{
USED(wbufp);
USED(addroot);
} }
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import (
"unsafe"
)
const _DWORD_MAX = 0xffffffff
const _INVALID_HANDLE_VALUE = ^uintptr(0)
// net_op must be the same as beginning of net.operation. Keep these in sync.
type net_op struct {
// used by windows
o overlapped
// used by netpoll
pd *pollDesc
mode int32
errno int32
qty uint32
}
type overlappedEntry struct {
key uintptr
op *net_op // In reality it's *overlapped, but we cast it to *net_op anyway.
internal uintptr
qty uint32
}
var iocphandle uintptr = _INVALID_HANDLE_VALUE // completion port io handle
func netpollinit() {
iocphandle = stdcall4(_CreateIoCompletionPort, _INVALID_HANDLE_VALUE, 0, 0, _DWORD_MAX)
if iocphandle == 0 {
println("netpoll: failed to create iocp handle (errno=", getlasterror(), ")")
throw("netpoll: failed to create iocp handle")
}
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
if stdcall4(_CreateIoCompletionPort, fd, iocphandle, 0, 0) == 0 {
return -int32(getlasterror())
}
return 0
}
func netpollclose(fd uintptr) int32 {
// nothing to do
return 0
}
func netpollarm(pd *pollDesc, mode int) {
throw("unused")
}
// Polls for completed network IO.
// Returns list of goroutines that become runnable.
func netpoll(block bool) *g {
var entries [64]overlappedEntry
var wait, qty, key, flags, n, i uint32
var errno int32
var op *net_op
var gp guintptr
mp := getg().m
if iocphandle == _INVALID_HANDLE_VALUE {
return nil
}
wait = 0
if block {
wait = _INFINITE
}
retry:
if _GetQueuedCompletionStatusEx != nil {
n = uint32(len(entries) / int(gomaxprocs))
if n < 8 {
n = 8
}
if block {
mp.blocked = true
}
if stdcall6(_GetQueuedCompletionStatusEx, iocphandle, uintptr(unsafe.Pointer(&entries[0])), uintptr(n), uintptr(unsafe.Pointer(&n)), uintptr(wait), 0) == 0 {
mp.blocked = false
errno = int32(getlasterror())
if !block && errno == _WAIT_TIMEOUT {
return nil
}
println("netpoll: GetQueuedCompletionStatusEx failed (errno=", errno, ")")
throw("netpoll: GetQueuedCompletionStatusEx failed")
}
mp.blocked = false
for i = 0; i < n; i++ {
op = entries[i].op
errno = 0
qty = 0
if stdcall5(_WSAGetOverlappedResult, op.pd.fd, uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(&qty)), 0, uintptr(unsafe.Pointer(&flags))) == 0 {
errno = int32(getlasterror())
}
handlecompletion(&gp, op, errno, qty)
}
} else {
op = nil
errno = 0
qty = 0
if block {
mp.blocked = true
}
if stdcall5(_GetQueuedCompletionStatus, iocphandle, uintptr(unsafe.Pointer(&qty)), uintptr(unsafe.Pointer(&key)), uintptr(unsafe.Pointer(&op)), uintptr(wait)) == 0 {
mp.blocked = false
errno = int32(getlasterror())
if !block && errno == _WAIT_TIMEOUT {
return nil
}
if op == nil {
println("netpoll: GetQueuedCompletionStatus failed (errno=", errno, ")")
throw("netpoll: GetQueuedCompletionStatus failed")
}
// dequeued failed IO packet, so report that
}
mp.blocked = false
handlecompletion(&gp, op, errno, qty)
}
if block && gp == 0 {
goto retry
}
return gp.ptr()
}
func handlecompletion(gpp *guintptr, op *net_op, errno int32, qty uint32) {
if op == nil {
throw("netpoll: GetQueuedCompletionStatus returned op == nil")
}
mode := op.mode
if mode != 'r' && mode != 'w' {
println("netpoll: GetQueuedCompletionStatus returned invalid mode=", mode)
throw("netpoll: GetQueuedCompletionStatus returned invalid mode")
}
op.errno = errno
op.qty = qty
netpollready(gpp, op.pd, mode)
}
...@@ -296,7 +296,7 @@ func casp(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool { ...@@ -296,7 +296,7 @@ func casp(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool {
func lock(l *mutex) func lock(l *mutex)
func unlock(l *mutex) func unlock(l *mutex)
// Here for gccgo for Solaris. // Here for gccgo for netpoll and Solaris.
func errno() int func errno() int
// Temporary for gccgo until we port proc.go. // Temporary for gccgo until we port proc.go.
...@@ -460,3 +460,9 @@ func setmaxthreads(int) int ...@@ -460,3 +460,9 @@ func setmaxthreads(int) int
func setMaxThreads(in int) (out int) { func setMaxThreads(in int) (out int) {
return setmaxthreads(in) return setmaxthreads(in)
} }
// Temporary for gccgo until we port atomic_pointer.go.
//go:nosplit
func atomicstorep(ptr unsafe.Pointer, new unsafe.Pointer) {
atomic.StorepNoWB(noescape(ptr), new)
}
...@@ -8,10 +8,6 @@ package runtime ...@@ -8,10 +8,6 @@ package runtime
import "unsafe" import "unsafe"
// Export temporarily for gccgo's C code to call:
//go:linkname addtimer runtime.addtimer
//go:linkname deltimer runtime.deltimer
// Package time knows the layout of this structure. // Package time knows the layout of this structure.
// If this struct changes, adjust ../time/sleep.go:/runtimeTimer. // If this struct changes, adjust ../time/sleep.go:/runtimeTimer.
// For GOOS=nacl, package syscall knows the layout of this structure. // For GOOS=nacl, package syscall knows the layout of this structure.
......
...@@ -64,6 +64,27 @@ echo "func (ts *timespec) set_nsec(x int32) {" >> ${OUT} ...@@ -64,6 +64,27 @@ echo "func (ts *timespec) set_nsec(x int32) {" >> ${OUT}
echo " ts.tv_nsec = timespec_nsec_t(x)" >> ${OUT} echo " ts.tv_nsec = timespec_nsec_t(x)" >> ${OUT}
echo "}" >> ${OUT} echo "}" >> ${OUT}
# Define the epollevent struct. This needs special attention because
# the C definition uses a union and is sometimes packed.
if grep '^const _epoll_data_offset ' ${OUT} >/dev/null 2>&1; then
val=`grep '^const _epoll_data_offset ' ${OUT} | sed -e 's/const _epoll_data_offset = \(.*\)$/\1/'`
if test "$val" = "4"; then
echo 'type epollevent struct { events uint32; data [8]byte }' >> ${OUT}
elif test "$val" = "8"; then
echo 'type epollevent struct { events uint32; pad [4]byte; data [8]byte }' >> ${OUT}
else
echo 1>&2 "unknown epoll data offset value ${val}"
exit 1
fi
fi
# Make sure EPOLLRDHUP and EPOLL_CLOEXEC are defined.
if ! grep '^const _EPOLLRDHUP' ${OUT} >/dev/null 2>&1; then
echo "const _EPOLLRDHUP = 0x2000" >> ${OUT}
fi
if ! grep '^const _EPOLL_CLOEXEC' ${OUT} >/dev/null 2>&1; then
echo "const _EPOLL_CLOEXEC = 02000000" >> ${OUT}
fi
# The semt structure, for Solaris. # The semt structure, for Solaris.
grep '^type _sem_t ' gen-sysinfo.go | \ grep '^type _sem_t ' gen-sysinfo.go | \
sed -e 's/_sem_t/semt/' >> ${OUT} sed -e 's/_sem_t/semt/' >> ${OUT}
...@@ -101,3 +122,14 @@ grep '^type _mac_ipaddr_t ' gen-sysinfo.go | \ ...@@ -101,3 +122,14 @@ grep '^type _mac_ipaddr_t ' gen-sysinfo.go | \
grep '^type _mactun_info_t ' gen-sysinfo.go | \ grep '^type _mactun_info_t ' gen-sysinfo.go | \
sed -e 's/_in6_addr_t/[16]byte/g' \ sed -e 's/_in6_addr_t/[16]byte/g' \
>> ${OUT} >> ${OUT}
# The Solaris port_event_t struct.
grep '^type _port_event_t ' gen-sysinfo.go | \
sed -e s'/_port_event_t/portevent/' \
>> ${OUT}
# The *BSD kevent struct.
grep '^type _kevent ' gen-sysinfo.go | \
sed -e s'/_kevent/keventt/' \
-e 's/ udata [^;}]*/ udata *byte/' \
>> ${OUT}
...@@ -544,4 +544,3 @@ int32 runtime_setgcpercent(int32) ...@@ -544,4 +544,3 @@ int32 runtime_setgcpercent(int32)
struct Workbuf; struct Workbuf;
void runtime_proc_scan(struct Workbuf**, void (*)(struct Workbuf**, Obj)); void runtime_proc_scan(struct Workbuf**, void (*)(struct Workbuf**, Obj));
void runtime_netpoll_scan(struct Workbuf**, void (*)(struct Workbuf**, Obj));
...@@ -1277,7 +1277,6 @@ markroot(ParFor *desc, uint32 i) ...@@ -1277,7 +1277,6 @@ markroot(ParFor *desc, uint32 i)
enqueue1(&wbuf, (Obj){(byte*)&runtime_allp, sizeof runtime_allp, 0}); enqueue1(&wbuf, (Obj){(byte*)&runtime_allp, sizeof runtime_allp, 0});
enqueue1(&wbuf, (Obj){(byte*)&work, sizeof work, 0}); enqueue1(&wbuf, (Obj){(byte*)&work, sizeof work, 0});
runtime_proc_scan(&wbuf, enqueue1); runtime_proc_scan(&wbuf, enqueue1);
runtime_netpoll_scan(&wbuf, enqueue1);
break; break;
case RootFinalizers: case RootFinalizers:
......
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include "runtime.h"
#include "defs.h"
#include "malloc.h"
#ifndef EPOLLRDHUP
#define EPOLLRDHUP 0x2000
#endif
#ifndef EPOLL_CLOEXEC
#define EPOLL_CLOEXEC 02000000
#endif
#ifndef HAVE_EPOLL_CREATE1
extern int epoll_create1(int __flags);
#endif
typedef struct epoll_event EpollEvent;
static int32
runtime_epollcreate(int32 size)
{
int r;
r = epoll_create(size);
if(r >= 0)
return r;
return - errno;
}
static int32
runtime_epollcreate1(int32 flags)
{
int r;
r = epoll_create1(flags);
if(r >= 0)
return r;
return - errno;
}
static int32
runtime_epollctl(int32 epfd, int32 op, int32 fd, EpollEvent *ev)
{
int r;
r = epoll_ctl(epfd, op, fd, ev);
if(r >= 0)
return r;
return - errno;
}
static int32
runtime_epollwait(int32 epfd, EpollEvent *ev, int32 nev, int32 timeout)
{
int r;
r = epoll_wait(epfd, ev, nev, timeout);
if(r >= 0)
return r;
return - errno;
}
static void
runtime_closeonexec(int32 fd)
{
fcntl(fd, F_SETFD, FD_CLOEXEC);
}
static int32 epfd = -1; // epoll descriptor
void
runtime_netpollinit(void)
{
epfd = runtime_epollcreate1(EPOLL_CLOEXEC);
if(epfd >= 0)
return;
epfd = runtime_epollcreate(1024);
if(epfd >= 0) {
runtime_closeonexec(epfd);
return;
}
runtime_printf("netpollinit: failed to create descriptor (%d)\n", -epfd);
runtime_throw("netpollinit: failed to create descriptor");
}
int32
runtime_netpollopen(uintptr fd, PollDesc *pd)
{
EpollEvent ev;
int32 res;
ev.events = EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET;
ev.data.ptr = (void*)pd;
res = runtime_epollctl(epfd, EPOLL_CTL_ADD, (int32)fd, &ev);
return -res;
}
int32
runtime_netpollclose(uintptr fd)
{
EpollEvent ev;
int32 res;
res = runtime_epollctl(epfd, EPOLL_CTL_DEL, (int32)fd, &ev);
return -res;
}
void
runtime_netpollarm(PollDesc* pd, int32 mode)
{
USED(pd);
USED(mode);
runtime_throw("unused");
}
// polls for ready network connections
// returns list of goroutines that become runnable
G*
runtime_netpoll(bool block)
{
static int32 lasterr;
EpollEvent events[128], *ev;
int32 n, i, waitms, mode;
G *gp;
if(epfd == -1)
return nil;
waitms = -1;
if(!block)
waitms = 0;
retry:
n = runtime_epollwait(epfd, events, nelem(events), waitms);
if(n < 0) {
if(n != -EINTR && n != lasterr) {
lasterr = n;
runtime_printf("runtime: epollwait on fd %d failed with %d\n", epfd, -n);
}
goto retry;
}
gp = nil;
for(i = 0; i < n; i++) {
ev = &events[i];
if(ev->events == 0)
continue;
mode = 0;
if(ev->events & (EPOLLIN|EPOLLRDHUP|EPOLLHUP|EPOLLERR))
mode += 'r';
if(ev->events & (EPOLLOUT|EPOLLHUP|EPOLLERR))
mode += 'w';
if(mode)
runtime_netpollready(&gp, (void*)ev->data.ptr, mode);
}
if(block && gp == nil)
goto retry;
return gp;
}
void
runtime_netpoll_scan(struct Workbuf** wbufp, void (*enqueue1)(struct Workbuf**, Obj))
{
USED(wbufp);
USED(enqueue1);
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd netbsd openbsd
#include "runtime.h"
#include "defs.h"
#include "malloc.h"
// Integrated network poller (kqueue-based implementation).
int32 runtime_kqueue(void);
int32 runtime_kevent(int32, Kevent*, int32, Kevent*, int32, Timespec*);
void runtime_closeonexec(int32);
static int32 kq = -1;
void
runtime_netpollinit(void)
{
kq = runtime_kqueue();
if(kq < 0) {
runtime_printf("netpollinit: kqueue failed with %d\n", -kq);
runtime_throw("netpollinit: kqueue failed");
}
runtime_closeonexec(kq);
}
int32
runtime_netpollopen(uintptr fd, PollDesc *pd)
{
Kevent ev[2];
int32 n;
// Arm both EVFILT_READ and EVFILT_WRITE in edge-triggered mode (EV_CLEAR)
// for the whole fd lifetime. The notifications are automatically unregistered
// when fd is closed.
ev[0].ident = (uint32)fd;
ev[0].filter = EVFILT_READ;
ev[0].flags = EV_ADD|EV_CLEAR;
ev[0].fflags = 0;
ev[0].data = 0;
ev[0].udata = (kevent_udata)pd;
ev[1] = ev[0];
ev[1].filter = EVFILT_WRITE;
n = runtime_kevent(kq, ev, 2, nil, 0, nil);
if(n < 0)
return -n;
return 0;
}
int32
runtime_netpollclose(uintptr fd)
{
// Don't need to unregister because calling close()
// on fd will remove any kevents that reference the descriptor.
USED(fd);
return 0;
}
void
runtime_netpollarm(PollDesc* pd, int32 mode)
{
USED(pd, mode);
runtime_throw("unused");
}
// Polls for ready network connections.
// Returns list of goroutines that become runnable.
G*
runtime_netpoll(bool block)
{
static int32 lasterr;
Kevent events[64], *ev;
Timespec ts, *tp;
int32 n, i, mode;
G *gp;
if(kq == -1)
return nil;
tp = nil;
if(!block) {
ts.tv_sec = 0;
ts.tv_nsec = 0;
tp = &ts;
}
gp = nil;
retry:
n = runtime_kevent(kq, nil, 0, events, nelem(events), tp);
if(n < 0) {
if(n != -EINTR && n != lasterr) {
lasterr = n;
runtime_printf("runtime: kevent on fd %d failed with %d\n", kq, -n);
}
goto retry;
}
for(i = 0; i < n; i++) {
ev = &events[i];
mode = 0;
if(ev->filter == EVFILT_READ)
mode += 'r';
if(ev->filter == EVFILT_WRITE)
mode += 'w';
if(mode)
runtime_netpollready(&gp, (PollDesc*)ev->udata, mode);
}
if(block && gp == nil)
goto retry;
return gp;
}
void
runtime_netpoll_scan(struct Workbuf** wbufp, void (*enqueue1)(struct Workbuf**, Obj))
{
USED(wbufp);
USED(enqueue1);
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build solaris
#include "config.h"
#include <errno.h>
#include <sys/times.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include "runtime.h"
#include "malloc.h"
static Lock selectlock;
static int rdwake;
static int wrwake;
static fd_set fds;
static PollDesc **data;
static int allocated;
void
runtime_netpollinit(void)
{
int p[2];
int fl;
FD_ZERO(&fds);
allocated = 128;
data = runtime_mallocgc(allocated * sizeof(PollDesc *), 0,
FlagNoScan|FlagNoProfiling|FlagNoInvokeGC);
if(pipe(p) < 0)
runtime_throw("netpollinit: failed to create pipe");
rdwake = p[0];
wrwake = p[1];
fl = fcntl(rdwake, F_GETFL);
if(fl < 0)
runtime_throw("netpollinit: fcntl failed");
fl |= O_NONBLOCK;
if(fcntl(rdwake, F_SETFL, fl))
runtime_throw("netpollinit: fcntl failed");
fcntl(rdwake, F_SETFD, FD_CLOEXEC);
fl = fcntl(wrwake, F_GETFL);
if(fl < 0)
runtime_throw("netpollinit: fcntl failed");
fl |= O_NONBLOCK;
if(fcntl(wrwake, F_SETFL, fl))
runtime_throw("netpollinit: fcntl failed");
fcntl(wrwake, F_SETFD, FD_CLOEXEC);
FD_SET(rdwake, &fds);
}
int32
runtime_netpollopen(uintptr fd, PollDesc *pd)
{
byte b;
runtime_lock(&selectlock);
if((int)fd >= allocated) {
int c;
PollDesc **n;
c = allocated;
runtime_unlock(&selectlock);
while((int)fd >= c)
c *= 2;
n = runtime_mallocgc(c * sizeof(PollDesc *), 0,
FlagNoScan|FlagNoProfiling|FlagNoInvokeGC);
runtime_lock(&selectlock);
if(c > allocated) {
__builtin_memcpy(n, data, allocated * sizeof(PollDesc *));
allocated = c;
data = n;
}
}
FD_SET(fd, &fds);
data[fd] = pd;
runtime_unlock(&selectlock);
b = 0;
write(wrwake, &b, sizeof b);
return 0;
}
int32
runtime_netpollclose(uintptr fd)
{
byte b;
runtime_lock(&selectlock);
FD_CLR(fd, &fds);
data[fd] = nil;
runtime_unlock(&selectlock);
b = 0;
write(wrwake, &b, sizeof b);
return 0;
}
/* Used to avoid using too much stack memory. */
static bool inuse;
static fd_set grfds, gwfds, gefds, gtfds;
G*
runtime_netpoll(bool block)
{
fd_set *prfds, *pwfds, *pefds, *ptfds;
bool allocatedfds;
struct timeval timeout;
struct timeval *pt;
int max, c, i;
G *gp;
int32 mode;
byte b;
struct stat st;
allocatedfds = false;
retry:
runtime_lock(&selectlock);
max = allocated;
if(max == 0) {
runtime_unlock(&selectlock);
return nil;
}
if(inuse) {
if(!allocatedfds) {
prfds = runtime_SysAlloc(4 * sizeof fds, &mstats()->other_sys);
pwfds = prfds + 1;
pefds = pwfds + 1;
ptfds = pefds + 1;
allocatedfds = true;
}
} else {
prfds = &grfds;
pwfds = &gwfds;
pefds = &gefds;
ptfds = &gtfds;
inuse = true;
allocatedfds = false;
}
__builtin_memcpy(prfds, &fds, sizeof fds);
runtime_unlock(&selectlock);
__builtin_memcpy(pwfds, prfds, sizeof fds);
FD_CLR(rdwake, pwfds);
__builtin_memcpy(pefds, pwfds, sizeof fds);
__builtin_memcpy(ptfds, pwfds, sizeof fds);
__builtin_memset(&timeout, 0, sizeof timeout);
pt = &timeout;
if(block)
pt = nil;
c = select(max, prfds, pwfds, pefds, pt);
if(c < 0) {
if(errno == EBADF) {
// Some file descriptor has been closed.
// Check each one, and treat each closed
// descriptor as ready for read/write.
c = 0;
FD_ZERO(prfds);
FD_ZERO(pwfds);
FD_ZERO(pefds);
for(i = 0; i < max; i++) {
if(FD_ISSET(i, ptfds)
&& fstat(i, &st) < 0
&& errno == EBADF) {
FD_SET(i, prfds);
FD_SET(i, pwfds);
c += 2;
}
}
}
else {
if(errno != EINTR)
runtime_printf("runtime: select failed with %d\n", errno);
goto retry;
}
}
gp = nil;
for(i = 0; i < max && c > 0; i++) {
mode = 0;
if(FD_ISSET(i, prfds)) {
mode += 'r';
--c;
}
if(FD_ISSET(i, pwfds)) {
mode += 'w';
--c;
}
if(FD_ISSET(i, pefds)) {
mode = 'r' + 'w';
--c;
}
if(i == rdwake && mode != 0) {
while(read(rdwake, &b, sizeof b) > 0)
;
continue;
}
if(mode) {
PollDesc *pd;
runtime_lock(&selectlock);
pd = data[i];
runtime_unlock(&selectlock);
if(pd != nil)
runtime_netpollready(&gp, pd, mode);
}
}
if(block && gp == nil)
goto retry;
if(allocatedfds) {
runtime_SysFree(prfds, 4 * sizeof fds, &mstats()->other_sys);
} else {
runtime_lock(&selectlock);
inuse = false;
runtime_unlock(&selectlock);
}
return gp;
}
void
runtime_netpoll_scan(struct Workbuf** wbufp, void (*enqueue1)(struct Workbuf**, Obj))
{
enqueue1(wbufp, (Obj){(byte*)&data, sizeof data, 0});
}
...@@ -391,21 +391,8 @@ int64 runtime_tickspersecond(void) ...@@ -391,21 +391,8 @@ int64 runtime_tickspersecond(void)
__asm__ (GOSYM_PREFIX "runtime.tickspersecond"); __asm__ (GOSYM_PREFIX "runtime.tickspersecond");
void runtime_blockevent(int64, int32); void runtime_blockevent(int64, int32);
extern int64 runtime_blockprofilerate; extern int64 runtime_blockprofilerate;
void runtime_addtimer(Timer*) G* runtime_netpoll(bool)
__asm__ (GOSYM_PREFIX "runtime.addtimer"); __asm__ (GOSYM_PREFIX "runtime.netpoll");
bool runtime_deltimer(Timer*)
__asm__ (GOSYM_PREFIX "runtime.deltimer");
G* runtime_netpoll(bool);
void runtime_netpollinit(void);
int32 runtime_netpollopen(uintptr, PollDesc*);
int32 runtime_netpollclose(uintptr);
void runtime_netpollready(G**, PollDesc*, int32);
uintptr runtime_netpollfd(PollDesc*);
void runtime_netpollarm(PollDesc*, int32);
void** runtime_netpolluser(PollDesc*);
bool runtime_netpollclosing(PollDesc*);
void runtime_netpolllock(PollDesc*);
void runtime_netpollunlock(PollDesc*);
void runtime_crash(void); void runtime_crash(void);
void runtime_parsedebugvars(void) void runtime_parsedebugvars(void)
__asm__(GOSYM_PREFIX "runtime.parsedebugvars"); __asm__(GOSYM_PREFIX "runtime.parsedebugvars");
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "config.h" #include "config.h"
#include <stddef.h>
#include <sys/types.h> #include <sys/types.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
...@@ -49,6 +50,9 @@ ...@@ -49,6 +50,9 @@
#if defined(HAVE_SYS_EPOLL_H) #if defined(HAVE_SYS_EPOLL_H)
#include <sys/epoll.h> #include <sys/epoll.h>
#endif #endif
#if defined(HAVE_SYS_EVENT_H)
#include <sys/event.h>
#endif
#if defined(HAVE_SYS_FILE_H) #if defined(HAVE_SYS_FILE_H)
#include <sys/file.h> #include <sys/file.h>
#endif #endif
...@@ -155,6 +159,9 @@ ...@@ -155,6 +159,9 @@
#if defined(HAVE_SEMAPHORE_H) #if defined(HAVE_SEMAPHORE_H)
#include <semaphore.h> #include <semaphore.h>
#endif #endif
#if defined(HAVE_PORT_H)
#include <port.h>
#endif
/* Constants that may only be defined as expressions on some systems, /* Constants that may only be defined as expressions on some systems,
expressions too complex for -fdump-go-spec to handle. These are expressions too complex for -fdump-go-spec to handle. These are
...@@ -260,3 +267,9 @@ enum { ...@@ -260,3 +267,9 @@ enum {
NLA_HDRLEN_val = NLA_HDRLEN, NLA_HDRLEN_val = NLA_HDRLEN,
#endif #endif
}; };
#if defined(HAVE_SYS_EPOLL_H)
enum {
epoll_data_offset = offsetof(struct epoll_event, data)
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment