gpool.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. // Clear clears pool, which means it will remove all items from pool.
  78. func (p *Pool) Clear() {
  79. if p.ExpireFunc != nil {
  80. for {
  81. if r := p.list.PopFront(); r != nil {
  82. p.ExpireFunc(r.(*poolItem).value)
  83. } else {
  84. break
  85. }
  86. }
  87. } else {
  88. p.list.RemoveAll()
  89. }
  90. }
  91. // Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
  92. // it creates and returns one from NewFunc.
  93. func (p *Pool) Get() (interface{}, error) {
  94. for !p.closed.Val() {
  95. if r := p.list.PopFront(); r != nil {
  96. f := r.(*poolItem)
  97. if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
  98. return f.value, nil
  99. } else if p.ExpireFunc != nil {
  100. // TODO: move expire function calling asynchronously out from `Get` operation.
  101. p.ExpireFunc(f.value)
  102. }
  103. } else {
  104. break
  105. }
  106. }
  107. if p.NewFunc != nil {
  108. return p.NewFunc()
  109. }
  110. return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
  111. }
  112. // Size returns the count of available items of pool.
  113. func (p *Pool) Size() int {
  114. return p.list.Len()
  115. }
  116. // Close closes the pool. If `p` has ExpireFunc,
  117. // then it automatically closes all items using this function before it's closed.
  118. // Commonly you do not need to call this function manually.
  119. func (p *Pool) Close() {
  120. p.closed.Set(true)
  121. }
  122. // checkExpire removes expired items from pool in every second.
  123. func (p *Pool) checkExpireItems(ctx context.Context) {
  124. if p.closed.Val() {
  125. // If p has ExpireFunc,
  126. // then it must close all items using this function.
  127. if p.ExpireFunc != nil {
  128. for {
  129. if r := p.list.PopFront(); r != nil {
  130. p.ExpireFunc(r.(*poolItem).value)
  131. } else {
  132. break
  133. }
  134. }
  135. }
  136. gtimer.Exit()
  137. }
  138. // All items do not expire.
  139. if p.TTL == 0 {
  140. return
  141. }
  142. // The latest item expire timestamp in milliseconds.
  143. var latestExpire int64 = -1
  144. // Retrieve the current timestamp in milliseconds, it expires the items
  145. // by comparing with this timestamp. It is not accurate comparison for
  146. // every item expired, but high performance.
  147. var timestampMilli = gtime.TimestampMilli()
  148. for {
  149. if latestExpire > timestampMilli {
  150. break
  151. }
  152. if r := p.list.PopFront(); r != nil {
  153. item := r.(*poolItem)
  154. latestExpire = item.expireAt
  155. // TODO improve the auto-expiration mechanism of the pool.
  156. if item.expireAt > timestampMilli {
  157. p.list.PushFront(item)
  158. break
  159. }
  160. if p.ExpireFunc != nil {
  161. p.ExpireFunc(item.value)
  162. }
  163. } else {
  164. break
  165. }
  166. }
  167. }