stealthpool

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2021 License: MIT Imports: 7 Imported by: 0

README

Stealthpool

Build Status Go Report Card Go Reference

stealthpool provides a memory pool that allocates blocks off-heap that will NOT be tracked by the garbage collector. The name stealthpool comes from the fact that the memory being allocated by the pool is stealthy and will not be garbage collected ever These blocks should be used in situations where you want to keep garbage collection to a bare minimum. Needless to say, since the GC will not track any of the memory, extreme care must be had in order to avoid memory leaks

Installation

go get -u github.com/Link512/stealthpool

Getting started


// initialize a pool which will allocate a maximum of 100 blocks
pool, err := stealthpool.New(100)
defer pool.Close() // ALWAYS close the pool unless you're very fond of memory leaks

// initialize a pool with custom block size and preallocated blocks
poolCustom, err := stealthpool.New(100, stealthpool.WithBlockSize(8*1024), stealthpool.WithPreAlloc(100))
defer poolCustom.Close() // ALWAYS close the pool unless you're very fond of memory leaks


block, err := poolCustom.Get()
// do some work with block
// then return it exactly as-is to the pool
err = poolCustom.Return(block)

Docs

Go docs together with examples can be found here

Documentation

Overview

Package stealthpool provides a memory pool that allocates blocks off-heap that will NOT be tracked by the garbage collector. The name stealthpool comes from the fact that the memory being allocated by the pool is `stealthy` and will not be garbage collected ever.

These blocks should be used in situations where you want to keep garbage collection to a bare minimum. Needless to say, since the GC will not track any of the memory, extreme care must be had in order to avoid memory leaks:

pool, _ := stealthpool.New(1)
// ...
defer pool.Close() // always call Close to avoid memory leaks

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrPoolFull is returned when the maximum number of allocated blocks has been reached
	ErrPoolFull = errors.New("pool is full")
	// ErrPreallocOutOfBounds is returned when whe number of preallocated blocks requested is either negative or above maxBlocks
	ErrPreallocOutOfBounds = errors.New("prealloc value out of bounds")
	// ErrInvalidBlock is returned when an invalid slice is passed to Return()
	ErrInvalidBlock = errors.New("trying to return invalid block")
)

Functions

This section is empty.

Types

type Pool

type Pool struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

Pool is the off heap memory pool. It it safe to be used concurrently

func New

func New(maxBlocks int, opts ...PoolOpt) (*Pool, error)

New returns a new stealthpool with the given capacity. The configuration options can be used to change how many blocks are preallocated or block size. If preallocation fails (out of memory, etc), a cleanup of all previously preallocated will be attempted

Example
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(2)
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	block, err := pool.Get()
	if err != nil {
		panic(err)
	}
	fmt.Printf("len(block): %d cap(block): %d\n", len(block), cap(block))
}
Output:

len(block): 4096 cap(block): 4096
Example (CustomBlockSize)
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(2, stealthpool.WithBlockSize(8*1024*1024))
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	block, err := pool.Get()
	if err != nil {
		panic(err)
	}
	fmt.Printf("len(block): %d cap(block): %d\n", len(block), cap(block))

}
Output:

len(block): 8388608 cap(block): 8388608
Example (Preallocation)
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(2, stealthpool.WithPreAlloc(2))
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	fmt.Printf("free blocks: %d total allocated: %d\n", pool.FreeCount(), pool.AllocCount())

	block, err := pool.Get()
	if err != nil {
		panic(err)
	}
	fmt.Printf("len(block): %d cap(block): %d\n", len(block), cap(block))

}
Output:

free blocks: 2 total allocated: 2
len(block): 4096 cap(block): 4096

func (*Pool) AllocCount

func (p *Pool) AllocCount() int

AllocCount returns the total number of allocated blocks so far

Example
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(1)
	if err != nil {
		panic(err)
	}
	fmt.Printf("total allocated: %d\n", pool.AllocCount())

	_, _ = pool.Get()
	fmt.Printf("total allocated: %d\n", pool.AllocCount())

	pool.Close()

	newPool, err := stealthpool.New(3, stealthpool.WithPreAlloc(2))
	if err != nil {
		panic(err)
	}
	fmt.Printf("total allocated: %d\n", newPool.AllocCount())
	newPool.Close()

}
Output:

