Skip to content

Commit eec6697

Browse files
author
Steve Powell
committed
Produce non-zero exit code and message if any allocation is zero.
After a successful balancing of the memory into buckets, check that none of the explicit memory allocations is zero (after rounding down to whole kilobytes); if one is zero, fail with an error message. Code improved to make this easier to read in the Balance() method and removed the dependency upon internal state for tracking the error in subsequent method calls. The tests are improved to increase coverage and make failure testing clearer. [#94164248]
1 parent 934ea74 commit eec6697

File tree

3 files changed

+123
-40
lines changed

3 files changed

+123
-40
lines changed

integration/main_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ var _ = Describe("java-buildpack-memory-calculator executable", func() {
159159

160160
It("fails with an error", func() {
161161
Ω(cmdErr).Should(HaveOccurred(), "exit status")
162-
Ω(string(sErr)).Should(ContainSubstring("Cannot balance memory: Total memory exceeded by configuration: ["), "stderr")
162+
Ω(string(sErr)).Should(ContainSubstring("Cannot balance memory: Memory allocation failed for configuration: ["), "stderr")
163163
Ω(string(sOut)).Should(Equal(""), "stdout")
164164
})
165165
})

memory/allocator.go

+36-28
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ type allocator struct {
3434
originalSizes map[string]Range // unmodified after creation
3535
buckets map[string]Bucket // named buckets for allocation
3636
warnings []string // warnings if allocation found issues
37-
failure error // error if allocation failed
3837
}
3938

4039
func NewAllocator(sizes map[string]Range, heuristics map[string]float64) (*allocator, error) {
@@ -57,21 +56,20 @@ const (
5756
// Balance memory between buckets, adjusting stack units, observing
5857
// constraints, and detecting memory wastage and default proximity.
5958
func (a *allocator) Balance(memLimit MemSize) error {
60-
// Adjust stack bucket, if it exists
59+
// adjust stack bucket, if it exists
6160
stackBucket, estNumThreads := a.normaliseStack(memLimit)
6261

63-
// balance buckets
64-
a.balance(memLimit)
62+
// distribute memory among the buckets
63+
if berr := a.balance(memLimit); berr != nil {
64+
return fmt.Errorf("Memory allocation failed for configuration: %v, : %s", getSizes(a.originalSizes), berr)
65+
}
6566

66-
// Validate result and gather warnings
67+
// validate result and gather warnings
6768
a.validateAllocation(memLimit)
6869

69-
// Re-adjust stack bucket, if it exists
70+
// reset stack bucket, if it exists
7071
a.unnormaliseStack(stackBucket, estNumThreads)
7172

72-
if a.failure != nil {
73-
return fmt.Errorf("Total memory exceeded by configuration: %v", getSizes(a.originalSizes))
74-
}
7573
return nil
7674
}
7775

@@ -82,9 +80,6 @@ func (a *allocator) SetLowerBounds() {
8280
}
8381

8482
func (a *allocator) Switches(sfs switches.Funs) []string {
85-
if a.failure != nil {
86-
return nil
87-
}
8883
var strs = make([]string, 0, 10)
8984
for s, b := range a.buckets {
9085
strs = append(strs, sfs.Apply(s, b.GetSize().String())...)
@@ -93,12 +88,10 @@ func (a *allocator) Switches(sfs switches.Funs) []string {
9388
}
9489

9590
func (a *allocator) GetWarnings() []string {
96-
if a.failure != nil {
97-
return nil
98-
}
9991
return a.warnings
10092
}
10193

94+
// getSizes returns a slice of memory type range strings
10295
func getSizes(ss map[string]Range) []string {
10396
result := []string{}
10497
for n, s := range ss {
@@ -130,6 +123,7 @@ func totalWeight(bs map[string]Bucket) float64 {
130123
return w
131124
}
132125

126+
// Replace stack bucket to make it represent total memory for stacks temporarily
133127
func (a *allocator) normaliseStack(memLimit MemSize) (originalStackBucket Bucket, estNumThreads float64) {
134128
if sb, ok := a.buckets["stack"]; ok {
135129
stackMem := weightedSize(totalWeight(a.buckets), memLimit, sb)
@@ -146,6 +140,7 @@ func weightedSize(totWeight float64, memLimit MemSize, b Bucket) float64 {
146140
return (float64(memLimit) * b.Weight()) / totWeight
147141
}
148142

143+
// Replace stack bucket, and set size per thread
149144
func (a *allocator) unnormaliseStack(sb Bucket, estNum float64) {
150145
if sb == nil {
151146
return
@@ -156,18 +151,26 @@ func (a *allocator) unnormaliseStack(sb Bucket, estNum float64) {
156151
}
157152

158153
// Balance memory between buckets, observing constraints.
159-
func (a *allocator) balance(memLimit MemSize) {
154+
func (a *allocator) balance(memLimit MemSize) error {
160155
remaining := copyBucketMap(a.buckets)
161156
removed := true
162157

163158
for removed && len(remaining) != 0 {
164159
var err error
165160
memLimit, removed, err = balanceOrRemove(remaining, memLimit)
166161
if err != nil {
167-
a.failure = err
168-
return
162+
return err
169163
}
170164
}
165+
166+
// check for zero allocations
167+
for n, b := range a.buckets {
168+
if b.GetSize().String() == "0" {
169+
return fmt.Errorf("Cannot allocate memory to '%n' type", n)
170+
}
171+
}
172+
173+
return nil
171174
}
172175

173176
func (a *allocator) validateAllocation(memLimit MemSize) {
@@ -179,16 +182,8 @@ func (a *allocator) validateAllocation(memLimit MemSize) {
179182

180183
func memoryWastageWarnings(bs map[string]Bucket, memLimit MemSize) []string {
181184
warnings := []string{}
182-
if nativeBucket, ok := bs["native"]; ok && nativeBucket.Range().Floor() == MEMSIZE_ZERO {
183-
totWeight := totalWeight(bs)
184-
floatSize := NATIVE_MEMORY_WARNING_FACTOR * weightedSize(totWeight, memLimit, nativeBucket)
185-
if MemSize(math.Floor(0.5 + floatSize)).LessThan(*nativeBucket.GetSize()) {
186-
warnings = append(warnings,
187-
fmt.Sprintf(
188-
"There is more than %g times more spare native memory than the default "+
189-
"so configured Java memory may be too small or available memory may be too large",
190-
NATIVE_MEMORY_WARNING_FACTOR))
191-
}
185+
if nb, ok := bs["native"]; ok {
186+
warnings = append(warnings, nativeBucketWarning(nb, totalWeight(bs), memLimit)...)
192187
}
193188

194189
totalSize := MEMSIZE_ZERO
@@ -205,6 +200,19 @@ func memoryWastageWarnings(bs map[string]Bucket, memLimit MemSize) []string {
205200
return warnings
206201
}
207202

203+
func nativeBucketWarning(nativeBucket Bucket, totWeight float64, memLimit MemSize) []string {
204+
if nativeBucket.Range().Floor() == MEMSIZE_ZERO {
205+
floatSize := NATIVE_MEMORY_WARNING_FACTOR * weightedSize(totWeight, memLimit, nativeBucket)
206+
if MemSize(math.Floor(0.5 + floatSize)).LessThan(*nativeBucket.GetSize()) {
207+
return []string{fmt.Sprintf(
208+
"There is more than %g times more spare native memory than the default "+
209+
"so configured Java memory may be too small or available memory may be too large",
210+
NATIVE_MEMORY_WARNING_FACTOR)}
211+
}
212+
}
213+
return []string{}
214+
}
215+
208216
func closeToDefaultWarnings(bs map[string]Bucket, sizes map[string]Range, memLimit MemSize) []string {
209217
totWeight := totalWeight(bs)
210218
warnings := []string{}

memory/allocator_test.go

+86-11
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@ var _ = Describe("Allocator", func() {
5656
}
5757
})
5858

59-
JustBeforeEach(func() {
60-
a = shouldWork(memory.NewAllocator(convertToRanges(sizes), weights))
61-
})
62-
6359
Context("constructor", func() {
6460

6561
Context("with good parameters", func() {
@@ -79,6 +75,10 @@ var _ = Describe("Allocator", func() {
7975
}
8076
})
8177

78+
JustBeforeEach(func() {
79+
a = shouldWork(memory.NewAllocator(convertToRanges(sizes), weights))
80+
})
81+
8282
It("succeeds", func() {
8383
Ω(memory.GetBuckets(a)).Should(ConsistOf(
8484
"Bucket{name: stack, size: <nil>, range: 2M..2M, weight: 1}",
@@ -108,14 +108,89 @@ var _ = Describe("Allocator", func() {
108108
)) // heap, permgen, stack
109109
})
110110
})
111+
})
112+
113+
Context("balancing", func() {
114+
var (
115+
memLimit = memory.MEMSIZE_ZERO
116+
aerr error
117+
)
118+
119+
JustBeforeEach(func() {
120+
a = shouldWork(memory.NewAllocator(convertToRanges(sizes), weights))
121+
aerr = a.Balance(memLimit)
122+
})
123+
124+
Context("badly", func() {
125+
126+
JustBeforeEach(func() {
127+
Ω(aerr).Should(HaveOccurred())
128+
})
129+
130+
Context("with no memory and one bucket", func() {
131+
BeforeEach(func() {
132+
sizes = strmap{"heap": "0.."}
133+
weights = floatmap{"heap": 5.0}
134+
memLimit = memory.MEMSIZE_ZERO
135+
})
136+
It("fails", func() {})
137+
})
138+
139+
Context("with not enough memory and one bucket", func() {
140+
BeforeEach(func() {
141+
sizes = strmap{"heap": "64m.."}
142+
weights = floatmap{"heap": 5.0}
143+
memLimit = memory.NewMemSize(32 * mEGA)
144+
})
145+
It("fails", func() {})
146+
})
147+
148+
Context("with not enough memory and two buckets", func() {
149+
BeforeEach(func() {
150+
sizes = strmap{"heap": "33m..", "hope": "32m.."}
151+
weights = floatmap{"heap": 1.0, "hope": 1.0}
152+
memLimit = memory.NewMemSize(64 * mEGA)
153+
})
154+
It("fails", func() {})
155+
})
111156

112-
Context("good balancing", func() {
113-
var (
114-
memLimit = memory.MemSize(0)
115-
)
157+
Context("with just enough memory for one out of two buckets", func() {
158+
BeforeEach(func() {
159+
sizes = strmap{"heap": "38m..", "hope": ".."}
160+
weights = floatmap{"heap": 1.0, "hope": 1.0}
161+
memLimit = memory.NewMemSize(38 * mEGA)
162+
})
163+
It("fails", func() {})
164+
})
165+
})
116166

167+
Context("well", func() {
117168
JustBeforeEach(func() {
118-
Ω(a.Balance(memLimit)).ShouldNot(HaveOccurred())
169+
Ω(aerr).ShouldNot(HaveOccurred())
170+
})
171+
172+
Context("with exactly enough memory and one bucket", func() {
173+
BeforeEach(func() {
174+
sizes = strmap{"heap": "64m.."}
175+
weights = floatmap{"heap": 5.0}
176+
memLimit = memory.NewMemSize(64 * mEGA)
177+
})
178+
It("fills the bucket up", func() {
179+
Ω(memory.GetBuckets(a)).Should(ConsistOf(
180+
"Bucket{name: heap, size: 64M, range: 64M.., weight: 5}",
181+
))
182+
})
183+
})
184+
185+
Context("with no memory and no buckets", func() {
186+
BeforeEach(func() {
187+
sizes = strmap{"heap": "0.."}
188+
weights = floatmap{}
189+
memLimit = memory.MEMSIZE_ZERO
190+
})
191+
It("results in no buckets", func() {
192+
Ω(memory.GetBuckets(a)).Should(BeEmpty())
193+
})
119194
})
120195

121196
Context("with single bucket to 'balance'", func() {
@@ -133,7 +208,7 @@ var _ = Describe("Allocator", func() {
133208
})
134209
})
135210

136-
Context("with no buckets to 'balance'", func() {
211+
Context("with some memory and no buckets to 'balance'", func() {
137212

138213
BeforeEach(func() {
139214
sizes = strmap{}
@@ -146,7 +221,7 @@ var _ = Describe("Allocator", func() {
146221
})
147222
})
148223

149-
Context("with two buckets to balance", func() {
224+
Context("with some memory and two buckets to balance", func() {
150225

151226
BeforeEach(func() {
152227
sizes = strmap{"heap": "0..", "hope": "0.."}

0 commit comments

Comments
 (0)