Skip to content

Commit

Permalink
multi: add buffer.Write and pool.WriteBuffer, make GCQueue generic
Browse files Browse the repository at this point in the history
  • Loading branch information
cfromknecht committed Feb 16, 2019
1 parent ca4226d commit 6f96d04
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 200 deletions.
44 changes: 44 additions & 0 deletions buffer/buffer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package buffer_test

import (
"bytes"
"testing"

"github.com/lightningnetwork/lnd/buffer"
)

// TestRecycleSlice asserts that RecycleSlice always zeros a byte slice.
func TestRecycleSlice(t *testing.T) {
tests := []struct {
name string
slice []byte
}{
{
name: "length zero",
},
{
name: "length one",
slice: []byte("a"),
},
{
name: "length power of two length",
slice: bytes.Repeat([]byte("b"), 16),
},
{
name: "length non power of two",
slice: bytes.Repeat([]byte("c"), 27),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
buffer.RecycleSlice(test.slice)

expSlice := make([]byte, len(test.slice))
if !bytes.Equal(expSlice, test.slice) {
t.Fatalf("slice not recycled, want: %v, got: %v",
expSlice, test.slice)
}
})
}
}
17 changes: 17 additions & 0 deletions buffer/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package buffer

// RecycleSlice zeroes byte slice, making it fresh for another use.
// Zeroing the buffer using a logarithmic number of calls to the optimized copy
// method. Benchmarking shows this to be ~30 times faster than a for loop that
// sets each index to 0 for ~65KB buffers use for wire messages. Inspired by:
// https://stackoverflow.com/questions/30614165/is-there-analog-of-memset-in-go
func RecycleSlice(b []byte) {
if len(b) == 0 {
return
}

b[0] = 0
for i := 1; i < len(b); i *= 2 {
copy(b[i:], b[:i])
}
}
19 changes: 19 additions & 0 deletions buffer/write.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package buffer

import (
"github.com/lightningnetwork/lnd/lnwire"
)

// WriteSize represents the size of the maximum plaintext message than can be
// sent using brontide. The buffer does not include extra space for the MAC, as
// that is applied by the Noise protocol after encrypting the plaintext.
const WriteSize = lnwire.MaxMessagePayload

// Write is static byte array occupying to maximum-allowed plaintext-message
// size.
type Write [WriteSize]byte

// Recycle zeroes the Write, making it fresh for another use.
func (b *Write) Recycle() {
RecycleSlice(b[:])
}
79 changes: 0 additions & 79 deletions lnpeer/write_buffer_pool.go

This file was deleted.

67 changes: 0 additions & 67 deletions lnpeer/write_buffer_pool_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/davecgh/go-spew/spew"

"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/buffer"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
Expand Down Expand Up @@ -198,7 +199,7 @@ type peer struct {
// messages to write out directly on the socket. By re-using this
// buffer, we avoid needing to allocate more memory each time a new
// message is to be sent to a peer.
writeBuf *lnpeer.WriteBuffer
writeBuf *buffer.Write

queueQuit chan struct{}
quit chan struct{}
Expand Down
52 changes: 52 additions & 0 deletions pool/recycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package pool

import (
"time"

"github.com/lightningnetwork/lnd/queue"
)

// Recycler is an interface that allows an object to be reclaimed without
// needing to be returned to the runtime.
type Recycler interface {
// Recycle resets the object to its default state.
Recycle()
}

// Recycle is a generic queue for recycling objects implementing the Recycler
// interface. It is backed by an underlying queue.GCQueue, and invokes the
// Recycle method on returned objects before returning them to the queue.
type Recycle struct {
queue *queue.GCQueue
}

// NewRecycle initializes a fresh Recycle instance.
func NewRecycle(newItem func() interface{}, returnQueueSize int,
gcInterval, expiryInterval time.Duration) *Recycle {

return &Recycle{
queue: queue.NewGCQueue(
newItem, returnQueueSize,
gcInterval, expiryInterval,
),
}
}

// Take returns an element from the pool.
func (r *Recycle) Take() interface{} {
return r.queue.Take()
}

// Return returns an item implementing the Recycler interface to the pool. The
// Recycle method is invoked before returning the item to improve performance
// and utilization under load.
func (r *Recycle) Return(item Recycler) {
// Recycle the item to ensure that a dirty instance is never offered
// from Take. The call is done here so that the CPU cycles spent
// clearing the buffer are owned by the caller, and not by the queue
// itself. This makes the queue more likely to be available to deliver
// items in the free list.
item.Recycle()

r.queue.Return(item)
}
Loading

0 comments on commit 6f96d04

Please sign in to comment.