no locks all speed

This commit is contained in:
JackDoan
2025-11-11 20:35:51 -06:00
parent c026e8624a
commit 685ac3e112
6 changed files with 17 additions and 72 deletions

View File

@@ -3,6 +3,9 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"log"
"net/http"
_ "net/http/pprof"
"os" "os"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -58,6 +61,10 @@ func main() {
os.Exit(1) os.Exit(1)
} }
go func() {
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
if !*configTest { if !*configTest {
ctrl.Start() ctrl.Start()
notifyReady(l) notifyReady(l)

View File

@@ -32,8 +32,6 @@ type Device struct {
ReceiveQueue *virtqueue.SplitQueue ReceiveQueue *virtqueue.SplitQueue
TransmitQueue *virtqueue.SplitQueue TransmitQueue *virtqueue.SplitQueue
extraRx []virtqueue.UsedElement
} }
// NewDevice initializes a new vhost networking device within the // NewDevice initializes a new vhost networking device within the
@@ -279,7 +277,6 @@ func (dev *Device) TransmitPackets(vnethdr virtio.NetHdr, packets [][]byte) erro
} }
} }
//todo blocking here suxxxx
// Wait for the packet to have been transmitted. // Wait for the packet to have been transmitted.
for i := range chainIndexes { for i := range chainIndexes {
@@ -297,7 +294,7 @@ func (dev *Device) TransmitPackets(vnethdr virtio.NetHdr, packets [][]byte) erro
// processChains processes as many chains as needed to create one packet. The number of processed chains is returned. // processChains processes as many chains as needed to create one packet. The number of processed chains is returned.
func (dev *Device) processChains(pkt *packet.VirtIOPacket, chains []virtqueue.UsedElement) (int, error) { func (dev *Device) processChains(pkt *packet.VirtIOPacket, chains []virtqueue.UsedElement) (int, error) {
//read first element to see how many descriptors we need: //read first element to see how many descriptors we need:
pkt.Payload = pkt.Payload[:cap(pkt.Payload)] pkt.Reset()
n, err := dev.ReceiveQueue.GetDescriptorChainContents(uint16(chains[0].DescriptorIndex), pkt.Payload, int(chains[0].Length)) //todo n, err := dev.ReceiveQueue.GetDescriptorChainContents(uint16(chains[0].DescriptorIndex), pkt.Payload, int(chains[0].Length)) //todo
if err != nil { if err != nil {
return 0, err return 0, err
@@ -321,7 +318,7 @@ func (dev *Device) processChains(pkt *packet.VirtIOPacket, chains []virtqueue.Us
} }
//shift the buffer out of out: //shift the buffer out of out:
copy(pkt.Payload, pkt.Payload[virtio.NetHdrSize:]) pkt.Payload = pkt.Payload[virtio.NetHdrSize:]
cursor := n - virtio.NetHdrSize cursor := n - virtio.NetHdrSize

View File

@@ -47,8 +47,6 @@ type AvailableRing struct {
// avoid issues in case a device may try to access it, contrary to the // avoid issues in case a device may try to access it, contrary to the
// virtio specification. // virtio specification.
usedEvent *uint16 usedEvent *uint16
//mu sync.Mutex
} }
// newAvailableRing creates an available ring that uses the given underlying // newAvailableRing creates an available ring that uses the given underlying

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"sync"
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -54,8 +53,6 @@ type DescriptorTable struct {
bufferBase uintptr bufferBase uintptr
bufferSize int bufferSize int
itemSize int itemSize int
mu sync.Mutex
} }
// newDescriptorTable creates a descriptor table that uses the given underlying // newDescriptorTable creates a descriptor table that uses the given underlying
@@ -126,9 +123,6 @@ func (dt *DescriptorTable) initializeDescriptors() error {
return fmt.Errorf("allocate buffer memory for descriptors: %w", err) return fmt.Errorf("allocate buffer memory for descriptors: %w", err)
} }
dt.mu.Lock()
defer dt.mu.Unlock()
// Store the base for cleanup later // Store the base for cleanup later
dt.bufferBase = uintptr(basePtr) dt.bufferBase = uintptr(basePtr)
dt.bufferSize = totalSize dt.bufferSize = totalSize
@@ -155,9 +149,6 @@ func (dt *DescriptorTable) initializeDescriptors() error {
// collect potential errors before returning them. // collect potential errors before returning them.
// The descriptor table should no longer be used after calling this. // The descriptor table should no longer be used after calling this.
func (dt *DescriptorTable) releaseBuffers() error { func (dt *DescriptorTable) releaseBuffers() error {
dt.mu.Lock()
defer dt.mu.Unlock()
for i := range dt.descriptors { for i := range dt.descriptors {
descriptor := &dt.descriptors[i] descriptor := &dt.descriptors[i]
descriptor.address = 0 descriptor.address = 0
@@ -209,9 +200,6 @@ func (dt *DescriptorTable) createDescriptorChain(outBuffers [][]byte, numInBuffe
return 0, ErrDescriptorChainEmpty return 0, ErrDescriptorChainEmpty
} }
dt.mu.Lock()
defer dt.mu.Unlock()
// Do we still have enough free descriptors? // Do we still have enough free descriptors?
if numDesc > dt.freeNum { if numDesc > dt.freeNum {
return 0, ErrNotEnoughFreeDescriptors return 0, ErrNotEnoughFreeDescriptors
@@ -309,9 +297,6 @@ func (dt *DescriptorTable) getDescriptorChain(head uint16) (outBuffers, inBuffer
return nil, nil, fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain) return nil, nil, fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain)
} }
dt.mu.Lock()
defer dt.mu.Unlock()
// Iterate over the chain. The iteration is limited to the queue size to // Iterate over the chain. The iteration is limited to the queue size to
// avoid ending up in an endless loop when things go very wrong. // avoid ending up in an endless loop when things go very wrong.
next := head next := head
@@ -354,9 +339,6 @@ func (dt *DescriptorTable) getDescriptorChainContents(head uint16, out []byte, m
return 0, fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain) return 0, fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain)
} }
dt.mu.Lock()
defer dt.mu.Unlock()
// Iterate over the chain. The iteration is limited to the queue size to // Iterate over the chain. The iteration is limited to the queue size to
// avoid ending up in an endless loop when things go very wrong. // avoid ending up in an endless loop when things go very wrong.
@@ -431,9 +413,6 @@ func (dt *DescriptorTable) freeDescriptorChain(head uint16) error {
return fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain) return fmt.Errorf("%w: index out of range", ErrInvalidDescriptorChain)
} }
dt.mu.Lock()
defer dt.mu.Unlock()
// Iterate over the chain. The iteration is limited to the queue size to // Iterate over the chain. The iteration is limited to the queue size to
// avoid ending up in an endless loop when things go very wrong. // avoid ending up in an endless loop when things go very wrong.
next := head next := head

