status-go/vendor/github.com/libp2p/go-reuseport/poll/poll_linux.go

132 lines
2.9 KiB
Go

// +build linux
package poll
import (
"context"
"golang.org/x/sys/unix"
"sync"
"github.com/gxed/eventfd"
)
type Poller struct {
epfd int
eventMain unix.EpollEvent
eventWait unix.EpollEvent
events []unix.EpollEvent
wake *eventfd.EventFD // Use eventfd to wakeup epoll
wakeMutex sync.Mutex
}
func New(fd int) (p *Poller, err error) {
p = &Poller{
events: make([]unix.EpollEvent, 32),
}
if p.epfd, err = unix.EpollCreate1(0); err != nil {
return nil, err
}
wake, err := eventfd.New()
if err != nil {
unix.Close(p.epfd)
return nil, err
}
p.wake = wake
p.eventMain.Events = unix.EPOLLOUT
p.eventMain.Fd = int32(fd)
if err = unix.EpollCtl(p.epfd, unix.EPOLL_CTL_ADD, fd, &p.eventMain); err != nil {
p.Close()
return nil, err
}
// poll that eventfd can be read
p.eventWait.Events = unix.EPOLLIN
p.eventWait.Fd = int32(wake.Fd())
if err = unix.EpollCtl(p.epfd, unix.EPOLL_CTL_ADD, wake.Fd(), &p.eventWait); err != nil {
p.wake.Close()
p.Close()
return nil, err
}
return p, nil
}
func (p *Poller) Close() error {
p.wakeMutex.Lock()
err1 := p.wake.Close()
// set wake to nil to be sure that we won't call write on closed wake
// it should never happen but if someone changes something this might show a bug
p.wake = nil
p.wakeMutex.Unlock()
err2 := unix.Close(p.epfd)
if err1 != nil {
return err1
} else {
return err2
}
}
func (p *Poller) WaitWriteCtx(ctx context.Context) error {
doneChan := make(chan struct{})
defer close(doneChan)
go func() {
select {
case <-doneChan:
return
case <-ctx.Done():
select {
case <-doneChan:
// if we re done with this function do not write to p.wake
// it might be already closed and the fd could be reopened for
// different purpose
return
default:
}
p.wakeMutex.Lock()
if p.wake != nil {
p.wake.WriteEvents(1) // send event to wake up epoll
}
// if it is nil then we already closed
p.wakeMutex.Unlock()
return
}
}()
n, err := unix.EpollWait(p.epfd, p.events, -1)
if err != nil {
return err
}
good := false
for i := 0; i < n; i++ {
ev := p.events[i]
switch ev.Fd {
case p.eventMain.Fd:
good = true
case p.eventWait.Fd:
p.wakeMutex.Lock()
p.wake.ReadEvents() // clear eventfd
p.wakeMutex.Unlock()
default:
// shouldn't happen as epoll should onlt return events we registered
}
}
if good {
// in case both eventMain and eventWait are lit, we got with eventMain
// as it is the success condition here and if both of them are returned
// at the same time it means that socket connected right as context timed out
return nil
}
if ctx.Err() == nil {
// notification is sent by other goroutine when context deadline was reached
// if we are here it means that we got notification buy the deadline wasn't reached
panic("notification but no deadline, this should be impossible")
}
return ctx.Err()
}