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 }