[chore] update dependencies (#4468)

- github.com/ncruces/go-sqlite3
- codeberg.org/gruf/go-mempool
- codeberg.org/gruf/go-structr (changes related on the above) *
- codeberg.org/gruf/go-mutexes (changes related on the above) *

* this is largely just fiddling around with package internals in structr and mutexes to rely on changes in mempool, which added a new concurrency-safe pool

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4468
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2025-10-03 15:29:41 +02:00 committed by kim
commit ff950e94bb
32 changed files with 706 additions and 317 deletions

View file

@ -1,3 +1,3 @@
# go-mempool
very simple memory pool implementation
very simple memory pool implementation

View file

@ -1,17 +1,17 @@
package mempool
import (
"sync"
"sync/atomic"
"unsafe"
"golang.org/x/sys/cpu"
)
const DefaultDirtyFactor = 128
// Pool provides a type-safe form
// of UnsafePool using generics.
//
// Note it is NOT safe for concurrent
// use, you must protect it yourself!
// Pool provides a form of SimplePool
// with the addition of concurrency safety.
type Pool[T any] struct {
UnsafePool
// New is an optionally provided
// allocator used when no value
@ -21,79 +21,119 @@ type Pool[T any] struct {
// Reset is an optionally provided
// value resetting function called
// on passed value to Put().
Reset func(T)
Reset func(T) bool
}
UnsafePool
func NewPool[T any](new func() T, reset func(T) bool, check func(current, victim int) bool) Pool[T] {
return Pool[T]{
New: new,
Reset: reset,
UnsafePool: NewUnsafePool(check),
}
}
func (p *Pool[T]) Get() T {
if ptr := p.UnsafePool.Get(); ptr != nil {
return *(*T)(ptr)
} else if p.New != nil {
return p.New()
}
var z T
return z
var t T
if p.New != nil {
t = p.New()
}
return t
}
func (p *Pool[T]) Put(t T) {
if p.Reset != nil {
p.Reset(t)
if p.Reset != nil && !p.Reset(t) {
return
}
ptr := unsafe.Pointer(&t)
p.UnsafePool.Put(ptr)
}
// UnsafePool provides an incredibly
// simple memory pool implementation
// that stores ptrs to memory values,
// and regularly flushes internal pool
// structures according to DirtyFactor.
//
// Note it is NOT safe for concurrent
// use, you must protect it yourself!
// UnsafePool provides a form of UnsafeSimplePool
// with the addition of concurrency safety.
type UnsafePool struct {
// DirtyFactor determines the max
// number of $dirty count before
// pool is garbage collected. Where:
// $dirty = len(current) - len(victim)
DirtyFactor int
current []unsafe.Pointer
victim []unsafe.Pointer
internal
_ [cache_line_size - unsafe.Sizeof(internal{})%cache_line_size]byte
}
func (p *UnsafePool) Get() unsafe.Pointer {
// First try current list.
if len(p.current) > 0 {
ptr := p.current[len(p.current)-1]
p.current = p.current[:len(p.current)-1]
func NewUnsafePool(check func(current, victim int) bool) UnsafePool {
return UnsafePool{internal: internal{
pool: UnsafeSimplePool{Check: check},
}}
}
const (
// current platform integer size.
int_size = 32 << (^uint(0) >> 63)
// platform CPU cache line size to avoid false sharing.
cache_line_size = unsafe.Sizeof(cpu.CacheLinePad{})
)
type internal struct {
// fast-access ring-buffer of
// pointers accessible by index.
//
// if Go ever exposes goroutine IDs
// to us we can make this a lot faster.
ring [int_size / 4]unsafe.Pointer
index atomic.Uint64
// underlying pool and
// slow mutex protection.
pool UnsafeSimplePool
mutex sync.Mutex
}
func (p *internal) Check(fn func(current, victim int) bool) func(current, victim int) bool {
p.mutex.Lock()
if fn == nil {
if p.pool.Check == nil {
fn = defaultCheck
} else {
fn = p.pool.Check
}
} else {
p.pool.Check = fn
}
p.mutex.Unlock()
return fn
}
func (p *internal) Get() unsafe.Pointer {
if ptr := atomic.SwapPointer(&p.ring[p.index.Load()%uint64(cap(p.ring))], nil); ptr != nil {
p.index.Add(^uint64(0)) // i.e. -1
return ptr
}
// Fallback to victim.
if len(p.victim) > 0 {
ptr := p.victim[len(p.victim)-1]
p.victim = p.victim[:len(p.victim)-1]
return ptr
}
return nil
p.mutex.Lock()
ptr := p.pool.Get()
p.mutex.Unlock()
return ptr
}
func (p *UnsafePool) Put(ptr unsafe.Pointer) {
p.current = append(p.current, ptr)
// Get dirty factor.
df := p.DirtyFactor
if df == 0 {
df = DefaultDirtyFactor
}
if len(p.current)-len(p.victim) > df {
// Garbage collection!
p.victim = p.current
p.current = nil
func (p *internal) Put(ptr unsafe.Pointer) {
if atomic.CompareAndSwapPointer(&p.ring[p.index.Add(1)%uint64(cap(p.ring))], nil, ptr) {
return
}
p.mutex.Lock()
p.pool.Put(ptr)
p.mutex.Unlock()
}
func (p *internal) GC() {
for i := range p.ring {
atomic.StorePointer(&p.ring[i], nil)
}
p.mutex.Lock()
p.pool.GC()
p.mutex.Unlock()
}
func (p *internal) Size() int {
p.mutex.Lock()
sz := p.pool.Size()
p.mutex.Unlock()
return sz
}

111
vendor/codeberg.org/gruf/go-mempool/simple.go generated vendored Normal file
View file

@ -0,0 +1,111 @@
package mempool
import (
"unsafe"
)
// SimplePool provides a type-safe form
// of UnsafePool using generics.
//
// Note it is NOT safe for concurrent
// use, you must protect it yourself!
type SimplePool[T any] struct {
UnsafeSimplePool
// New is an optionally provided
// allocator used when no value
// is available for use in pool.
New func() T
// Reset is an optionally provided
// value resetting function called
// on passed value to Put().
Reset func(T) bool
}
func (p *SimplePool[T]) Get() T {
if ptr := p.UnsafeSimplePool.Get(); ptr != nil {
return *(*T)(ptr)
}
var t T
if p.New != nil {
t = p.New()
}
return t
}
func (p *SimplePool[T]) Put(t T) {
if p.Reset != nil && !p.Reset(t) {
return
}
ptr := unsafe.Pointer(&t)
p.UnsafeSimplePool.Put(ptr)
}
// UnsafeSimplePool provides an incredibly
// simple memory pool implementation
// that stores ptrs to memory values,
// and regularly flushes internal pool
// structures according to CheckGC().
//
// Note it is NOT safe for concurrent
// use, you must protect it yourself!
type UnsafeSimplePool struct {
// Check determines how often to flush
// internal pools based on underlying
// current and victim pool sizes. It gets
// called on every pool Put() operation.
//
// A flush will start a new current
// pool, make victim the old current,
// and drop the existing victim pool.
Check func(current, victim int) bool
current []unsafe.Pointer
victim []unsafe.Pointer
}
func (p *UnsafeSimplePool) Get() unsafe.Pointer {
// First try current list.
if len(p.current) > 0 {
ptr := p.current[len(p.current)-1]
p.current = p.current[:len(p.current)-1]
return ptr
}
// Fallback to victim.
if len(p.victim) > 0 {
ptr := p.victim[len(p.victim)-1]
p.victim = p.victim[:len(p.victim)-1]
return ptr
}
return nil
}
func (p *UnsafeSimplePool) Put(ptr unsafe.Pointer) {
p.current = append(p.current, ptr)
// Get GC check func.
if p.Check == nil {
p.Check = defaultCheck
}
if p.Check(len(p.current), len(p.victim)) {
p.GC() // garbage collection time!
}
}
func (p *UnsafeSimplePool) GC() {
p.victim = p.current
p.current = nil
}
func (p *UnsafeSimplePool) Size() int {
return len(p.current) + len(p.victim)
}
func defaultCheck(current, victim int) bool {
return current-victim > 128 || victim > 256
}