View File

@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"sync"
"syscall" "syscall"
"github.com/slackhq/nebula/overlay/eventfd" "github.com/slackhq/nebula/overlay/eventfd"
@@ -35,9 +34,6 @@ type SplitQueue struct {
// used buffer notifications. It blocks until the goroutine ended. // used buffer notifications. It blocks until the goroutine ended.
stop func() error stop func() error
// offerMutex is used to synchronize calls to
// [SplitQueue.OfferDescriptorChain].
offerMutex sync.Mutex
pageSize int pageSize int
itemSize int itemSize int
@@ -140,25 +136,21 @@ func NewSplitQueue(queueSize int) (_ *SplitQueue, err error) {
// Size returns the size of this queue, which is the number of entries/buffers // Size returns the size of this queue, which is the number of entries/buffers
// this queue can hold. // this queue can hold.
func (sq *SplitQueue) Size() int { func (sq *SplitQueue) Size() int {
sq.ensureInitialized()
return sq.size return sq.size
} }
// DescriptorTable returns the [DescriptorTable] behind this queue. // DescriptorTable returns the [DescriptorTable] behind this queue.
func (sq *SplitQueue) DescriptorTable() *DescriptorTable { func (sq *SplitQueue) DescriptorTable() *DescriptorTable {
sq.ensureInitialized()
return sq.descriptorTable return sq.descriptorTable
} }
// AvailableRing returns the [AvailableRing] behind this queue. // AvailableRing returns the [AvailableRing] behind this queue.
func (sq *SplitQueue) AvailableRing() *AvailableRing { func (sq *SplitQueue) AvailableRing() *AvailableRing {
sq.ensureInitialized()
return sq.availableRing return sq.availableRing
} }
// UsedRing returns the [UsedRing] behind this queue. // UsedRing returns the [UsedRing] behind this queue.
func (sq *SplitQueue) UsedRing() *UsedRing { func (sq *SplitQueue) UsedRing() *UsedRing {
sq.ensureInitialized()
return sq.usedRing return sq.usedRing
} }
@@ -166,7 +158,6 @@ func (sq *SplitQueue) UsedRing() *UsedRing {
// The returned file descriptor should be used with great care to not interfere // The returned file descriptor should be used with great care to not interfere
// with this implementation. // with this implementation.
func (sq *SplitQueue) KickEventFD() int { func (sq *SplitQueue) KickEventFD() int {
sq.ensureInitialized()
return sq.kickEventFD.FD() return sq.kickEventFD.FD()
} }
@@ -174,7 +165,6 @@ func (sq *SplitQueue) KickEventFD() int {
// The returned file descriptor should be used with great care to not interfere // The returned file descriptor should be used with great care to not interfere
// with this implementation. // with this implementation.
func (sq *SplitQueue) CallEventFD() int { func (sq *SplitQueue) CallEventFD() int {
sq.ensureInitialized()
return sq.callEventFD.FD() return sq.callEventFD.FD()
} }
@@ -276,15 +266,6 @@ func (sq *SplitQueue) BlockAndGetHeadsCapped(ctx context.Context, maxToTake int)
// and any further calls to [SplitQueue.OfferDescriptorChain] will stall. // and any further calls to [SplitQueue.OfferDescriptorChain] will stall.
func (sq *SplitQueue) OfferInDescriptorChains(numInBuffers int) (uint16, error) { func (sq *SplitQueue) OfferInDescriptorChains(numInBuffers int) (uint16, error) {
sq.ensureInitialized()
// Synchronize the offering of descriptor chains. While the descriptor table
// and available ring are synchronized on their own as well, this does not
// protect us from interleaved calls which could cause reordering.
// By locking here, we can ensure that all descriptor chains are made
// available to the device in the same order as this method was called.
sq.offerMutex.Lock()
defer sq.offerMutex.Unlock()
// Create a descriptor chain for the given buffers. // Create a descriptor chain for the given buffers.
var ( var (
head uint16 head uint16
@@ -317,21 +298,11 @@ func (sq *SplitQueue) OfferInDescriptorChains(numInBuffers int) (uint16, error)
} }
func (sq *SplitQueue) OfferOutDescriptorChains(prepend []byte, outBuffers [][]byte) ([]uint16, error) { func (sq *SplitQueue) OfferOutDescriptorChains(prepend []byte, outBuffers [][]byte) ([]uint16, error) {
sq.ensureInitialized()
// TODO change this // TODO change this
// Each descriptor can only hold a whole memory page, so split large out // Each descriptor can only hold a whole memory page, so split large out
// buffers into multiple smaller ones. // buffers into multiple smaller ones.
outBuffers = splitBuffers(outBuffers, sq.pageSize) outBuffers = splitBuffers(outBuffers, sq.pageSize)
// Synchronize the offering of descriptor chains. While the descriptor table
// and available ring are synchronized on their own as well, this does not
// protect us from interleaved calls which could cause reordering.
// By locking here, we can ensure that all descriptor chains are made
// available to the device in the same order as this method was called.
sq.offerMutex.Lock()
defer sq.offerMutex.Unlock()
chains := make([]uint16, len(outBuffers)) chains := make([]uint16, len(outBuffers))
// Create a descriptor chain for the given buffers. // Create a descriptor chain for the given buffers.
@@ -384,12 +355,10 @@ func (sq *SplitQueue) OfferOutDescriptorChains(prepend []byte, outBuffers [][]by
// longer using them. They must not be accessed after // longer using them. They must not be accessed after
// [SplitQueue.FreeDescriptorChain] has been called. // [SplitQueue.FreeDescriptorChain] has been called.
func (sq *SplitQueue) GetDescriptorChain(head uint16) (outBuffers, inBuffers [][]byte, err error) { func (sq *SplitQueue) GetDescriptorChain(head uint16) (outBuffers, inBuffers [][]byte, err error) {
sq.ensureInitialized()
return sq.descriptorTable.getDescriptorChain(head) return sq.descriptorTable.getDescriptorChain(head)
} }
func (sq *SplitQueue) GetDescriptorChainContents(head uint16, out []byte, maxLen int) (int, error) { func (sq *SplitQueue) GetDescriptorChainContents(head uint16, out []byte, maxLen int) (int, error) {
sq.ensureInitialized()
return sq.descriptorTable.getDescriptorChainContents(head, out, maxLen) return sq.descriptorTable.getDescriptorChainContents(head, out, maxLen)
} }
@@ -412,17 +381,6 @@ func (sq *SplitQueue) FreeDescriptorChain(head uint16) error {
} }
func (sq *SplitQueue) RecycleDescriptorChains(chains []UsedElement) error { func (sq *SplitQueue) RecycleDescriptorChains(chains []UsedElement) error {
sq.ensureInitialized()
//todo I don't think we need this here?
// Synchronize the offering of descriptor chains. While the descriptor table
// and available ring are synchronized on their own as well, this does not
// protect us from interleaved calls which could cause reordering.
// By locking here, we can ensure that all descriptor chains are made
// available to the device in the same order as this method was called.
//sq.offerMutex.Lock()
//defer sq.offerMutex.Unlock()
//todo not doing this may break eventually? //todo not doing this may break eventually?
//not called under lock //not called under lock
//if err := sq.descriptorTable.freeDescriptorChain(head); err != nil { //if err := sq.descriptorTable.freeDescriptorChain(head); err != nil {

View File

@@ -6,11 +6,17 @@ import (
type VirtIOPacket struct { type VirtIOPacket struct {
Payload []byte Payload []byte
buf []byte
Header virtio.NetHdr Header virtio.NetHdr
} }
func NewVIO() *VirtIOPacket { func NewVIO() *VirtIOPacket {
out := new(VirtIOPacket) out := new(VirtIOPacket)
out.Payload = make([]byte, Size) out.Payload = make([]byte, Size)
out.buf = out.Payload
return out return out
} }
func (v *VirtIOPacket) Reset() {
v.Payload = v.buf[:Size]
}