github.com/nebulouslabs/sia@v1.3.7/modules/renter/memory.go (about)

     1  package renter
     2  
     3  // TODO: Move the memory manager to its own package.
     4  
     5  // TODO: Add functions that allow a caller to increase or decrease the base
     6  // memory for the memory manager.
     7  
     8  import (
     9  	"sync"
    10  
    11  	"github.com/NebulousLabs/Sia/build"
    12  )
    13  
    14  // memoryManager can handle requests for memory and returns of memory. The
    15  // memory manager is initialized with a base amount of memory and it will allow
    16  // up to that much memory to be requested simultaneously. Beyond that, it will
    17  // block on calls to 'managedGetMemory' until enough memory has been returned to
    18  // allow the request.
    19  //
    20  // If a request is made that exceeds the base memory, the memory manager will
    21  // block until all memory is available, and then grant the request, blocking all
    22  // future requests for memory until the memory is returned. This allows large
    23  // requests to go through even if there is not enough base memory.
    24  type memoryManager struct {
    25  	available    uint64
    26  	base         uint64
    27  	fifo         []*memoryRequest
    28  	priorityFifo []*memoryRequest
    29  	mu           sync.Mutex
    30  	stop         <-chan struct{}
    31  	underflow    uint64
    32  }
    33  
    34  // memoryRequest is a single thread that is blocked while waiting for memory.
    35  type memoryRequest struct {
    36  	amount uint64
    37  	done   chan struct{}
    38  }
    39  
    40  // try will try to get the amount of memory requested from the manger, returning
    41  // true if the attempt is successful, and false if the attempt is not.  In the
    42  // event that the attempt is successful, the internal state of the memory
    43  // manager will be updated to reflect the granted request.
    44  func (mm *memoryManager) try(amount uint64) bool {
    45  	if mm.available >= amount {
    46  		// There is enough memory, decrement the memory and return.
    47  		mm.available -= amount
    48  		return true
    49  	} else if mm.available == mm.base {
    50  		// The amount of memory being requested is greater than the amount of
    51  		// memory available, but no memory is currently in use. Set the amount
    52  		// of memory available to zero and return.
    53  		//
    54  		// The effect is that all of the memory is allocated to this one
    55  		// request, allowing the request to succeed even though there is
    56  		// technically not enough total memory available for the request.
    57  		mm.available = 0
    58  		mm.underflow = amount - mm.base
    59  		return true
    60  	}
    61  	return false
    62  }
    63  
    64  // Request is a blocking request for memory. The request will return when the
    65  // memory has been acquired. If 'false' is returned, it means that the renter
    66  // shut down before the memory could be allocated.
    67  func (mm *memoryManager) Request(amount uint64, priority bool) bool {
    68  	// Try to request the memory.
    69  	mm.mu.Lock()
    70  	if len(mm.fifo) == 0 && mm.try(amount) {
    71  		mm.mu.Unlock()
    72  		return true
    73  	}
    74  
    75  	// There is not enough memory available for this request, join the fifo.
    76  	myRequest := &memoryRequest{
    77  		amount: amount,
    78  		done:   make(chan struct{}),
    79  	}
    80  	if priority {
    81  		mm.priorityFifo = append(mm.priorityFifo, myRequest)
    82  	} else {
    83  		mm.fifo = append(mm.fifo, myRequest)
    84  	}
    85  	mm.mu.Unlock()
    86  
    87  	// Block until memory is available or until shutdown. The thread that closes
    88  	// the 'available' channel will also handle updating the memoryManager
    89  	// variables.
    90  	select {
    91  	case <-myRequest.done:
    92  		return true
    93  	case <-mm.stop:
    94  		return false
    95  	}
    96  }
    97  
    98  // Return will return memory to the manager, waking any blocking threads which
    99  // now have enough memory to proceed.
   100  func (mm *memoryManager) Return(amount uint64) {
   101  	mm.mu.Lock()
   102  	defer mm.mu.Unlock()
   103  
   104  	// Add the remaining memory to the pool of available memory, clearing out
   105  	// the underflow if needed.
   106  	if mm.underflow > 0 && amount <= mm.underflow {
   107  		// Not even enough memory has been returned to clear the underflow.
   108  		// Reduce the underflow amount and return.
   109  		mm.underflow -= amount
   110  		return
   111  	} else if mm.underflow > 0 && amount > mm.underflow {
   112  		amount -= mm.underflow
   113  		mm.underflow = 0
   114  	}
   115  	mm.available += amount
   116  
   117  	// Sanity check - the amount of memory available should not exceed the base
   118  	// unless the memory manager is being used incorrectly.
   119  	if mm.available > mm.base {
   120  		build.Critical("renter memory manager being used incorrectly, too much memory returned")
   121  		mm.available = mm.base
   122  	}
   123  
   124  	// Release as many of the priority threads blocking in the fifo as possible.
   125  	for len(mm.priorityFifo) > 0 {
   126  		if !mm.try(mm.priorityFifo[0].amount) {
   127  			// There is not enough memory to grant the next request, meaning no
   128  			// future requests should be checked either.
   129  			return
   130  		}
   131  		// There is enough memory to grant the next request. Unblock that
   132  		// request and continue checking the next requests.
   133  		close(mm.priorityFifo[0].done)
   134  		mm.priorityFifo = mm.priorityFifo[1:]
   135  	}
   136  
   137  	// Release as many of the threads blocking in the fifo as possible.
   138  	for len(mm.fifo) > 0 {
   139  		if !mm.try(mm.fifo[0].amount) {
   140  			// There is not enough memory to grant the next request, meaning no
   141  			// future requests should be checked either.
   142  			return
   143  		}
   144  		// There is enough memory to grant the next request. Unblock that
   145  		// request and continue checking the next requests.
   146  		close(mm.fifo[0].done)
   147  		mm.fifo = mm.fifo[1:]
   148  	}
   149  }
   150  
   151  // newMemoryManager will create a memoryManager and return it.
   152  func newMemoryManager(baseMemory uint64, stopChan <-chan struct{}) *memoryManager {
   153  	return &memoryManager{
   154  		available: baseMemory,
   155  		base:      baseMemory,
   156  		stop:      stopChan,
   157  	}
   158  }