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 }