github.com/geniusesgroup/libgo@v0.0.0-20220713101832-828057a9d3d4/pool/lifo.go (about)

     1  /* For license and copyright information please see LEGAL file in repository */
     2  
     3  package pool
     4  
     5  import (
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"../protocol"
    11  )
    12  
    13  // LIFO pool, i.e. the most recently Put() item will return in call Get()
    14  // Such a scheme keeps CPU caches hot (in theory).
    15  //
    16  // Due to LIFO behaviors and MaxItems logic can't implement with sync/atomic like sync.Pool
    17  type LIFO struct {
    18  	MaxItems    int64
    19  	MinItems    int64
    20  	MaxIdles    int64
    21  	IdleTimeout protocol.Duration
    22  	// New optionally specifies a function to generate
    23  	// a value when Get would otherwise return nil.
    24  	// It may not be changed concurrently with calls to Get.
    25  	New func() interface{}
    26  	// CloseFunc optionally specifies a function to call when an item want to drop after timeout
    27  	CloseFunc func(item interface{})
    28  
    29  	itemsCount  int64      // idles + actives
    30  	mutex       sync.Mutex // lock below fields until new line
    31  	hotItem     interface{}
    32  	idleItems   []interface{}
    33  	victimItems []interface{}
    34  
    35  	state int32
    36  }
    37  
    38  func (p *LIFO) Init() {
    39  	var preStatus = p.SetState(PoolStatus_Running)
    40  	if preStatus != PoolStatus_Unset {
    41  		panic("[pool] LIFO instance can't reuse")
    42  	}
    43  	if p.CloseFunc == nil {
    44  		p.CloseFunc = func(item interface{}) {}
    45  	}
    46  	p.idleItems = make([]interface{}, p.MaxItems/4)
    47  	go p.Clean()
    48  }
    49  
    50  func (p *LIFO) State() PoolStatus { return PoolStatus(atomic.LoadInt32(&p.state)) }
    51  func (p *LIFO) SetState(state PoolStatus) (pre PoolStatus) {
    52  	return PoolStatus(atomic.SwapInt32(&p.state, int32(state)))
    53  }
    54  func (p *LIFO) Len() (ln int) {
    55  	p.mutex.Lock()
    56  	ln = len(p.idleItems) + len(p.victimItems)
    57  	p.mutex.Unlock()
    58  	return
    59  }
    60  
    61  func (p *LIFO) Get() (item interface{}) {
    62  	if p.isStop() {
    63  		return
    64  	}
    65  
    66  	item = p.popHead()
    67  	if item == nil && p.New != nil {
    68  		item = p.makeNew()
    69  	}
    70  	return
    71  }
    72  
    73  func (p *LIFO) Put(item interface{}) {
    74  	if p.isStop() {
    75  		p.CloseFunc(item)
    76  		return
    77  	}
    78  
    79  	p.pushHead(item)
    80  }
    81  
    82  func (p *LIFO) Close() {
    83  	var preStatus = p.SetState(PoolStatus_Stopping)
    84  	if preStatus != PoolStatus_Running {
    85  		panic("[pool] LIFO instance wasn't started to call Close()")
    86  	}
    87  
    88  	p.mutex.Lock()
    89  	for i := 0; i < len(p.idleItems); i++ {
    90  		var item = p.idleItems[i]
    91  		if item != nil {
    92  			p.CloseFunc(item)
    93  		}
    94  	}
    95  	p.mutex.Unlock()
    96  
    97  	p.SetState(PoolStatus_Stopped)
    98  }
    99  
   100  // Clean items but not by exact p.IdleTimeout. To improve performance we choose two window clean up.
   101  // Means some idle items can live up to double of p.IdleTimeout
   102  func (p *LIFO) Clean() {
   103  	for {
   104  		time.Sleep(time.Duration(p.IdleTimeout))
   105  		if p.isStop() {
   106  			break
   107  		}
   108  
   109  		p.mutex.Lock()
   110  		var vi = p.victimItems
   111  		p.victimItems = p.idleItems
   112  		p.idleItems = make([]interface{}, p.MaxItems/4)
   113  		p.mutex.Unlock()
   114  
   115  		atomic.AddInt64(&p.itemsCount, -int64(len(vi)))
   116  
   117  		// Close items outside of p.mutex
   118  		for i := 0; i < len(vi); i++ {
   119  			p.CloseFunc(vi[i])
   120  		}
   121  	}
   122  }
   123  
   124  func (p *LIFO) isStop() bool {
   125  	var pStatus = p.State()
   126  	if pStatus == PoolStatus_Stopping || pStatus == PoolStatus_Stopped {
   127  		return true
   128  	}
   129  	return false
   130  }
   131  
   132  func (p *LIFO) popHead() (item interface{}) {
   133  	p.mutex.Lock()
   134  	if p.hotItem != nil {
   135  		item = p.hotItem
   136  		p.hotItem = nil
   137  	} else {
   138  		var ln = len(p.idleItems) - 1
   139  		if ln > -1 {
   140  			item = p.idleItems[ln]
   141  			p.idleItems = p.idleItems[:ln]
   142  		} else {
   143  			ln = len(p.victimItems) - 1
   144  			if ln > -1 {
   145  				item = p.victimItems[ln]
   146  				p.victimItems = p.victimItems[:ln]
   147  			}
   148  		}
   149  	}
   150  	p.mutex.Unlock()
   151  	return
   152  }
   153  
   154  func (p *LIFO) pushHead(item interface{}) {
   155  	p.mutex.Lock()
   156  	if p.hotItem == nil {
   157  		p.hotItem = item
   158  	} else {
   159  		p.idleItems = append(p.idleItems, item)
   160  	}
   161  	p.mutex.Unlock()
   162  }
   163  
   164  func (p *LIFO) makeNew() (item interface{}) {
   165  	var ic = atomic.AddInt64(&p.itemsCount, 1)
   166  	if ic <= p.MaxItems {
   167  		item = p.New()
   168  	} else {
   169  		atomic.AddInt64(&p.itemsCount, -1)
   170  	}
   171  	return
   172  }