xpool

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Jan 2, 2025 License: MIT Imports: 1 Imported by: 0

README

xpool

tag Go Version GoDoc Go Lint codecov Report card CodeQL Dependency Review License Latest release GitHub Release Date Last commit PRs Welcome Mentioned in Awesome Go

The xpool is a user-friendly, type-safe version of sync.Pool.

Inspired by xpool

Definition

This package defines an interface Pool[T any]

// Pool is a type-safe object pool interface.
// for convenience, *sync.Pool is a Pool[any]
type Pool[T any] interface {
    // Get fetch one item from object pool
    // If needed, will create another object.
    Get() T

    // Put return the object to the pull.
    // It may reset the object before put it back to sync pool.
    Put(object T)
}

In such way that *sync.Pool is a Pool[any]

Usage

Imagine you need a pool of io.ReadWrite interfaces implemented by bytes.Buffer. You don't need to cast from interface{} anymore, just do:

    pool := xpool.New(func() io.ReadWriter {
        return new(bytes.Buffer)
    })

    rw := pool.Get()
    defer pool.Put(rw)

    // now you can use a new io.ReadWrite instance

instead using pure go

    pool := &sync.Pool{
        New: func() any {
            return new(bytes.Buffer)
        },
    }

    rw, _ := pool.Get().(io.ReadWriter)
    defer pool.Put(rw)

    // now you can use a new io.ReadWrite instance

Object pools are perfect for that are simple to create, like the ones that have a constructor with no parameters. If we need to specify parameters to create one object, then each combination of parameters may create a different object and they are not easy to use from an object pool.

There are two possible approaches:

  • map all possible parameters and create one object pool for combination.
  • create monadic object that can be easily created and a particular state can be set via some methods.

The second approach we call "Resettable Objects".

Dealing with monadic Resettable Objects

Object pools are perfect for stateless objects, however when dealing with monadic objects we need to be extra careful with the object state. Fortunately, we have some objects that we can easily reset the state before reuse.

Some classes of objects like hash.Hash and bytes.Buffer we can call a method Reset() to return the object to his initial state. Others such bytes.Reader and gzip.Writer have a special meaning for a Reset(state S) to be possible reuse the same object instead create a new one.

We define two forms of Reset:

The Niladic interface, where Reset() receives no arguments (for instance, the hash.Hash case) to be executed before put the object back to the pool.

// Resetter interface.
type Resetter interface {
    Reset()
}

And the Monadic interface, where Reset(S) receives one single argument (for instance, the gzip.Writer case) to be executed when we fetch an object from the pool and initialize with a value of type S, and will be resetted back to a zero value of S before put the object back to the pool.

// Resetter interface.
type Resetter[S any] interface {
    Reset(state S)
}

Monadic resetters are handling by package xpool/monadic.

Important: you may not want to expose objects with a Reset method, the xpool will not ensure that the type T is a Resetter[S] unless you use the NewWithResetter constructor.

Examples

Calling Reset() before put it back to the pool of objects, on xpool package:

    var pool xpool.Pool[hash.Hash] = xpool.NewWithResetter(func() hash.Hash {
        return sha256.New()
    })

    hasher := pool.Get()   // get a new hash.Hash interface
    defer pool.Put(hasher) // reset it with nil before put back to sync pool.

    _, _ = hasher.Write(p)

    value := hasher.Sum(nil)

Calling Reset(v) with some value when acquire the instance and Reset( <zero value> ) before put it back to the pool of objects, on xpool/monadic package:

    // this constructor can't infer type S, so you should be explicit!
    var pool monadic.Pool[[]byte,*bytes.Reader] = monadic.New[[]byte](
        func() *bytes.Reader {
            return bytes.NewReader(nil)
        },
    )

    reader := pool.Get([]byte(`payload`)) // reset the bytes.Reader with payload
    defer pool.Put(reader)                // reset the bytes.Reader with nil

    content, err := io.ReadAll(reader)
Custom Resetters

It is possible set a custom thread-safe Resetter, instead just call Reset() or Reset(v), via a custom resette, instead use the default one.

on xpool package:

    //besides the log, both calls are equivalent

    pool:= xpool.NewWithCustomResetter(sha256.New, 
        func(h hash.Hash) {
            h.Reset()

            log.Println("just reset the hash.Hash")
        },
    ),

    // the default resetter try to call `Reset()` method.
    pool:=  xpool.NewWithDefaultResetter(sha256.New),

on xpool/monadic package:

    // besides the log, both calls are equivalent
    
    // the monadic pool will try to call `Reset([]byte)` method by default.
    pool:= monadic.New[[]byte](func() *bytes.Reader {
        return bytes.NewReader(nil)
    })

    // the monadic pool will try to call the specific resetter callback.
    pool:= monadic.NewWithCustomResetter(func() *bytes.Reader {
        return bytes.NewReader(nil)
    }, func(object *bytes.Reader, state []byte) {
        object.Reset(state)

        log.Println("just reset the *bytes.Buffer")
    })

