Skip to content

Commit 485a963

Browse files
joy999hailaz
andauthored
feat(container/gpool): add generic pool feature (#4493)
add TPool[T] and let Pool base on it. --------- Co-authored-by: hailaz <[email protected]>
1 parent b57b49e commit 485a963

File tree

4 files changed

+307
-128
lines changed

4 files changed

+307
-128
lines changed

container/gpool/gpool.go

Lines changed: 11 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,19 @@
88
package gpool
99

1010
import (
11-
"context"
1211
"time"
13-
14-
"github.com/gogf/gf/v2/container/glist"
15-
"github.com/gogf/gf/v2/container/gtype"
16-
"github.com/gogf/gf/v2/errors/gcode"
17-
"github.com/gogf/gf/v2/errors/gerror"
18-
"github.com/gogf/gf/v2/os/gtime"
19-
"github.com/gogf/gf/v2/os/gtimer"
2012
)
2113

2214
// Pool is an Object-Reusable Pool.
2315
type Pool struct {
24-
list *glist.List // Available/idle items list.
25-
closed *gtype.Bool // Whether the pool is closed.
26-
TTL time.Duration // Time To Live for pool items.
27-
NewFunc func() (any, error) // Callback function to create pool item.
28-
// ExpireFunc is the function for expired items destruction.
29-
// This function needs to be defined when the pool items
30-
// need to perform additional destruction operations.
31-
// Eg: net.Conn, os.File, etc.
32-
ExpireFunc func(any)
33-
}
34-
35-
// Pool item.
36-
type poolItem struct {
37-
value any // Item value.
38-
expireAt int64 // Expire timestamp in milliseconds.
16+
*TPool[any]
3917
}
4018

4119
// NewFunc Creation function for object.
42-
type NewFunc func() (any, error)
20+
type NewFunc = TPoolNewFunc[any]
4321

4422
// ExpireFunc Destruction function for object.
45-
type ExpireFunc func(any)
23+
type ExpireFunc = TPoolExpireFunc[any]
4624

4725
// New creates and returns a new object pool.
4826
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
@@ -52,134 +30,40 @@ type ExpireFunc func(any)
5230
// ttl < 0 : immediate expired after use;
5331
// ttl > 0 : timeout expired;
5432
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
55-
r := &Pool{
56-
list: glist.New(true),
57-
closed: gtype.NewBool(),
58-
TTL: ttl,
59-
NewFunc: newFunc,
60-
}
61-
if len(expireFunc) > 0 {
62-
r.ExpireFunc = expireFunc[0]
33+
return &Pool{
34+
TPool: NewTPool(ttl, newFunc, expireFunc...),
6335
}
64-
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
65-
return r
6636
}
6737

6838
// Put puts an item to pool.
6939
func (p *Pool) Put(value any) error {
70-
if p.closed.Val() {
71-
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
72-
}
73-
item := &poolItem{
74-
value: value,
75-
}
76-
if p.TTL == 0 {
77-
item.expireAt = 0
78-
} else {
79-
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
80-
// So we need calculate the milliseconds using its nanoseconds value.
81-
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
82-
}
83-
p.list.PushBack(item)
84-
return nil
40+
return p.TPool.Put(value)
8541
}
8642

8743
// MustPut puts an item to pool, it panics if any error occurs.
8844
func (p *Pool) MustPut(value any) {
89-
if err := p.Put(value); err != nil {
90-
panic(err)
91-
}
45+
p.TPool.MustPut(value)
9246
}
9347

9448
// Clear clears pool, which means it will remove all items from pool.
9549
func (p *Pool) Clear() {
96-
if p.ExpireFunc != nil {
97-
for {
98-
if r := p.list.PopFront(); r != nil {
99-
p.ExpireFunc(r.(*poolItem).value)
100-
} else {
101-
break
102-
}
103-
}
104-
} else {
105-
p.list.RemoveAll()
106-
}
50+
p.TPool.Clear()
10751
}
10852

10953
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
11054
// it creates and returns one from NewFunc.
11155
func (p *Pool) Get() (any, error) {
112-
for !p.closed.Val() {
113-
if r := p.list.PopFront(); r != nil {
114-
f := r.(*poolItem)
115-
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
116-
return f.value, nil
117-
} else if p.ExpireFunc != nil {
118-
// TODO: move expire function calling asynchronously out from `Get` operation.
119-
p.ExpireFunc(f.value)
120-
}
121-
} else {
122-
break
123-
}
124-
}
125-
if p.NewFunc != nil {
126-
return p.NewFunc()
127-
}
128-
return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
56+
return p.TPool.Get()
12957
}
13058

