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  }