github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/poller/iouring.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package poller
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"sync"
    10  	"sync/atomic"
    11  	"syscall"
    12  	"unsafe"
    13  
    14  	"github.com/ii64/gouring"
    15  	"github.com/weedge/lib/log"
    16  	"golang.org/x/sys/unix"
    17  )
    18  
    19  const (
    20  	reqFeatures = gouring.IORING_FEAT_SINGLE_MMAP | gouring.IORING_FEAT_FAST_POLL | gouring.IORING_FEAT_NODROP
    21  )
    22  
    23  type ioUring struct {
    24  	spins             int64                           // spins count for submit and wait timeout
    25  	ring              *gouring.IoUring                // liburing ring obj
    26  	eventfd           int                             // register iouring eventfd
    27  	mapUserDataEvent  map[gouring.UserData]*eventInfo // user data from cqe to event info
    28  	userDataEventLock sync.RWMutex                    // rwlock for mapUserDataEvent
    29  	subLock           sync.Mutex
    30  	cqeSignCh         chan struct{}
    31  }
    32  
    33  // newIoUring
    34  // new io uring with params, check required features,
    35  // register ring fd
    36  func newIoUring(entries uint32, params *gouring.IoUringParams) (iouring *ioUring, err error) {
    37  	ring, err := gouring.NewWithParams(entries, params)
    38  	if err != nil {
    39  		return
    40  	}
    41  
    42  	if params != nil && params.Features&reqFeatures == 0 {
    43  		err = ErrIOUringFeaturesUnAvailable
    44  		ring.Close()
    45  		return
    46  	}
    47  
    48  	/*
    49  			Note:
    50  			When the ring descriptor is registered, it is stored internally in the struct io_uring structure.
    51  			For applications that share a ring between threads, for example having one thread do submits and another reap events, then this optimization cannot be used as each thread may have a different index for the registered ring fd.
    52  
    53  		ret, err := ring.RegisterRingFD()
    54  		if err != nil || ret < 0 {
    55  			log.Errorf("ring.RegisterRingFD err %s", err.Error())
    56  			err = ErrIOUringRegisterFDFail
    57  			return
    58  		}
    59  	*/
    60  
    61  	log.Infof("newIoUring ok")
    62  
    63  	iouring = &ioUring{
    64  		ring:             ring,
    65  		mapUserDataEvent: make(map[gouring.UserData]*eventInfo),
    66  		cqeSignCh:        make(chan struct{}, 1),
    67  	}
    68  
    69  	return
    70  }
    71  
    72  func (m *ioUring) CloseRing() {
    73  	if m.ring != nil {
    74  		m.ring.Close()
    75  	}
    76  }
    77  
    78  func (m *ioUring) RegisterEventFd() (err error) {
    79  	eventfd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC)
    80  	if err != nil {
    81  		return
    82  	}
    83  	m.eventfd = eventfd
    84  
    85  	err = m.ring.RegisterEventFd(m.eventfd)
    86  	if err != nil {
    87  		return
    88  	}
    89  
    90  	return
    91  }
    92  
    93  // getEventInfo io_uring submit and wait cqe for reap event
    94  // notice: gc
    95  func (m *ioUring) getEventInfov1() (info *eventInfo, err error) {
    96  	if atomic.AddInt64(&m.spins, 1) <= 20 {
    97  		return
    98  	}
    99  	atomic.StoreInt64(&m.spins, 0)
   100  
   101  	var cqeData *gouring.IoUringCqe
   102  	// submit wait at least 1 cqe and wait 1 us timeout, todo: use sync call instead of async callback
   103  	err = m.ring.SubmitAndWaitTimeOut(&cqeData, 1, 1, nil)
   104  	if err != nil {
   105  		if errors.Is(err, syscall.ETIME) || errors.Is(err, syscall.EINTR) || errors.Is(err, syscall.EAGAIN) {
   106  			err = nil
   107  		}
   108  		return
   109  	}
   110  	if cqeData == nil {
   111  		return
   112  	}
   113  
   114  	if cqeData.UserData.GetUnsafe() == nil {
   115  		// Own timeout doesn't have user data
   116  		errStr := fmt.Sprintf("no user data, cqe:%+v", cqeData)
   117  		err = errors.New(errStr)
   118  		return
   119  	}
   120  	cqe := *cqeData
   121  
   122  	m.userDataEventLock.Lock()
   123  	info, ok := m.mapUserDataEvent[cqe.UserData]
   124  	if !ok {
   125  		errStr := fmt.Sprintf("cqe %+v userData %d get event info: %s empty", cqe, cqe.UserData, info)
   126  		m.userDataEventLock.Unlock()
   127  		//panic(errStr)
   128  		log.Error(errStr)
   129  		// commit cqe is seen
   130  		m.cqeDone(cqe)
   131  		return
   132  	}
   133  	//info = (*eventInfo)(cqe.UserData.GetUnsafe())
   134  	if info != nil && (info.cb == nil || info.etype == ETypeUnknow) {
   135  		m.userDataEventLock.Unlock()
   136  		err = errors.New("error event infoPtr")
   137  		// commit cqe is seen
   138  		m.cqeDone(cqe)
   139  		return
   140  	}
   141  	//https://github.com/golang/go/issues/20135
   142  	delete(m.mapUserDataEvent, cqe.UserData)
   143  	log.Infof("userData %d get event info: %s", cqe.UserData, info)
   144  	info.cqe = cqe
   145  	m.userDataEventLock.Unlock()
   146  
   147  	return
   148  }
   149  
   150  // getEventInfos
   151  // @todo
   152  // io_uring submit and wait mutli cqe for reap events
   153  func (m *ioUring) getEventInfos(infos []*eventInfo, err error) {
   154  	return
   155  }
   156  
   157  func (m *ioUring) getEventInfo() (info *eventInfo, err error) {
   158  	for {
   159  		select {
   160  		case <-m.cqeSignCh:
   161  			var cqe *gouring.IoUringCqe
   162  			err = m.ring.PeekCqe(&cqe)
   163  			if err != nil {
   164  				continue
   165  			}
   166  			if cqe == nil {
   167  				log.Warnf("cqe is nil")
   168  				continue
   169  			}
   170  
   171  			m.userDataEventLock.Lock()
   172  			info, ok := m.mapUserDataEvent[cqe.UserData]
   173  			if !ok {
   174  				errStr := fmt.Sprintf("cqe %+v userData %d get event info: %s empty", cqe, cqe.UserData, info)
   175  				m.userDataEventLock.Unlock()
   176  				//panic(errStr)
   177  				log.Error(errStr)
   178  				// commit cqe is seen
   179  				m.cqeDone(*cqe)
   180  				continue
   181  			}
   182  			//info = (*eventInfo)(cqe.UserData.GetUnsafe())
   183  			if info != nil && (info.cb == nil || info.etype == ETypeUnknow) {
   184  				m.userDataEventLock.Unlock()
   185  				log.Error("error event infoPtr")
   186  				// commit cqe is seen
   187  				m.cqeDone(*cqe)
   188  				continue
   189  			}
   190  			//https://github.com/golang/go/issues/20135
   191  			delete(m.mapUserDataEvent, cqe.UserData)
   192  			log.Debugf("userData %d get event info: %s", cqe.UserData, info)
   193  			info.cqe = *cqe
   194  			m.userDataEventLock.Unlock()
   195  
   196  			return info, nil
   197  		}
   198  	}
   199  }
   200  
   201  func (m *ioUring) addAcceptSqe(cb EventCallBack, lfd int,
   202  	clientAddr *syscall.RawSockaddrAny, clientAddrLen uint32, flags uint8) {
   203  	m.subLock.Lock()
   204  	defer m.subLock.Unlock()
   205  	sqe := m.ring.GetSqe()
   206  	gouring.PrepAccept(sqe, lfd, clientAddr, (*uintptr)(unsafe.Pointer(&clientAddrLen)), 0)
   207  	sqe.Flags = flags
   208  
   209  	eventInfo := &eventInfo{
   210  		fd:    lfd,
   211  		etype: ETypeAccept,
   212  		cb:    cb,
   213  	}
   214  
   215  	sqe.UserData = gouring.UserData(uintptr(unsafe.Pointer(eventInfo)))
   216  	m.userDataEventLock.Lock()
   217  	m.mapUserDataEvent[sqe.UserData] = eventInfo
   218  	m.userDataEventLock.Unlock()
   219  	log.Debugf("addAcceptSqe userData %d eventInfo:%s", sqe.UserData, eventInfo)
   220  	m.ring.Submit()
   221  }
   222  
   223  func (m *ioUring) addRecvSqe(cb EventCallBack, cfd int, buff []byte, size int, flags uint8) {
   224  	m.subLock.Lock()
   225  	defer m.subLock.Unlock()
   226  	var buf *byte
   227  	if len(buff) > 0 {
   228  		buf = &buff[0]
   229  	}
   230  	sqe := m.ring.GetSqe()
   231  	gouring.PrepRecv(sqe, cfd, buf, size, uint(flags))
   232  	sqe.Flags = flags
   233  
   234  	eventInfo := &eventInfo{
   235  		fd:    cfd,
   236  		etype: ETypeRead,
   237  		cb:    cb,
   238  	}
   239  
   240  	//sqe.UserData.SetUnsafe(unsafe.Pointer(eventInfo))
   241  	sqe.UserData = gouring.UserData(uintptr(unsafe.Pointer(eventInfo)))
   242  	m.userDataEventLock.Lock()
   243  	m.mapUserDataEvent[sqe.UserData] = eventInfo
   244  	m.userDataEventLock.Unlock()
   245  	log.Debugf("addRecvSqe userData %d eventInfo:%s", sqe.UserData, eventInfo)
   246  	m.ring.Submit()
   247  }
   248  
   249  func (m *ioUring) addSendSqe(cb EventCallBack, cfd int, buff []byte, msgSize int, flags uint8) {
   250  	m.subLock.Lock()
   251  	defer m.subLock.Unlock()
   252  	var buf *byte
   253  	if len(buff) > 0 {
   254  		buf = &buff[0]
   255  	}
   256  	sqe := m.ring.GetSqe()
   257  	gouring.PrepSend(sqe, cfd, buf, msgSize, uint(flags))
   258  	sqe.Flags = flags
   259  
   260  	eventInfo := &eventInfo{
   261  		fd:    cfd,
   262  		etype: ETypeWrite,
   263  		cb:    cb,
   264  	}
   265  
   266  	//sqe.UserData.SetUnsafe(unsafe.Pointer(eventInfo))
   267  	sqe.UserData = gouring.UserData(uintptr(unsafe.Pointer(eventInfo)))
   268  	m.userDataEventLock.Lock()
   269  	m.mapUserDataEvent[sqe.UserData] = eventInfo
   270  	m.userDataEventLock.Unlock()
   271  	log.Debugf("addSendSqe userData %d eventInfo:%s", sqe.UserData, eventInfo)
   272  	m.ring.Submit()
   273  }
   274  
   275  func (m *ioUring) cqeDone(cqe gouring.IoUringCqe) {
   276  	m.ring.SeenCqe(&cqe)
   277  }