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  }