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