gpool.go 5.1 KB

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