You can use custom resetters to handle more complex types of Reset. For instance, the flate.NewReader returns an io.ReadCloser that also implements flate.Resetter that supports a different kind of Reset() that expect two arguments and also returns an error.

If we can discard the error and set the second parameter a constant value like nil, we can:

    // can infer types from resetter
    poolReader := monadic.NewWithCustomResetter(func() io.ReadCloser {
        return flate.NewReader(nil)
    }, func(object io.ReadCloser, state io.Reader) {
        if resetter, ok := any(object).(flate.Resetter); ok {
            _ = resetter.Reset(state, nil)
        }
    })

An alternative can be create an object to hold different arguments like in the example below:

    type flateResetterArgs struct {
        r    io.Reader
        dict []byte
    }
    // can infer type S from resetter
    poolReader := monadic.NewWithCustomResetter(func() io.ReadCloser {
        return flate.NewReader(nil)
    }, func(object io.ReadCloser, state *flateResetterArgs) {
        if resetter, ok := any(object).(flate.Resetter); ok {
            _ = resetter.Reset(state.r, state.dict)
        }
    })

Custom resetters can do more than just set the status of the object, they can be used to log, trace and extract metrics.

Important

On xpool the resetter is optional, while on xpool/monadic this is mandatory. If you don't want to have resetters on a monadic xpool, please create a regular xpool.Pool.

Documentation

Overview

xpool is a type safe object pool build on top of sync.Pool

It is easy to use, just give a function that can create an object of a given type T as argument of New and it will return an implementation of Pool interface:

pool := xpool.New(func() io.ReadWriter {
  return new(bytes.Buffer)
})
rw := pool.Get()
defer pool.Put(rw)

For monadic objects, when we need to reset the object to an initial state before put it back to the pool, there are two alternative constructors:

Another alternative is to use https://github.com/peczenyj/xpool/monadic subpackage package.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Pool

type Pool[T any] interface {
	// Get fetch one item from object pool
	// If needed, will create another object.
	Get() T

	// Put return the object to the pull.
	// It may reset the object before put it back to sync pool.
	Put(object T)
}

Pool is a type-safe object pool interface. This interface is parameterized on one generic types:

  • T is reserved for the type of the object that will be stored on the pool.

For convenience, a pointer to sync.Pool is a Pool[any]

func New

func New[T any](
	ctor func() T,
) Pool[T]

New is the constructor of an Pool for a given generic type T. Receives the constructor of the type T.

Example
package main

import (
	"bytes"
	"fmt"
	"io"
	"os"

	"github.com/peczenyj/xpool"
)

func main() {
	// pool can infer T from constructor
	pool := xpool.New(func() io.ReadWriter {
		return new(bytes.Buffer)
	})

	rw := pool.Get()
	defer pool.Put(rw)

	// your favorite usage of rw

	fmt.Fprint(rw, "example")

	_, _ = io.Copy(os.Stdout, rw)
}
Output:

example

func NewWithCustomResetter added in v0.5.0

func NewWithCustomResetter[T any](
	ctor func() T,
	onPutResetter func(T),
) Pool[T]

NewWithDefaultResetter is an alternative constructor of an Pool for a given generic type T. We can specify a special resetter, to be called before return the object from the pool. Be careful, the custom resetter must be thread safe. Will panic if onPutResetter is nil.

Example
package main

import (
	"crypto/sha256"
	"fmt"
	"hash"

	"github.com/peczenyj/xpool"
)

func main() {
	// pool can infer T from constructor
	var pool xpool.Pool[hash.Hash] = xpool.NewWithCustomResetter(sha256.New,
		func(h hash.Hash) {
			h.Reset()
			fmt.Println("hash resetted with success")
		},
	)

	var hasher hash.Hash = pool.Get() // get a new hash.Hash interface

	_, _ = hasher.Write([]byte(`payload`))

	fmt.Printf("%x\n", hasher.Sum(nil))

	pool.Put(hasher) // reset it before put back to sync pool.
}
Output:

239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5
hash resetted with success

func NewWithResetter added in v0.1.0

func NewWithResetter[T Resetter](
	ctor func() T,
) Pool[T]

NewWithResetter is an alternative constructor of an Pool for a given generic type T. T must be a Resetter, before put the object back to object pool we will call Reset().

Example
package main

import (
	"crypto/sha256"
	"fmt"
	"hash"

	"github.com/peczenyj/xpool"
)

func main() {
	// pool can infer T from constructor
	var pool xpool.Pool[hash.Hash] = xpool.NewWithResetter(sha256.New)

	var hasher hash.Hash = pool.Get() // get a new hash.Hash interface
	defer pool.Put(hasher)            // reset it before put back to sync pool.

	_, _ = hasher.Write([]byte(`payload`))

	fmt.Printf("%x\n", hasher.Sum(nil))

}
Output:

239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5

type Resetter added in v0.1.0

type Resetter interface {
	// Reset may return the object to his initial state.
	Reset()
}

Resetter interface.

Directories

Path Synopsis
The intent of monadic is to support monadic objects.
The intent of monadic is to support monadic objects.

Jump to

Keyboard shortcuts

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