mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
184 lines
5.0 KiB
Go
184 lines
5.0 KiB
Go
|
|
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||
|
|
//
|
||
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
||
|
|
// If a copy of the MIT was not distributed with this file,
|
||
|
|
// You can obtain one at https://github.com/gogf/gf.
|
||
|
|
|
||
|
|
package gpool
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/gogf/gf/v2/container/glist"
|
||
|
|
"github.com/gogf/gf/v2/container/gtype"
|
||
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
||
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
||
|
|
"github.com/gogf/gf/v2/os/gtime"
|
||
|
|
"github.com/gogf/gf/v2/os/gtimer"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TPool is an Object-Reusable Pool.
|
||
|
|
type TPool[T any] struct {
|
||
|
|
list *glist.TList[*tPoolItem[T]] // Available/idle items list.
|
||
|
|
closed *gtype.Bool // Whether the pool is closed.
|
||
|
|
TTL time.Duration // Time To Live for pool items.
|
||
|
|
NewFunc func() (T, error) // Callback function to create pool item.
|
||
|
|
// ExpireFunc is the function for expired items destruction.
|
||
|
|
// This function needs to be defined when the pool items
|
||
|
|
// need to perform additional destruction operations.
|
||
|
|
// Eg: net.Conn, os.File, etc.
|
||
|
|
ExpireFunc func(T)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TPool item.
|
||
|
|
type tPoolItem[T any] struct {
|
||
|
|
value T // Item value.
|
||
|
|
expireAt int64 // Expire timestamp in milliseconds.
|
||
|
|
}
|
||
|
|
|
||
|
|
// TPoolNewFunc Creation function for object.
|
||
|
|
type TPoolNewFunc[T any] func() (T, error)
|
||
|
|
|
||
|
|
// TPoolExpireFunc Destruction function for object.
|
||
|
|
type TPoolExpireFunc[T any] func(T)
|
||
|
|
|
||
|
|
// NewTPool creates and returns a new object pool.
|
||
|
|
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||
|
|
//
|
||
|
|
// Note the expiration logic:
|
||
|
|
// ttl = 0 : not expired;
|
||
|
|
// ttl < 0 : immediate expired after use;
|
||
|
|
// ttl > 0 : timeout expired;
|
||
|
|
func NewTPool[T any](ttl time.Duration, newFunc TPoolNewFunc[T], expireFunc ...TPoolExpireFunc[T]) *TPool[T] {
|
||
|
|
r := &TPool[T]{
|
||
|
|
list: glist.NewT[*tPoolItem[T]](true),
|
||
|
|
closed: gtype.NewBool(),
|
||
|
|
TTL: ttl,
|
||
|
|
NewFunc: newFunc,
|
||
|
|
}
|
||
|
|
if len(expireFunc) > 0 {
|
||
|
|
r.ExpireFunc = expireFunc[0]
|
||
|
|
}
|
||
|
|
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
|
||
|
|
return r
|
||
|
|
}
|
||
|
|
|
||
|
|
// Put puts an item to pool.
|
||
|
|
func (p *TPool[T]) Put(value T) error {
|
||
|
|
if p.closed.Val() {
|
||
|
|
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
|
||
|
|
}
|
||
|
|
item := &tPoolItem[T]{
|
||
|
|
value: value,
|
||
|
|
}
|
||
|
|
if p.TTL == 0 {
|
||
|
|
item.expireAt = 0
|
||
|
|
} else {
|
||
|
|
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
|
||
|
|
// So we need calculate the milliseconds using its nanoseconds value.
|
||
|
|
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
|
||
|
|
}
|
||
|
|
p.list.PushBack(item)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// MustPut puts an item to pool, it panics if any error occurs.
|
||
|
|
func (p *TPool[T]) MustPut(value T) {
|
||
|
|
if err := p.Put(value); err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear clears pool, which means it will remove all items from pool.
|
||
|
|
func (p *TPool[T]) Clear() {
|
||
|
|
if p.ExpireFunc != nil {
|
||
|
|
for {
|
||
|
|
if r := p.list.PopFront(); r != nil {
|
||
|
|
p.ExpireFunc(r.value)
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
p.list.RemoveAll()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
|
||
|
|
// it creates and returns one from NewFunc.
|
||
|
|
func (p *TPool[T]) Get() (value T, err error) {
|
||
|
|
for !p.closed.Val() {
|
||
|
|
if f := p.list.PopFront(); f != nil {
|
||
|
|
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
|
||
|
|
return f.value, nil
|
||
|
|
} else if p.ExpireFunc != nil {
|
||
|
|
// TODO: move expire function calling asynchronously out from `Get` operation.
|
||
|
|
p.ExpireFunc(f.value)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if p.NewFunc != nil {
|
||
|
|
return p.NewFunc()
|
||
|
|
}
|
||
|
|
err = gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Size returns the count of available items of pool.
|
||
|
|
func (p *TPool[T]) Size() int {
|
||
|
|
return p.list.Len()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close closes the pool. If `p` has ExpireFunc,
|
||
|
|
// then it automatically closes all items using this function before it's closed.
|
||
|
|
// Commonly you do not need to call this function manually.
|
||
|
|
func (p *TPool[T]) Close() {
|
||
|
|
p.closed.Set(true)
|
||
|
|
}
|
||
|
|
|
||
|
|
// checkExpire removes expired items from pool in every second.
|
||
|
|
func (p *TPool[T]) checkExpireItems(ctx context.Context) {
|
||
|
|
if p.closed.Val() {
|
||
|
|
// If p has ExpireFunc,
|
||
|
|
// then it must close all items using this function.
|
||
|
|
if p.ExpireFunc != nil {
|
||
|
|
for {
|
||
|
|
if r := p.list.PopFront(); r != nil {
|
||
|
|
p.ExpireFunc(r.value)
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
gtimer.Exit()
|
||
|
|
}
|
||
|
|
// All items do not expire.
|
||
|
|
if p.TTL == 0 {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
// The latest item expire timestamp in milliseconds.
|
||
|
|
var latestExpire int64 = -1
|
||
|
|
// Retrieve the current timestamp in milliseconds, it expires the items
|
||
|
|
// by comparing with this timestamp. It is not accurate comparison for
|
||
|
|
// every item expired, but high performance.
|
||
|
|
var timestampMilli = gtime.TimestampMilli()
|
||
|
|
for latestExpire <= timestampMilli {
|
||
|
|
if item := p.list.PopFront(); item != nil {
|
||
|
|
latestExpire = item.expireAt
|
||
|
|
// TODO improve the auto-expiration mechanism of the pool.
|
||
|
|
if item.expireAt > timestampMilli {
|
||
|
|
p.list.PushFront(item)
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if p.ExpireFunc != nil {
|
||
|
|
p.ExpireFunc(item.value)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|