Files
mostlymatter/vendor/github.com/mailru/easygo/netpoll/kqueue.go
Agniva De Sarker a246104d04 MM-21012: Revamp websocket implementation (#16620)
* MM-21012: Revamp websocket implementation

We replace the old gorilla/websocket implementation with the
gobwas/ws library. The gorilla library was in maintenance mode
and had a high level API due to which we cannot use that for
situations where a large number of concurrent connections needs
to be supported.

The ws library is a very low-level library that allows us
to work with raw net.Conns. We make several improvements:

- We completely remove the reader goroutines, and instead
replace them with a manual epoll implementation which sends off
messages to be read when it receives any data on the connection.
This lets us scale to a much larger number of connections.
- The reader buffer is eliminated, because we directly read
from the connection now.

https://mattermost.atlassian.net/browse/MM-21012

```release-notes
Improved the websocket implementation by using epoll manually
to read from a websocket. As a result, the number of goroutines
is expected to go down by half.
```

* fix tests

* fix shadowing errors

* final changes

* windows support!

* Remove pointer to waitgroup

* Fix edge case

* Trigger CI

* Trigger CI

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
2021-02-13 23:42:11 +05:30

402 строки
11 KiB
Go

// +build darwin dragonfly freebsd netbsd openbsd
package netpoll
import (
"reflect"
"sync"
"unsafe"
"golang.org/x/sys/unix"
)
// KeventFilter is a kqueue event filter.
type KeventFilter int
// String returns string representation of a filter.
func (filter KeventFilter) String() (str string) {
switch filter {
case EVFILT_READ:
return "EVFILT_READ"
case EVFILT_WRITE:
return "EVFILT_WRITE"
case EVFILT_AIO:
return "EVFILT_AIO"
case EVFILT_VNODE:
return "EVFILT_VNODE"
case EVFILT_PROC:
return "EVFILT_PROC"
case EVFILT_SIGNAL:
return "EVFILT_SIGNAL"
case EVFILT_TIMER:
return "EVFILT_TIMER"
case EVFILT_USER:
return "EVFILT_USER"
case _EVFILT_CLOSED:
return "_EVFILT_CLOSED"
default:
return "_EVFILT_UNKNOWN"
}
}
const (
// EVFILT_READ takes a descriptor as the identifier, and returns whenever
// there is data available to read. The behavior of the filter is slightly
// different depending on the descriptor type.
EVFILT_READ = unix.EVFILT_READ
// EVFILT_WRITE takes a descriptor as the identifier, and returns whenever
// it is possible to write to the descriptor. For sockets, pipes and fifos,
// data will contain the amount of space remaining in the write buffer. The
// filter will set EV_EOF when the reader disconnects, and for the fifo
// case, this may be cleared by use of EV_CLEAR. Note that this filter is
// not supported for vnodes or BPF devices. For sockets, the low water mark
// and socket error handling is identical to the EVFILT_READ case.
EVFILT_WRITE = unix.EVFILT_WRITE
// EVFILT_AIO the sigevent portion of the AIO request is filled in, with
// sigev_notify_kqueue containing the descriptor of the kqueue that the
// event should be attached to, sigev_notify_kevent_flags containing the
// kevent flags which should be EV_ONESHOT, EV_CLEAR or EV_DISPATCH,
// sigev_value containing the udata value, and sigev_notify set to
// SIGEV_KEVENT. When the aio_*() system call is made, the event will be
// registered with the specified kqueue, and the ident argument set to the
// struct aiocb returned by the aio_*() system call. The filter returns
// under the same conditions as aio_error().
EVFILT_AIO = unix.EVFILT_AIO
// EVFILT_VNODE takes a file descriptor as the identifier and the events to
// watch for in fflags, and returns when one or more of the requested
// events occurs on the descriptor.
EVFILT_VNODE = unix.EVFILT_VNODE
// EVFILT_PROC takes the process ID to monitor as the identifier and the
// events to watch for in fflags, and returns when the process performs one
// or more of the requested events. If a process can normally see another
// process, it can attach an event to it.
EVFILT_PROC = unix.EVFILT_PROC
// EVFILT_SIGNAL takes the signal number to monitor as the identifier and
// returns when the given signal is delivered to the process. This coexists
// with the signal() and sigaction() facilities, and has a lower
// precedence. The filter will record all attempts to deliver a signal to
// a process, even if the signal has been marked as SIG_IGN, except for the
// SIGCHLD signal, which, if ignored, won't be recorded by the filter.
// Event notification happens after normal signal delivery processing. data
// returns the number of times the signal has occurred since the last call
// to kevent(). This filter automatically sets the EV_CLEAR flag
// internally.
EVFILT_SIGNAL = unix.EVFILT_SIGNAL
// EVFILT_TIMER establishes an arbitrary timer identified by ident. When
// adding a timer, data specifies the timeout period. The timer will be
// periodic unless EV_ONESHOT is specified. On return, data contains the
// number of times the timeout has expired since the last call to kevent().
// This filter automatically sets the EV_CLEAR flag internally. There is a
// system wide limit on the number of timers which is controlled by the
// kern.kq_calloutmax sysctl.
EVFILT_TIMER = unix.EVFILT_TIMER
// EVFILT_USER establishes a user event identified by ident which is not
// associated with any kernel mechanism but is trig- gered by user level
// code.
EVFILT_USER = unix.EVFILT_USER
// Custom filter value signaling that kqueue instance get closed.
_EVFILT_CLOSED = -0x7f
)
// KeventFlag represents kqueue event flag.
type KeventFlag int
// String returns string representation of flag bits of the form
// "EV_A|EV_B|...".
func (flag KeventFlag) String() (str string) {
name := func(f KeventFlag, name string) {
if flag&f == 0 {
return
}
if str != "" {
str += "|"
}
str += name
}
name(EV_ADD, "EV_ADD")
name(EV_ENABLE, "EV_ENABLE")
name(EV_DISABLE, "EV_DISABLE")
name(EV_DISPATCH, "EV_DISPATCH")
name(EV_DELETE, "EV_DELETE")
name(EV_RECEIPT, "EV_RECEIPT")
name(EV_ONESHOT, "EV_ONESHOT")
name(EV_CLEAR, "EV_CLEAR")
name(EV_EOF, "EV_EOF")
name(EV_ERROR, "EV_ERROR")
return
}
const (
// EV_ADD adds the event to the kqueue. Re-adding an existing event will modify
// the parameters of the original event, and not result in a duplicate
// entry. Adding an event automatically enables it, unless overridden by
// the EV_DISABLE flag.
EV_ADD = unix.EV_ADD
// EV_ENABLE permits kevent() to return the event if it is triggered.
EV_ENABLE = unix.EV_ENABLE
// EV_DISABLE disables the event so kevent() will not return it. The filter itself is
// not disabled.
EV_DISABLE = unix.EV_DISABLE
// EV_DISPATCH disables the event source immediately after delivery of an event. See
// EV_DISABLE above.
EV_DISPATCH = unix.EV_DISPATCH
// EV_DELETE removes the event from the kqueue. Events which are attached to file
// descriptors are automatically deleted on the last close of the
// descriptor.
EV_DELETE = unix.EV_DELETE
// EV_RECEIPT is useful for making bulk changes to a kqueue without draining
// any pending events. When passed as input, it forces EV_ERROR to always
// be returned. When a filter is successfully added the data field will be
// zero.
EV_RECEIPT = unix.EV_RECEIPT
// EV_ONESHOT causes the event to return only the first occurrence of the
// filter being triggered. After the user retrieves the event from the
// kqueue, it is deleted.
EV_ONESHOT = unix.EV_ONESHOT
// EV_CLEAR makes event state be reset after the event is retrieved by the
// user. This is useful for filters which report state transitions instead
// of the current state. Note that some filters may automatically set this
// flag internally.
EV_CLEAR = unix.EV_CLEAR
// EV_EOF may be set by the filters to indicate filter-specific EOF
// condition.
EV_EOF = unix.EV_EOF
// EV_ERROR is set to indiacate an error occured with the identtifier.
EV_ERROR = unix.EV_ERROR
)
// filterCount is a constant number of available filters which can be
// registered for an identifier.
const filterCount = 8
// Kevent represents kevent.
type Kevent struct {
Filter KeventFilter
Flags KeventFlag
Fflags uint32
Data int64
}
// Kevents is a fixed number of pairs of event filter and flags which can be
// registered for an identifier.
type Kevents [8]Kevent
// KeventHandler is a function that will be called when event occures on
// registered identifier.
type KeventHandler func(Kevent)
// KqueueConfig contains options for configuration kqueue instance.
type KqueueConfig struct {
// OnWaitError will be called from goroutine, waiting for events.
OnWaitError func(error)
}
func (c *KqueueConfig) withDefaults() (config KqueueConfig) {
if c != nil {
config = *c
}
if config.OnWaitError == nil {
config.OnWaitError = defaultOnWaitError
}
return config
}
// Kqueue represents kqueue instance.
type Kqueue struct {
mu sync.RWMutex
fd int
cb map[int]KeventHandler
done chan struct{}
closed bool
}
// KqueueCreate creates new kqueue instance.
// It starts wait loop in a separate goroutine.
func KqueueCreate(c *KqueueConfig) (*Kqueue, error) {
config := c.withDefaults()
fd, err := unix.Kqueue()
if err != nil {
return nil, err
}
kq := &Kqueue{
fd: fd,
cb: make(map[int]KeventHandler),
done: make(chan struct{}),
}
go kq.wait(config.OnWaitError)
return kq, nil
}
// Close closes kqueue instance.
// NOTE: not implemented yet.
func (k *Kqueue) Close() error {
// TODO(): implement close.
return nil
}
// Add adds a event handler for identifier fd with given n events.
func (k *Kqueue) Add(fd int, events Kevents, n int, cb KeventHandler) error {
var kevs [filterCount]unix.Kevent_t
for i := 0; i < n; i++ {
kevs[i] = evGet(fd, events[i].Filter, events[i].Flags)
}
arr := unsafe.Pointer(&kevs)
hdr := &reflect.SliceHeader{
Data: uintptr(arr),
Len: n,
Cap: n,
}
changes := *(*[]unix.Kevent_t)(unsafe.Pointer(hdr))
k.mu.Lock()
defer k.mu.Unlock()
if k.closed {
return ErrClosed
}
if _, has := k.cb[fd]; has {
return ErrRegistered
}
k.cb[fd] = cb
_, err := unix.Kevent(k.fd, changes, nil, nil)
return err
}
// Mod modifies events registered for fd.
func (k *Kqueue) Mod(fd int, events Kevents, n int) error {
var kevs [filterCount]unix.Kevent_t
for i := 0; i < n; i++ {
kevs[i] = evGet(fd, events[i].Filter, events[i].Flags)
}
arr := unsafe.Pointer(&kevs)
hdr := &reflect.SliceHeader{
Data: uintptr(arr),
Len: n,
Cap: n,
}
changes := *(*[]unix.Kevent_t)(unsafe.Pointer(hdr))
k.mu.RLock()
defer k.mu.RUnlock()
if k.closed {
return ErrClosed
}
if _, has := k.cb[fd]; !has {
return ErrNotRegistered
}
_, err := unix.Kevent(k.fd, changes, nil, nil)
return err
}
// Del removes callback for fd. Note that it does not cleanups events for fd in
// kqueue. You should close fd or call Mod() with EV_DELETE flag set.
func (k *Kqueue) Del(fd int) error {
k.mu.Lock()
defer k.mu.Unlock()
if k.closed {
return ErrClosed
}
if _, has := k.cb[fd]; !has {
return ErrNotRegistered
}
delete(k.cb, fd)
return nil
}
func (k *Kqueue) wait(onError func(error)) {
const (
maxWaitEventsBegin = 1 << 10 // 1024
maxWaitEventsStop = 1 << 15 // 32768
)
defer func() {
if err := unix.Close(k.fd); err != nil {
onError(err)
}
close(k.done)
}()
evs := make([]unix.Kevent_t, maxWaitEventsBegin)
cbs := make([]KeventHandler, maxWaitEventsBegin)
for {
n, err := unix.Kevent(k.fd, nil, evs, nil)
if err != nil {
if temporaryErr(err) {
continue
}
onError(err)
return
}
cbs = cbs[:n]
k.mu.RLock()
for i := 0; i < n; i++ {
fd := int(evs[i].Ident)
if fd == -1 { //todo
k.mu.RUnlock()
return
}
cbs[i] = k.cb[fd]
}
k.mu.RUnlock()
for i, cb := range cbs {
if cb != nil {
e := evs[i]
cb(Kevent{
Filter: KeventFilter(e.Filter),
Flags: KeventFlag(e.Flags),
Data: e.Data,
Fflags: e.Fflags,
})
cbs[i] = nil
}
}
if n == len(evs) && n*2 <= maxWaitEventsStop {
evs = make([]unix.Kevent_t, n*2)
cbs = make([]KeventHandler, n*2)
}
}
}
func evGet(fd int, filter KeventFilter, flags KeventFlag) unix.Kevent_t {
return unix.Kevent_t{
Ident: uint64(fd),
Filter: int16(filter),
Flags: uint16(flags),
}
}