github.com/dshulyak/uring@v0.0.0-20210209113719-1b2ec51f1542/loop/loop_test.go (about) 1 package loop 2 3 import ( 4 "runtime" 5 "sync" 6 "syscall" 7 "testing" 8 "time" 9 "unsafe" 10 11 "github.com/dshulyak/uring" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "golang.org/x/sys/unix" 15 ) 16 17 func TestLoop(t *testing.T) { 18 tester := func(t *testing.T, q *Loop) { 19 t.Cleanup(func() { 20 q.Close() 21 }) 22 r := 1024 23 iter := 100 24 var wg sync.WaitGroup 25 results := make(chan uring.CQEntry, r*iter) 26 for i := 0; i < r; i++ { 27 wg.Add(1) 28 go func() { 29 defer wg.Done() 30 for j := 0; j < iter; j++ { 31 cqe, err := q.Syscall(func(sqe *uring.SQEntry) { 32 uring.Nop(sqe) 33 }) 34 if !assert.NoError(t, err) { 35 return 36 } 37 results <- cqe 38 } 39 }() 40 } 41 wg.Wait() 42 close(results) 43 count := 0 44 for _ = range results { 45 count++ 46 } 47 require.Equal(t, r*iter, count) 48 } 49 50 t.Run("default", func(t *testing.T) { 51 q, err := Setup(1024, nil, nil) 52 require.NoError(t, err) 53 tester(t, q) 54 }) 55 t.Run("simple poll", func(t *testing.T) { 56 q, err := Setup(1024, nil, &Params{ 57 WaitMethod: WaitPoll, 58 }) 59 require.NoError(t, err) 60 tester(t, q) 61 }) 62 t.Run("simple enter", func(t *testing.T) { 63 q, err := Setup(1024, nil, &Params{ 64 WaitMethod: WaitEnter, 65 }) 66 require.NoError(t, err) 67 tester(t, q) 68 }) 69 t.Run("simple eventfd", func(t *testing.T) { 70 q, err := Setup(1024, nil, &Params{ 71 Rings: 1, 72 WaitMethod: WaitEventfd, 73 }) 74 require.NoError(t, err) 75 tester(t, q) 76 }) 77 t.Run("sharded enter", func(t *testing.T) { 78 q, err := Setup(1024, nil, &Params{ 79 Rings: runtime.NumCPU(), 80 WaitMethod: WaitEnter, 81 }) 82 require.NoError(t, err) 83 tester(t, q) 84 }) 85 } 86 87 func TestBatch(t *testing.T) { 88 tester := func(t *testing.T, q *Loop) { 89 t.Cleanup(func() { 90 q.Close() 91 }) 92 iter := 10000 93 size := 4 94 var wg sync.WaitGroup 95 results := make(chan uring.CQEntry, iter*size) 96 for i := 0; i < iter; i++ { 97 wg.Add(1) 98 go func() { 99 defer wg.Done() 100 batch := make([]SQOperation, size) 101 for i := range batch { 102 batch[i] = uring.Nop 103 } 104 cqes, err := q.BatchSyscall(nil, batch) 105 if !assert.NoError(t, err) { 106 return 107 } 108 for _, cqe := range cqes { 109 results <- cqe 110 } 111 }() 112 } 113 wg.Wait() 114 close(results) 115 count := 0 116 for _ = range results { 117 count++ 118 } 119 require.Equal(t, count, iter*size) 120 } 121 122 t.Run("default", func(t *testing.T) { 123 q, err := Setup(1024, nil, nil) 124 require.NoError(t, err) 125 tester(t, q) 126 }) 127 t.Run("sharded enter", func(t *testing.T) { 128 q, err := Setup(1024, nil, &Params{ 129 Rings: runtime.NumCPU(), 130 WaitMethod: WaitEnter, 131 }) 132 require.NoError(t, err) 133 tester(t, q) 134 }) 135 } 136 137 func BenchmarkLoop(b *testing.B) { 138 bench := func(b *testing.B, q *Loop) { 139 b.Cleanup(func() { 140 q.Close() 141 }) 142 var wg sync.WaitGroup 143 b.ResetTimer() 144 for i := 0; i < b.N; i++ { 145 wg.Add(1) 146 go func() { 147 defer wg.Done() 148 _, err := q.Syscall(func(sqe *uring.SQEntry) { 149 uring.Nop(sqe) 150 }) 151 if err != nil { 152 b.Error(err) 153 } 154 }() 155 } 156 wg.Wait() 157 } 158 b.Run("default", func(b *testing.B) { 159 q, err := Setup(2048, &uring.IOUringParams{ 160 CQEntries: 2 * 4096, 161 Flags: uring.IORING_SETUP_CQSIZE, 162 }, nil) 163 require.NoError(b, err) 164 bench(b, q) 165 }) 166 b.Run("enter", func(b *testing.B) { 167 q, err := Setup(2048, &uring.IOUringParams{ 168 CQEntries: 2 * 4096, 169 Flags: uring.IORING_SETUP_CQSIZE, 170 }, &Params{ 171 Rings: runtime.NumCPU(), 172 WaitMethod: WaitEnter, 173 Flags: FlagSharedWorkers, 174 }) 175 require.NoError(b, err) 176 bench(b, q) 177 }) 178 } 179 180 func BenchmarkBatch(b *testing.B) { 181 bench := func(b *testing.B, q *Loop, size int) { 182 b.Cleanup(func() { q.Close() }) 183 var wg sync.WaitGroup 184 b.ResetTimer() 185 186 for i := 0; i < b.N/size; i++ { 187 wg.Add(1) 188 go func() { 189 defer wg.Done() 190 cqes := make([]uring.CQEntry, 0, size) 191 batch := make([]SQOperation, size) 192 for i := range batch { 193 batch[i] = uring.Nop 194 } 195 _, err := q.BatchSyscall(cqes, batch) 196 if err != nil { 197 b.Error(err) 198 } 199 }() 200 } 201 wg.Add(1) 202 go func() { 203 defer wg.Done() 204 cqes := make([]uring.CQEntry, 0, b.N%size) 205 batch := make([]SQOperation, b.N%size) 206 for i := range batch { 207 batch[i] = uring.Nop 208 } 209 _, err := q.BatchSyscall(cqes, batch) 210 if err != nil { 211 b.Error(err) 212 } 213 }() 214 wg.Wait() 215 } 216 b.Run("default 16", func(b *testing.B) { 217 q, err := Setup(128, &uring.IOUringParams{ 218 CQEntries: 2 * 4096, 219 Flags: uring.IORING_SETUP_CQSIZE, 220 }, nil) 221 require.NoError(b, err) 222 bench(b, q, 16) 223 }) 224 } 225 226 func TestTimeoutNoOverwrite(t *testing.T) { 227 q, err := Setup(2, nil, &Params{Rings: 1, WaitMethod: WaitEventfd}) 228 require.NoError(t, err) 229 t.Cleanup(func() { q.Close() }) 230 // we are testing here that the result we used for timeout will not be overwritten by nop. 231 // timeout operation executes long enough (10ms), for results array to wrap around 232 tchan := make(chan struct{}) 233 go func() { 234 ts := unix.Timespec{Nsec: 10_000_000} 235 cqe, err := q.Syscall(func(sqe *uring.SQEntry) { 236 uring.Timeout(sqe, &ts, false, 0) 237 }, uintptr(unsafe.Pointer(&ts))) 238 require.NoError(t, err) 239 require.Equal(t, syscall.ETIME, syscall.Errno(-cqe.Result())) 240 close(tchan) 241 }() 242 for i := 0; i < 100; i++ { 243 _, err := q.Syscall(uring.Nop) 244 require.NoError(t, err) 245 } 246 select { 247 case <-tchan: 248 case <-time.After(10 * time.Second): 249 require.FailNow(t, "timed out") 250 } 251 } 252 253 func TestLinkedBatch(t *testing.T) { 254 q, err := Setup(64, nil, &Params{Rings: 1, WaitMethod: WaitEventfd}) 255 require.NoError(t, err) 256 t.Cleanup(func() { q.Close() }) 257 258 result := make(chan []uring.CQEntry) 259 go func() { 260 wait := unix.Timespec{Sec: 10} 261 timeout := unix.Timespec{Nsec: 10_000} 262 cqes, err := q.BatchSyscall(nil, []SQOperation{ 263 func(sqe *uring.SQEntry) { 264 uring.Timeout(sqe, &wait, false, 0) 265 sqe.SetFlags(uring.IOSQE_IO_LINK) 266 }, 267 func(sqe *uring.SQEntry) { 268 uring.LinkTimeout(sqe, &timeout, false) 269 }, 270 }, uintptr(unsafe.Pointer(&wait)), uintptr(unsafe.Pointer(&timeout))) 271 require.NoError(t, err) 272 result <- cqes 273 }() 274 select { 275 case <-time.After(time.Second): 276 require.FailNow(t, "failed to interrupt waiter") 277 case cqes := <-result: 278 assert.Equal(t, syscall.ECANCELED.Error(), syscall.Errno(-cqes[0].Result()).Error()) 279 assert.Equal(t, syscall.ETIME.Error(), syscall.Errno(-cqes[1].Result()).Error()) 280 } 281 }