New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x/sys/unix: EpollEvent use void *ptr instead of int fd #45540
Comments
This was previously proposed in #32192, which was merged into #32153. The decision there was to not change As per #32192 (comment) having an @pierre-emmanuelJ Could you elaborate a bit on your use case (e.g. with an example using your |
Would it make sense to expose the That could be pretty straightforward to use in conjunction with |
Yes, the new |
A sample of the event package package epoll
const (
// MaxEvents represents the max events for an epoll instance.
MaxEvents = 32
)
type Event struct {
Fd int32
send bool
Cookie interface{}
}
// Send to the handler HandlerOut
func (e *Event) Send() {
e.send = true
}
// Receive to the handler HandlerIn
func (e *Event) Receive() {
e.send = false
}
// Handlers represents all handler for incoming events.
type Handlers struct {
HandlerIn func(*Event) bool
HandlerOut func(*Event) bool
HandlerErr func(*Event)
HandlerNewConnection func(socket int) (int, interface{}, error)
}
// Epoll represents an epoll instance.
type Epoll struct {
ev *epollEvent
epollfd int
fd int
handlers Handlers
events [MaxEvents]epollEvent
}
// New returns an epoll instance.
func New(fd int, h Handlers) (*Epoll, error) {
ptr := C.malloc(C.ulong(unsafe.Sizeof(Event{})))
(*Event)(ptr).Fd = int32(fd)
(*Event)(ptr).send = false
(*Event)(ptr).Cookie = nil
ev := &epollEvent{
Ptr: ptr,
}
epfd, err := unix.EpollCreate1(0)
if err != nil {
return nil, err
}
ev.Events = unix.EPOLLIN
if err := epollCtl(epfd, unix.EPOLL_CTL_ADD, fd, ev); err != nil {
return nil, err
}
return &Epoll{
ev: ev,
epollfd: epfd,
fd: fd,
handlers: h,
}, nil
}
// Close epoll File Descriptor.
func (e *Epoll) Close() {
unix.Close(e.epollfd)
}
func (e *Epoll) Wait() {
for {
nfds, err := e.wait()
if err != nil {
log.Fatal(err)
}
for i := 0; i < int(nfds); i++ {
if e.incoming(i) {
fd, data, err := e.handlers.HandlerNewConnection(e.fd)
if err != nil {
log.Println(err)
continue
}
if err := e.add(fd, data); err != nil {
log.Fatal(err)
}
continue
}
if err := e.executeHandlers(&e.events[i]); err != nil {
log.Fatal(err)
}
}
}
}
func (e *Epoll) wait() (int, error) {
var nfds int
var err error
for {
nfds, err = epollWait(e.epollfd, e.events[:], -1)
if err == unix.EINTR {
continue
}
break
}
if err != nil {
return 0, err
}
return nfds, nil
}
func (e *Epoll) incoming(index int) bool {
return (*(*Event)(e.events[index].Ptr)).Fd == int32(e.fd)
}
func (e *Epoll) add(fd int, data interface{}) error {
err := unix.SetNonblock(fd, true)
if err != nil {
return err
}
e.ev.Events = unix.EPOLLIN
ptr := C.malloc(C.ulong(unsafe.Sizeof(Event{})))
(*Event)(ptr).Fd = int32(fd)
(*Event)(ptr).send = false
(*Event)(ptr).Cookie = data
e.ev.Ptr = unsafe.Pointer(ptr)
return epollCtl(e.epollfd, unix.EPOLL_CTL_ADD, fd, e.ev)
}
func (e *Epoll) executeHandlers(ev *epollEvent) error {
event := (*Event)(ev.Ptr)
if (ev.Events & (unix.EPOLLIN | unix.EPOLLERR | unix.EPOLLHUP)) != 0 {
if !e.handlers.HandlerIn(event) {
return nil
}
if event.send {
if err := e.out(event.Fd); err != nil {
return err
}
}
return nil
}
if (ev.Events & (unix.EPOLLOUT | unix.EPOLLERR | unix.EPOLLHUP)) != 0 {
if !e.handlers.HandlerOut(event) {
C.free(ev.Ptr)
return nil
}
if !event.send {
if err := e.in(event.Fd); err != nil {
return err
}
}
return nil
}
if (ev.Events & (unix.EPOLLERR | unix.EPOLLHUP)) != 0 {
e.handlers.HandlerErr(event)
C.free(ev.Ptr)
return nil
}
log.Println("Unknown Epoll event")
return nil
}
func (e *Epoll) out(fd int32) error {
event := (*Event)(e.ev.Ptr)
event.Fd = fd
e.ev.Events = unix.EPOLLOUT
return epollCtl(e.epollfd, unix.EPOLL_CTL_MOD, int(fd), e.ev)
}
func (e *Epoll) in(fd int32) error {
event := (*Event)(e.ev.Ptr)
event.Fd = fd
e.ev.Events = unix.EPOLLIN
return epollCtl(e.epollfd, unix.EPOLL_CTL_MOD, int(fd), e.ev)
} My file package epoll
import (
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
var (
_zero uintptr
)
type epollEvent struct {
Events uint32
Ptr unsafe.Pointer
}
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case unix.EAGAIN:
return syscall.EAGAIN
case unix.EINVAL:
return syscall.EINVAL
case unix.ENOENT:
return syscall.ENOENT
}
return e
}
func epollWait(epfd int, events []epollEvent, msec int) (n int, err error) {
var _p0 unsafe.Pointer
if len(events) > 0 {
_p0 = unsafe.Pointer(&events[0])
} else {
_p0 = unsafe.Pointer(&_zero)
}
r0, _, e1 := unix.Syscall6(unix.SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
func epollCtl(epfd int, op int, fd int, event *epollEvent) (err error) {
_, _, e1 := unix.RawSyscall6(unix.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
} Example of usage: socket, err := newSocket(ip, port)
if err != nil {
log.Fatal(err)
}
defer unix.Close(socket)
h := epoll.Handlers{
HandlerIn: func(e *epoll.Event) {
// in stuff
},
HandlerOut: func(e *epoll.Event) {
// out stuff
},
HandlerErr: func(e *epoll.Event) {
// err stuff
},
HandlerNewConnection: func(fd int) (int, interface{}, error) {
// call from CGO
conn, err := C.GotNewConnection(fd, config)
if err != nil {
return 0, nil, err
}
return conn.FD, conn, nil
},
}
epoll, err := epoll.New(socket, h)
if err != nil {
log.Fatal(err)
}
defer epoll.Close()
epoll.Wait() In this sample of code I tried to use pointers from MALLOC(3) instead of Go pointers. to prevent this issue. |
It's very weird @tklauser after calling A thing I can not understand because here I register the pointer related to new event: e.ev.Events = unix.EPOLLIN
ptr := C.malloc(C.ulong(unsafe.Sizeof(Event{})))
(*Event)(ptr).Fd = int32(fd)
(*Event)(ptr).send = false
(*Event)(ptr).Cookie = data
e.ev.Ptr = unsafe.Pointer(ptr)
return epollCtl(e.epollfd, unix.EPOLL_CTL_ADD, fd, e.ev) After debugging pointer address I registered before |
Here is a more compact example if you want (quick implementation): Same here |
@tklauser I found the main reason of the issue mentioned in those examples, if you want to use epoll in ptr mode correctly with the new Since the C #define __EPOLL_PACKED __attribute__ ((__packed__))
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED; the type EpollEvent struct {
Events uint32
Ptr unsafe.Pointer // Or uintptr
} The To properly use epoll in ptr mode the solution is to use CGO to instantiate an type EpollEvent struct {
Events uint32
Ptr [8]byte
} And then use the |
What version of Go are you using (
go version
)?I'm using EPOLL(7) from the
sys/unix
package, sinceepoll_event
struct have a C unionepoll_data
The
sys/unix
package supporting the structEpollEvent
withPad
at the end of the struct to fill the union size.
The goal of this issue, is to use
EpollEvent
withvoid *ptr
and notint fd
I created a struct
epollEvent
and called the syscall manuallyTo try to implement the change.
The implementation issue
When I initialize my slice
epollEvent
and allPtr uintpr
to a struct instance associated to an epoll event.And I call
epollWait
I retrieve my address associated to the event.But the problem here is the Golang runtime, sometime the runtime move or GC the data associated to my address.
I tried to use
unsafe.Pointer
insteaf ofuintptr
but the issue is the same.In this case what is the best way to create data and associate it to my
Ptr
To be retrieved it later by
epoll_wait(2)
syscall?Do you think the Idea I would like to realize it possible, if yes I could propose some change to support both in
sys/unix
packagefd
mode andptr
mode.Thanks a lot
The text was updated successfully, but these errors were encountered: