update with Josh's changes

This commit is contained in:
kadmin 2021-07-06 20:57:16 +00:00
parent c5cb642376
commit 9fd01334cf
8 changed files with 102 additions and 193 deletions

View File

@ -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")

View File

@ -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)
}

View File

@ -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?

View File

@ -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")

View File

@ -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

View File

@ -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 }

View File

@ -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)
}
}

View File

@ -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)
}
}
}