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