github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/pool/pool.go (about)

     1  // Package pool implements a memory pool similar in concept to
     2  // sync.Pool but with more determinism.
     3  package pool
     4  
     5  import (
     6  	"fmt"
     7  	"log"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/rclone/rclone/lib/mmap"
    12  )
    13  
    14  // Pool of internal buffers
    15  //
    16  // We hold buffers in cache. Every time we Get or Put we update
    17  // minFill which is the minimum len(cache) seen.
    18  //
    19  // Every flushTime we remove minFill buffers from the cache as they
    20  // were not used in the previous flushTime interval.
    21  type Pool struct {
    22  	mu           sync.Mutex
    23  	cache        [][]byte
    24  	minFill      int // the minimum fill of the cache
    25  	bufferSize   int
    26  	poolSize     int
    27  	timer        *time.Timer
    28  	inUse        int
    29  	alloced      int
    30  	flushTime    time.Duration
    31  	flushPending bool
    32  	alloc        func(int) ([]byte, error)
    33  	free         func([]byte) error
    34  }
    35  
    36  // New makes a buffer pool
    37  //
    38  // flushTime is the interval the buffer pools is flushed
    39  // bufferSize is the size of the allocations
    40  // poolSize is the maximum number of free buffers in the pool
    41  // useMmap should be set to use mmap allocations
    42  func New(flushTime time.Duration, bufferSize, poolSize int, useMmap bool) *Pool {
    43  	bp := &Pool{
    44  		cache:      make([][]byte, 0, poolSize),
    45  		poolSize:   poolSize,
    46  		flushTime:  flushTime,
    47  		bufferSize: bufferSize,
    48  	}
    49  	if useMmap {
    50  		bp.alloc = mmap.Alloc
    51  		bp.free = mmap.Free
    52  	} else {
    53  		bp.alloc = func(size int) ([]byte, error) {
    54  			return make([]byte, size), nil
    55  		}
    56  		bp.free = func([]byte) error {
    57  			return nil
    58  		}
    59  	}
    60  	bp.timer = time.AfterFunc(flushTime, bp.flushAged)
    61  	return bp
    62  }
    63  
    64  // get gets the last buffer in bp.cache
    65  //
    66  // Call with mu held
    67  func (bp *Pool) get() []byte {
    68  	n := len(bp.cache) - 1
    69  	buf := bp.cache[n]
    70  	bp.cache[n] = nil // clear buffer pointer from bp.cache
    71  	bp.cache = bp.cache[:n]
    72  	return buf
    73  }
    74  
    75  // put puts the buffer on the end of bp.cache
    76  //
    77  // Call with mu held
    78  func (bp *Pool) put(buf []byte) {
    79  	bp.cache = append(bp.cache, buf)
    80  }
    81  
    82  // flush n entries from the entire buffer pool
    83  // Call with mu held
    84  func (bp *Pool) flush(n int) {
    85  	for i := 0; i < n; i++ {
    86  		bp.freeBuffer(bp.get())
    87  	}
    88  	bp.minFill = len(bp.cache)
    89  }
    90  
    91  // Flush the entire buffer pool
    92  func (bp *Pool) Flush() {
    93  	bp.mu.Lock()
    94  	bp.flush(len(bp.cache))
    95  	bp.mu.Unlock()
    96  }
    97  
    98  // Remove bp.minFill buffers
    99  func (bp *Pool) flushAged() {
   100  	bp.mu.Lock()
   101  	bp.flushPending = false
   102  	bp.flush(bp.minFill)
   103  	// If there are still items in the cache, schedule another flush
   104  	if len(bp.cache) != 0 {
   105  		bp.kickFlusher()
   106  	}
   107  	bp.mu.Unlock()
   108  }
   109  
   110  // InUse returns the number of buffers in use which haven't been
   111  // returned to the pool
   112  func (bp *Pool) InUse() int {
   113  	bp.mu.Lock()
   114  	defer bp.mu.Unlock()
   115  	return bp.inUse
   116  }
   117  
   118  // InPool returns the number of buffers in the pool
   119  func (bp *Pool) InPool() int {
   120  	bp.mu.Lock()
   121  	defer bp.mu.Unlock()
   122  	return len(bp.cache)
   123  }
   124  
   125  // Alloced returns the number of buffers allocated and not yet freed
   126  func (bp *Pool) Alloced() int {
   127  	bp.mu.Lock()
   128  	defer bp.mu.Unlock()
   129  	return bp.alloced
   130  }
   131  
   132  // starts or resets the buffer flusher timer - call with mu held
   133  func (bp *Pool) kickFlusher() {
   134  	if bp.flushPending {
   135  		return
   136  	}
   137  	bp.flushPending = true
   138  	bp.timer.Reset(bp.flushTime)
   139  }
   140  
   141  // Make sure minFill is correct - call with mu held
   142  func (bp *Pool) updateMinFill() {
   143  	if len(bp.cache) < bp.minFill {
   144  		bp.minFill = len(bp.cache)
   145  	}
   146  }
   147  
   148  // Get a buffer from the pool or allocate one
   149  func (bp *Pool) Get() []byte {
   150  	bp.mu.Lock()
   151  	var buf []byte
   152  	waitTime := time.Millisecond
   153  	for {
   154  		if len(bp.cache) > 0 {
   155  			buf = bp.get()
   156  			break
   157  		} else {
   158  			var err error
   159  			buf, err = bp.alloc(bp.bufferSize)
   160  			if err == nil {
   161  				bp.alloced++
   162  				break
   163  			}
   164  			log.Printf("Failed to get memory for buffer, waiting for %v: %v", waitTime, err)
   165  			bp.mu.Unlock()
   166  			time.Sleep(waitTime)
   167  			bp.mu.Lock()
   168  			waitTime *= 2
   169  		}
   170  	}
   171  	bp.inUse++
   172  	bp.updateMinFill()
   173  	bp.mu.Unlock()
   174  	return buf
   175  }
   176  
   177  // freeBuffer returns mem to the os if required - call with lock held
   178  func (bp *Pool) freeBuffer(mem []byte) {
   179  	err := bp.free(mem)
   180  	if err != nil {
   181  		log.Printf("Failed to free memory: %v", err)
   182  	}
   183  	bp.alloced--
   184  }
   185  
   186  // Put returns the buffer to the buffer cache or frees it
   187  //
   188  // Note that if you try to return a buffer of the wrong size to Put it
   189  // will panic.
   190  func (bp *Pool) Put(buf []byte) {
   191  	bp.mu.Lock()
   192  	defer bp.mu.Unlock()
   193  	buf = buf[0:cap(buf)]
   194  	if len(buf) != bp.bufferSize {
   195  		panic(fmt.Sprintf("Returning buffer sized %d but expecting %d", len(buf), bp.bufferSize))
   196  	}
   197  	if len(bp.cache) < bp.poolSize {
   198  		bp.put(buf)
   199  	} else {
   200  		bp.freeBuffer(buf)
   201  	}
   202  	bp.inUse--
   203  	bp.updateMinFill()
   204  	bp.kickFlusher()
   205  }