From 9fd01334cfb0e4a881767cf3c1ddad584b1a3b79 Mon Sep 17 00:00:00 2001 From: kadmin Date: Tue, 6 Jul 2021 20:57:16 +0000 Subject: [PATCH] update with Josh's changes --- net/uring/all.go | 16 ++++- net/uring/file_test.go | 22 +++++++ net/uring/io_uring.c | 9 ++- net/uring/io_uring.go | 14 ---- net/uring/io_uring_linux.go | 2 +- net/uring/io_uring_notlinux.go | 56 ---------------- net/uring/io_uring_test.go | 117 --------------------------------- net/uring/udp_test.go | 59 +++++++++++++++++ 8 files changed, 102 insertions(+), 193 deletions(-) delete mode 100644 net/uring/io_uring.go delete mode 100644 net/uring/io_uring_notlinux.go delete mode 100644 net/uring/io_uring_test.go diff --git a/net/uring/all.go b/net/uring/all.go index d2361e444..596c98565 100644 --- a/net/uring/all.go +++ b/net/uring/all.go @@ -1,11 +1,23 @@ package uring -import "runtime" +import ( + "errors" + "flag" + "runtime" +) // This file contains code shared across all platforms. // Available reports whether io_uring is available on this machine. // If Available reports false, no other package uring APIs should be called. func Available() bool { - return runtime.GOOS == "linux" + return runtime.GOOS == "linux" && *useIOURing } + +var useIOURing = flag.Bool("use-io-uring", true, "attempt to use io_uring if available") + +// NotSupportedError indicates an operation was attempted when io_uring is not supported. +var NotSupportedError = errors.New("io_uring not supported") + +// DisabledError indicates that io_uring was explicitly disabled. +var DisabledError = errors.New("io_uring disabled") diff --git a/net/uring/file_test.go b/net/uring/file_test.go index 6975e3684..39a7900ca 100644 --- a/net/uring/file_test.go +++ b/net/uring/file_test.go @@ -1,6 +1,7 @@ package uring import ( + "io/ioutil" "os" "testing" @@ -31,3 +32,24 @@ func TestFileRead(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(buf[:n], qt.DeepEquals, want) } + +func TestFileWrite(t *testing.T) { + if !Available() { + t.Skip("io_uring not available") + } + c := qt.New(t) + tmpFile, err := ioutil.TempFile(".", "uring-test") + c.Assert(err, qt.IsNil) + t.Cleanup(func() { + os.Remove(tmpFile.Name()) + }) + f, err := newFile(tmpFile) + c.Assert(err, qt.IsNil) + content := []byte("a test string to check writing works 😀 with non-unicode input") + n, err := f.Write(content) + if n != len(content) { + t.Errorf("mismatch between written len and content len: want %d, got %d", len(content), n) + } + c.Assert(err, qt.IsNil) + c.Assert(f.Close(), qt.IsNil) +} diff --git a/net/uring/io_uring.c b/net/uring/io_uring.c index 1bb41d4ab..afa25b5d3 100644 --- a/net/uring/io_uring.c +++ b/net/uring/io_uring.c @@ -42,7 +42,7 @@ static int initialize(struct io_uring *ring, int fd) { struct req { struct msghdr hdr; - struct iovec iov; + struct iovec iov; struct sockaddr_in sa; struct sockaddr_in6 sa6; // in_kernel indicates (by being non-zero) whether this request is sitting in the kernel @@ -84,6 +84,9 @@ static void freeReq(struct req *r) { // TODO: What recvfrom support arrives, maybe use that instead? static int submit_recvmsg_request(struct io_uring *ring, struct req *r, size_t idx) { struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + if (!sqe) { + return -1; + } io_uring_prep_recvmsg(sqe, 0, &r->hdr, 0); // use the 0th file in the list of registered fds io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE); io_uring_sqe_set_data(sqe, (void *)(idx)); @@ -105,7 +108,7 @@ static int submit_sendmsg_request(struct io_uring *ring, struct req *r, int bufl static void submit_nop_request(struct io_uring *ring) { struct io_uring_sqe *sqe = io_uring_get_sqe(ring); - io_uring_prep_nop(sqe); + io_uring_prep_nop(sqe); io_uring_sqe_set_data(sqe, (void *)(-1)); io_uring_submit(ring); } @@ -167,7 +170,7 @@ static go_completion_result completion(struct io_uring *ring, int block) { } static int set_deadline(struct io_uring *ring, int64_t sec, long long ns) { - // TODO where to put this timeout so that it lives beyond the scope of this call? + // TODO where to put this timespec so that it lives beyond the scope of this call? struct __kernel_timespec ts = { sec, ns }; struct io_uring_sqe *sqe = io_uring_get_sqe(ring); // TODO should these be through function calls? diff --git a/net/uring/io_uring.go b/net/uring/io_uring.go deleted file mode 100644 index 12a49ef58..000000000 --- a/net/uring/io_uring.go +++ /dev/null @@ -1,14 +0,0 @@ -package uring - -import ( - "errors" - "flag" -) - -var useIOURing = flag.Bool("use-io-uring", true, "attempt to use io_uring if available") - -// NotSupportedError indicates an operation was attempted when io_uring is not supported. -var NotSupportedError = errors.New("io_uring not supported") - -// DisabledError indicates that io_uring was explicitly disabled. -var DisabledError = errors.New("io_uring disabled") diff --git a/net/uring/io_uring_linux.go b/net/uring/io_uring_linux.go index 751560c78..1e72ad6c4 100644 --- a/net/uring/io_uring_linux.go +++ b/net/uring/io_uring_linux.go @@ -78,7 +78,7 @@ func NewUDPConn(pconn net.PacketConn) (*UDPConn, error) { conn, ok := pconn.(*net.UDPConn) if !ok { return nil, fmt.Errorf("cannot use io_uring with conn of type %T", pconn) - } + } // this is dumb local := conn.LocalAddr() var ipp netaddr.IPPort diff --git a/net/uring/io_uring_notlinux.go b/net/uring/io_uring_notlinux.go deleted file mode 100644 index 098e4ac9d..000000000 --- a/net/uring/io_uring_notlinux.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build !linux - -package uring - -import ( - "net" - "os" - "time" - - "inet.af/netaddr" -) - -func URingAvailable() bool { return false } - -// A UDPConn is a recv-only UDP fd manager. -// TODO: Support writes. -// TODO: support multiplexing multiple fds? -// May be more expensive than having multiple urings, and certainly more complicated. -// TODO: API review for performance. -// We'd like to enqueue a bunch of recv calls and deqeueue them later, -// but we have a problem with buffer management: We get our buffers just-in-time -// from wireguard-go, which means we have to make copies. -// That's OK for now, but later it could be a performance issue. -// For now, keep it simple and enqueue/dequeue in a single step. -// TODO: IPv6 -// TODO: Maybe combine the urings into a single uring with dispatch. -type UDPConn struct{} - -func NewUDPConn(conn *net.UDPConn) (*UDPConn, error) { return nil, NotSupportedError } -func (c *UDPConn) LocalAddr() net.Addr { panic("Not supported") } - -func (u *UDPConn) Close() error { return NotSupportedError } -func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - err = NotSupportedError - return -} -func (u *UDPConn) ReadFromNetaddr(buf []byte) (n int, addr netaddr.IPPort, err error) { - err = NotSupportedError - return -} -func (u *UDPConn) WriteTo(p []byte, addr net.Addr) (int, error) { return 0, NotSupportedError } -func (c *UDPConn) SetDeadline(t time.Time) error { return NotSupportedError } -func (c *UDPConn) SetReadDeadline(t time.Time) error { return NotSupportedError } -func (c *UDPConn) SetWriteDeadline(t time.Time) error { return NotSupportedError } - -// A File is a write-only file fd manager. -// TODO: Support reads -// TODO: all the todos from UDPConn -type File struct{} - -func NewFile(file *os.File) (*File, error) { return nil, NotSupportedError } -func (u *File) Write(buf []byte) (int, error) { return 0, NotSupportedError } - -// Read data into buf[offset:]. -// We are allowed to write junk into buf[offset-4:offset]. -func (u *File) Read(buf []byte) (n int, err error) { return 0, NotSupportedError } diff --git a/net/uring/io_uring_test.go b/net/uring/io_uring_test.go deleted file mode 100644 index df8e9fc36..000000000 --- a/net/uring/io_uring_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// +build linux - -package uring - -import ( - "io/ioutil" - "net" - "os" - "testing" -) - -const TestPort = 3636 - -var serverAddr = &net.UDPAddr{ - Port: TestPort, -} - -func NewUDPTestServer(t *testing.T) error { - conn, err := net.ListenUDP("udp", serverAddr) - if err != nil { - return err - } - go func() { - for { - buf := make([]byte, 0, 512) - n, _, err := conn.ReadFromUDP(buf) - if err != nil { - t.Errorf("failed to read on server: %v", err) - break - } - t.Logf("%s, %v, %v", buf, n, err) - } - }() - return nil -} - -func NewUDPIOURingConnTestServer(t *testing.T) error { - conn, err := net.ListenUDP("udp", serverAddr) - if err != nil { - return err - } - go func() { - for { - buf := make([]byte, 0, 512) - n, _, err := conn.ReadFromUDP(buf) - if err != nil { - t.Errorf("failed to read on server: %v", err) - break - } - t.Logf("%s, %v, %v", buf, n, err) - } - }() - return nil -} - -func TestUDPConn(t *testing.T) { - err := NewUDPTestServer(t) - if err != nil { - t.Errorf("failed to start UDPServer: %v", err) - } - udpConn, err := net.DialUDP("udp", nil, serverAddr) - if err != nil { - t.Errorf("failed to start udp connection to server: %v", err) - } - defer udpConn.Close() - - conn, err := NewUDPConn(udpConn) - if err != nil { - t.Errorf("failed to start io_uring udp connection: %v", err) - } - defer conn.Close() - - content := []byte("a test string to check udpconn works 😀 with non-unicode input") - n, err := conn.WriteTo(content, serverAddr) - if err != nil { - t.Errorf("conn write failed: %v", err) - } - if n != len(content) { - t.Errorf("written len mismatch: want %v, got %v", len(content), n) - } - - // Test many writes at once - for i := 0; i < 1000; i++ { - n, err := conn.WriteTo(content, serverAddr) - if err != nil { - t.Errorf("conn write failed: %v", err) - } - if n != len(content) { - t.Errorf("written len mismatch: want %v, got %v", len(content), n) - } - } -} - -func TestFile(t *testing.T) { - tmpFile, err := ioutil.TempFile(".", "uring-test") - if err != nil { - t.Fatalf("failed to create temp file: %v", err) - } - t.Cleanup(func() { - os.Remove(tmpFile.Name()) - }) - f, err := NewFile(tmpFile) - if err != nil { - t.Fatalf("failed to create io_uring file: %v", err) - } - content := []byte("a test string to check writing works 😀 with non-unicode input") - n, err := f.Write(content) - if n != len(content) { - t.Errorf("mismatch between written len and content len: want %d, got %d", len(content), n) - } - if err != nil { - t.Errorf("file write failed: %v", err) - } - if err = f.Close(); err != nil { - t.Errorf("file close failed: %v", err) - } -} diff --git a/net/uring/udp_test.go b/net/uring/udp_test.go index 0a0a01b3d..54808a854 100644 --- a/net/uring/udp_test.go +++ b/net/uring/udp_test.go @@ -43,3 +43,62 @@ func TestUDPSendRecv(t *testing.T) { c.Assert(err, qt.IsNil) c.Assert(recvBuf[:n], qt.DeepEquals, sendBuf) } + +// TODO(jknodt): maybe delete the test below because it's redundant + +const TestPort = 3636 + +var serverAddr = &net.UDPAddr{ + Port: TestPort, +} + +func NewUDPTestServer(t *testing.T) error { + conn, err := net.ListenUDP("udp", serverAddr) + if err != nil { + return err + } + go func() { + for { + buf := make([]byte, 512) + _, _, err := conn.ReadFromUDP(buf) + if err != nil { + t.Errorf("failed to read on server: %v", err) + break + } + } + }() + return nil +} + +func TestUDPConn(t *testing.T) { + if !Available() { + t.Skip("io_uring not available") + } + c := qt.New(t) + // TODO add a closer here + err := NewUDPTestServer(t) + c.Assert(err, qt.IsNil) + udpConn, err := net.DialUDP("udp", nil, serverAddr) + c.Assert(err, qt.IsNil) + defer udpConn.Close() + + conn, err := NewUDPConn(udpConn) + c.Assert(err, qt.IsNil) + defer conn.Close() + + content := []byte("a test string to check udpconn works 😀 with non-unicode input") + n, err := conn.WriteTo(content, serverAddr) + c.Assert(err, qt.IsNil) + if n != len(content) { + t.Errorf("written len mismatch: want %v, got %v", len(content), n) + } + + // Test many writes at once + for i := 0; i < 1000; i++ { + n, err := conn.WriteTo(content, serverAddr) + c.Assert(err, qt.IsNil) + if n != len(content) { + t.Errorf("written len mismatch: want %v, got %v", len(content), n) + } + } +}