13159
// Size returns the count of available items of pool.
13260
func (p *Pool) Size() int {
133-
return p.list.Len()
61+
return p.TPool.Size()
13462
}
13563

13664
// Close closes the pool. If `p` has ExpireFunc,
13765
// then it automatically closes all items using this function before it's closed.
13866
// Commonly you do not need to call this function manually.
13967
func (p *Pool) Close() {
140-
p.closed.Set(true)
141-
}
142-
143-
// checkExpire removes expired items from pool in every second.
144-
func (p *Pool) checkExpireItems(ctx context.Context) {
145-
if p.closed.Val() {
146-
// If p has ExpireFunc,
147-
// then it must close all items using this function.
148-
if p.ExpireFunc != nil {
149-
for {
150-
if r := p.list.PopFront(); r != nil {
151-
p.ExpireFunc(r.(*poolItem).value)
152-
} else {
153-
break
154-
}
155-
}
156-
}
157-
gtimer.Exit()
158-
}
159-
// All items do not expire.
160-
if p.TTL == 0 {
161-
return
162-
}
163-
// The latest item expire timestamp in milliseconds.
164-
var latestExpire int64 = -1
165-
// Retrieve the current timestamp in milliseconds, it expires the items
166-
// by comparing with this timestamp. It is not accurate comparison for
167-
// every item expired, but high performance.
168-
var timestampMilli = gtime.TimestampMilli()
169-
for latestExpire <= timestampMilli {
170-
if r := p.list.PopFront(); r != nil {
171-
item := r.(*poolItem)
172-
latestExpire = item.expireAt
173-
// TODO improve the auto-expiration mechanism of the pool.
174-
if item.expireAt > timestampMilli {
175-
p.list.PushFront(item)
176-
break
177-
}
178-
if p.ExpireFunc != nil {
179-
p.ExpireFunc(item.value)
180-
}
181-
} else {
182-
break
183-
}
184-
}
68+
p.TPool.Close()
18569
}

container/gpool/gpool_t.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package gpool
8+
9+
import (
10+
"context"
11+
"time"
12+
13+
"github.com/gogf/gf/v2/container/glist"
14+
"github.com/gogf/gf/v2/container/gtype"
15+
"github.com/gogf/gf/v2/errors/gcode"
16+
"github.com/gogf/gf/v2/errors/gerror"
17+
"github.com/gogf/gf/v2/os/gtime"
18+
"github.com/gogf/gf/v2/os/gtimer"
19+
)
20+
21+
// TPool is an Object-Reusable Pool.
22+
type TPool[T any] struct {
23+
list *glist.TList[*tPoolItem[T]] // Available/idle items list.
24+
closed *gtype.Bool // Whether the pool is closed.
25+
TTL time.Duration // Time To Live for pool items.
26+
NewFunc func() (T, error) // Callback function to create pool item.
27+
// ExpireFunc is the function for expired items destruction.
28+
// This function needs to be defined when the pool items
29+
// need to perform additional destruction operations.
30+
// Eg: net.Conn, os.File, etc.
31+
ExpireFunc func(T)
32+
}
33+
34+
// TPool item.
35+
type tPoolItem[T any] struct {
36+
value T // Item value.
37+
expireAt int64 // Expire timestamp in milliseconds.
38+
}
39+
40+
// TPoolNewFunc Creation function for object.
41+
type TPoolNewFunc[T any] func() (T, error)
42+
43+
// TPoolExpireFunc Destruction function for object.
44+
type TPoolExpireFunc[T any] func(T)
45+
46+
// NewTPool creates and returns a new object pool.
47+
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
48+
//
49+
// Note the expiration logic:
50+
// ttl = 0 : not expired;
51+
// ttl < 0 : immediate expired after use;
52+
// ttl > 0 : timeout expired;
53+
func NewTPool[T any](ttl time.Duration, newFunc TPoolNewFunc[T], expireFunc ...TPoolExpireFunc[T]) *TPool[T] {
54+
r := &TPool[T]{
55+
list: glist.NewT[*tPoolItem[T]](true),
56+
closed: gtype.NewBool(),
57+
TTL: ttl,
58+
NewFunc: newFunc,
59+
}
60+
if len(expireFunc) > 0 {
61+
r.ExpireFunc = expireFunc[0]
62+
}
63+
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
64+
return r
65+
}
66+
67+
// Put puts an item to pool.
68+
func (p *TPool[T]) Put(value T) error {
69+
if p.closed.Val() {
70+
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
71+
}
72+
item := &tPoolItem[T]{
73+
value: value,
74+
}
75+
if p.TTL == 0 {
76+
item.expireAt = 0
77+
} else {
78+
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
79+
// So we need calculate the milliseconds using its nanoseconds value.
80+
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
81+
}
82+
p.list.PushBack(item)
83+
return nil
84+
}
85+
86+
// MustPut puts an item to pool, it panics if any error occurs.
87+
func (p *TPool[T]) MustPut(value T) {
88+
if err := p.Put(value); err != nil {
89+
panic(err)
90+
}
91+
}
92+
93+
// Clear clears pool, which means it will remove all items from pool.
94+
func (p *TPool[T]) Clear() {
95+
if p.ExpireFunc != nil {
96+
for {
97+
if r := p.list.PopFront(); r != nil {
98+
p.ExpireFunc(r.value)
99+
} else {
100+
break
101+
}
102+
}
103+
} else {
104+
p.list.RemoveAll()
105+
}
106+
}
107+
108+
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
109+
// it creates and returns one from NewFunc.
110+
func (p *TPool[T]) Get() (value T, err error) {
111+
for !p.closed.Val() {
112+
if f := p.list.PopFront(); f != nil {
113+
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
114+
return f.value, nil
115+
} else if p.ExpireFunc != nil {
116+
// TODO: move expire function calling asynchronously out from `Get` operation.
117+
p.ExpireFunc(f.value)
118+
}
119+
} else {
120+
break
121+
}
122+
}
123+
if p.NewFunc != nil {
124+
return p.NewFunc()
125+
}
126+
err = gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
127+
return
128+
}
129+
130+
// Size returns the count of available items of pool.
131+
func (p *TPool[T]) Size() int {
132+
return p.list.Len()
133+
}
134+
135+
// Close closes the pool. If `p` has ExpireFunc,
136+
// then it automatically closes all items using this function before it's closed.
137+
// Commonly you do not need to call this function manually.
138+
func (p *TPool[T]) Close() {
139+
p.closed.Set(true)
140+
}
141+
142+
// checkExpire removes expired items from pool in every second.
143+
func (p *TPool[T]) checkExpireItems(ctx context.Context) {
144+
if p.closed.Val() {
145+
// If p has ExpireFunc,
146+
// then it must close all items using this function.
147+
if p.ExpireFunc != nil {
148+
for {
149+
if r := p.list.PopFront(); r != nil {
150+
p.ExpireFunc(r.value)
151+
} else {
152+
break
153+
}
154+
}
155+
}
156+
gtimer.Exit()
157+
}
158+
// All items do not expire.
159+
if p.TTL == 0 {
160+
return
161+
}
162+
// The latest item expire timestamp in milliseconds.
163+
var latestExpire int64 = -1
164+
// Retrieve the current timestamp in milliseconds, it expires the items
165+
// by comparing with this timestamp. It is not accurate comparison for
166+
// every item expired, but high performance.
167+
var timestampMilli = gtime.TimestampMilli()
168+
for latestExpire <= timestampMilli {
169+
if item := p.list.PopFront(); item != nil {
170+
latestExpire = item.expireAt
171+
// TODO improve the auto-expiration mechanism of the pool.
172+
if item.expireAt > timestampMilli {
173+
p.list.PushFront(item)
174+
break
175+
}
176+
if p.ExpireFunc != nil {
177+
p.ExpireFunc(item.value)
178+
}
179+
} else {
180+
break
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)