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