github.com/gogf/gf@v1.16.9/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/gogf/gf. 6 7 // Package gpool provides object-reusable concurrent-safe pool. 8 package gpool 9 10 import ( 11 "github.com/gogf/gf/errors/gcode" 12 "github.com/gogf/gf/errors/gerror" 13 "time" 14 15 "github.com/gogf/gf/container/glist" 16 "github.com/gogf/gf/container/gtype" 17 "github.com/gogf/gf/os/gtime" 18 "github.com/gogf/gf/os/gtimer" 19 ) 20 21 // Pool is an Object-Reusable Pool. 22 type Pool struct { 23 list *glist.List // 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() (interface{}, error) // Callback function to create pool item. 27 // ExpireFunc is the 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(interface{}) 32 } 33 34 // Pool item. 35 type poolItem struct { 36 value interface{} // Item value. 37 expireAt int64 // Expire timestamp in milliseconds. 38 } 39 40 // Creation function for object. 41 type NewFunc func() (interface{}, error) 42 43 // Destruction function for object. 44 type ExpireFunc func(interface{}) 45 46 // New 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 New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool { 54 r := &Pool{ 55 list: glist.New(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(time.Second, r.checkExpireItems) 64 return r 65 } 66 67 // Put puts an item to pool. 68 func (p *Pool) Put(value interface{}) error { 69 if p.closed.Val() { 70 return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed") 71 } 72 item := &poolItem{ 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 // Clear clears pool, which means it will remove all items from pool. 87 func (p *Pool) Clear() { 88 if p.ExpireFunc != nil { 89 for { 90 if r := p.list.PopFront(); r != nil { 91 p.ExpireFunc(r.(*poolItem).value) 92 } else { 93 break 94 } 95 } 96 } else { 97 p.list.RemoveAll() 98 } 99 100 } 101 102 // Get picks and returns an item from pool. If the pool is empty and NewFunc is defined, 103 // it creates and returns one from NewFunc. 104 func (p *Pool) Get() (interface{}, error) { 105 for !p.closed.Val() { 106 if r := p.list.PopFront(); r != nil { 107 f := r.(*poolItem) 108 if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() { 109 return f.value, nil 110 } else if p.ExpireFunc != nil { 111 // TODO: move expire function calling asynchronously from `Get` operation. 112 p.ExpireFunc(f.value) 113 } 114 } else { 115 break 116 } 117 } 118 if p.NewFunc != nil { 119 return p.NewFunc() 120 } 121 return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty") 122 } 123 124 // Size returns the count of available items of pool. 125 func (p *Pool) Size() int { 126 return p.list.Len() 127 } 128 129 // Close closes the pool. If <p> has ExpireFunc, 130 // then it automatically closes all items using this function before it's closed. 131 // Commonly you do not need call this function manually. 132 func (p *Pool) Close() { 133 p.closed.Set(true) 134 } 135 136 // checkExpire removes expired items from pool in every second. 137 func (p *Pool) checkExpireItems() { 138 if p.closed.Val() { 139 // If p has ExpireFunc, 140 // then it must close all items using this function. 141 if p.ExpireFunc != nil { 142 for { 143 if r := p.list.PopFront(); r != nil { 144 p.ExpireFunc(r.(*poolItem).value) 145 } else { 146 break 147 } 148 } 149 } 150 gtimer.Exit() 151 } 152 // All items do not expire. 153 if p.TTL == 0 { 154 return 155 } 156 // The latest item expire timestamp in milliseconds. 157 var latestExpire int64 = -1 158 // Retrieve the current timestamp in milliseconds, it expires the items 159 // by comparing with this timestamp. It is not accurate comparison for 160 // every items expired, but high performance. 161 var timestampMilli = gtime.TimestampMilli() 162 for { 163 if latestExpire > timestampMilli { 164 break 165 } 166 if r := p.list.PopFront(); r != nil { 167 item := r.(*poolItem) 168 latestExpire = item.expireAt 169 // TODO improve the auto-expiration mechanism of the pool. 170 if item.expireAt > timestampMilli { 171 p.list.PushFront(item) 172 break 173 } 174 if p.ExpireFunc != nil { 175 p.ExpireFunc(item.value) 176 } 177 } else { 178 break 179 } 180 } 181 }