github.com/wangyougui/gf/v2@v2.6.5/container/gpool/gpool.go (about) 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/wangyougui/gf. 6 7 // Package gpool provides object-reusable concurrent-safe pool. 8 package gpool 9 10 import ( 11 "context" 12 "time" 13 14 "github.com/wangyougui/gf/v2/container/glist" 15 "github.com/wangyougui/gf/v2/container/gtype" 16 "github.com/wangyougui/gf/v2/errors/gcode" 17 "github.com/wangyougui/gf/v2/errors/gerror" 18 "github.com/wangyougui/gf/v2/os/gtime" 19 "github.com/wangyougui/gf/v2/os/gtimer" 20 ) 21 22 // Pool is an Object-Reusable Pool. 23 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() (interface{}, 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(interface{}) 33 } 34 35 // Pool item. 36 type poolItem struct { 37 value interface{} // Item value. 38 expireAt int64 // Expire timestamp in milliseconds. 39 } 40 41 // NewFunc Creation function for object. 42 type NewFunc func() (interface{}, error) 43 44 // ExpireFunc Destruction function for object. 45 type ExpireFunc func(interface{}) 46 47 // New creates and returns a new object pool. 48 // To ensure execution efficiency, the expiration time cannot be modified once it is set. 49 // 50 // Note the expiration logic: 51 // ttl = 0 : not expired; 52 // ttl < 0 : immediate expired after use; 53 // ttl > 0 : timeout expired; 54 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] 63 } 64 gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems) 65 return r 66 } 67 68 // Put puts an item to pool. 69 func (p *Pool) Put(value interface{}) 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 85 } 86 87 // MustPut puts an item to pool, it panics if any error occurs. 88 func (p *Pool) MustPut(value interface{}) { 89 if err := p.Put(value); err != nil { 90 panic(err) 91 } 92 } 93 94 // Clear clears pool, which means it will remove all items from pool. 95 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 } 107 } 108 109 // Get picks and returns an item from pool. If the pool is empty and NewFunc is defined, 110 // it creates and returns one from NewFunc. 111 func (p *Pool) Get() (interface{}, 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") 129 } 130 131 // Size returns the count of available items of pool. 132 func (p *Pool) Size() int { 133 return p.list.Len() 134 } 135 136 // Close closes the pool. If `p` has ExpireFunc, 137 // then it automatically closes all items using this function before it's closed. 138 // Commonly you do not need to call this function manually. 139 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 { 170 if latestExpire > timestampMilli { 171 break 172 } 173 if r := p.list.PopFront(); r != nil { 174 item := r.(*poolItem) 175 latestExpire = item.expireAt 176 // TODO improve the auto-expiration mechanism of the pool. 177 if item.expireAt > timestampMilli { 178 p.list.PushFront(item) 179 break 180 } 181 if p.ExpireFunc != nil { 182 p.ExpireFunc(item.value) 183 } 184 } else { 185 break 186 } 187 } 188 }