total allocated: 0
total allocated: 1
total allocated: 2

func (*Pool) Close

func (p *Pool) Close() error

Close will cleanup the memory pool and deallocate ALL previously allocated blocks. Using any of the blocks returned from Get() after a call to Close() will result in a panic

Example
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(2)
	if err != nil {
		panic(err)
	}

	block1, err := pool.Get()
	if err != nil {
		panic(err)
	}

	block2, err := pool.Get()
	if err != nil {
		panic(err)
	}

	if err := pool.Close(); err != nil {
		panic(err)
	}

	// using the slices after Close() will panic
	fmt.Println(block1[:5])
	fmt.Println(block2[3:])
}
Output:

func (*Pool) FreeCount

func (p *Pool) FreeCount() int

FreeCount returns the number of free blocks that can be reused

Example
package main

import (
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(1)
	if err != nil {
		panic(err)
	}
	fmt.Printf("free blocks: %d\n", pool.FreeCount())

	block, _ := pool.Get()
	fmt.Printf("free blocks: %d\n", pool.FreeCount())

	_ = pool.Return(block)
	fmt.Printf("free blocks: %d\n", pool.FreeCount())
	pool.Close()

	newPool, err := stealthpool.New(3, stealthpool.WithPreAlloc(2))
	if err != nil {
		panic(err)
	}
	fmt.Printf("free blocks: %d\n", newPool.FreeCount())
	newPool.Close()

}
Output:

free blocks: 0
free blocks: 0
free blocks: 1
free blocks: 2

func (*Pool) Get

func (p *Pool) Get() ([]byte, error)

Get returns a memory block. It will first try and retrieve a previously allocated block and if that's not possible, will allocate a new block. If there were maxBlocks blocks already allocated, returns ErrPoolFull

Example
package main

import (
	"errors"
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(1)
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	block, err := pool.Get()
	if err != nil {
		panic(err)
	}
	fmt.Printf("len(block): %d cap(block): %d\n", len(block), cap(block))
	fmt.Printf("free blocks: %d total allocated: %d\n", pool.FreeCount(), pool.AllocCount())

	_, err = pool.Get()
	fmt.Printf("pool is full: %t\n", errors.Is(stealthpool.ErrPoolFull, err))

}
Output:

len(block): 4096 cap(block): 4096
free blocks: 0 total allocated: 1
pool is full: true

func (*Pool) Return

func (p *Pool) Return(b []byte) error

Return gives back a block retrieved from Get and stores it for future re-use. The block has to be exactly the same slice object returned from Get(), otherwise ErrInvalidBlock will be returned.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/Link512/stealthpool"
)

func main() {
	pool, err := stealthpool.New(1)
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	block, err := pool.Get()
	if err != nil {
		panic(err)
	}
	fmt.Printf("free blocks: %d total allocated: %d\n", pool.FreeCount(), pool.AllocCount())

	err = pool.Return(block[1:])
	fmt.Printf("resliced block is invalid: %t\n", errors.Is(stealthpool.ErrInvalidBlock, err))

	// once block is returned, the pool will re-use it in a subsequent Get call
	err = pool.Return(block)
	if err != nil {
		panic(err)
	}
	fmt.Printf("free blocks: %d total allocated: %d\n", pool.FreeCount(), pool.AllocCount())

	newBlock, err := pool.Get()
	if err != nil {
		panic(err)
	}

	fmt.Printf("block was reused: %t\n", &block[0] == &newBlock[0])

}
Output:

free blocks: 0 total allocated: 1
resliced block is invalid: true
free blocks: 1 total allocated: 1
block was reused: true

type PoolOpt

type PoolOpt func(*poolOpts)

PoolOpt is a configuration option for a stealthpool

func WithBlockSize

func WithBlockSize(blockSize int) PoolOpt

WithBlockSize specifies the block size that will be returned. It is highly advised that this block size be a multiple of 4KB or whatever value `os.Getpagesize()`, since the mmap syscall returns page aligned memory

func WithPreAlloc

func WithPreAlloc(prealloc int) PoolOpt

WithPreAlloc specifies how many blocks the pool should preallocate on initialization. Default is 0.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL