@@ -20,10 +20,6 @@ import (
20
20
21
21
const (
22
22
mibBytes = 1024 * 1024
23
-
24
- // Minimum interval between forced GC when in soft limited mode. We don't want to
25
- // do GCs too frequently since it is a CPU-heavy operation.
26
- minGCIntervalWhenSoftLimited = 10 * time .Second
27
23
)
28
24
29
25
var (
@@ -50,11 +46,14 @@ type MemoryLimiter struct {
50
46
51
47
ticker * time.Ticker
52
48
53
- lastGCDone time.Time
49
+ minGCIntervalWhenSoftLimited time.Duration
50
+ minGCIntervalWhenHardLimited time.Duration
51
+ lastGCDone time.Time
54
52
55
- // The function to read the mem values is set as a reference to help with
53
+ // The functions to read the mem values and run GS are set as a reference to help with
56
54
// testing different values.
57
55
readMemStatsFn func (m * runtime.MemStats )
56
+ runGCFn func ()
58
57
59
58
// Fields used for logging.
60
59
logger * zap.Logger
@@ -78,18 +77,20 @@ func NewMemoryLimiter(cfg *Config, logger *zap.Logger) (*MemoryLimiter, error) {
78
77
zap .Duration ("check_interval" , cfg .CheckInterval ))
79
78
80
79
return & MemoryLimiter {
81
- usageChecker : * usageChecker ,
82
- memCheckWait : cfg .CheckInterval ,
83
- ticker : time .NewTicker (cfg .CheckInterval ),
84
- readMemStatsFn : ReadMemStatsFn ,
85
- logger : logger ,
86
- mustRefuse : & atomic.Bool {},
80
+ usageChecker : * usageChecker ,
81
+ memCheckWait : cfg .CheckInterval ,
82
+ ticker : time .NewTicker (cfg .CheckInterval ),
83
+ minGCIntervalWhenSoftLimited : cfg .MinGCIntervalWhenSoftLimited ,
84
+ minGCIntervalWhenHardLimited : cfg .MinGCIntervalWhenHardLimited ,
85
+ lastGCDone : time .Now (),
86
+ readMemStatsFn : ReadMemStatsFn ,
87
+ runGCFn : runtime .GC ,
88
+ logger : logger ,
89
+ mustRefuse : & atomic.Bool {},
87
90
}, nil
88
91
}
89
92
90
- // startMonitoring starts a single ticker'd goroutine per instance
91
- // that will check memory usage every checkInterval period.
92
- func (ml * MemoryLimiter ) startMonitoring () {
93
+ func (ml * MemoryLimiter ) Start (_ context.Context , _ component.Host ) error {
93
94
ml .refCounterLock .Lock ()
94
95
defer ml .refCounterLock .Unlock ()
95
96
@@ -110,10 +111,6 @@ func (ml *MemoryLimiter) startMonitoring() {
110
111
}
111
112
}()
112
113
}
113
- }
114
-
115
- func (ml * MemoryLimiter ) Start (_ context.Context , _ component.Host ) error {
116
- ml .startMonitoring ()
117
114
return nil
118
115
}
119
116
@@ -167,7 +164,7 @@ func memstatToZapField(ms *runtime.MemStats) zap.Field {
167
164
}
168
165
169
166
func (ml * MemoryLimiter ) doGCandReadMemStats () * runtime.MemStats {
170
- runtime . GC ()
167
+ ml . runGCFn ()
171
168
ml .lastGCDone = time .Now ()
172
169
ms := ml .readMemStats ()
173
170
ml .logger .Info ("Memory usage after GC." , memstatToZapField (ms ))
@@ -180,38 +177,42 @@ func (ml *MemoryLimiter) CheckMemLimits() {
180
177
181
178
ml .logger .Debug ("Currently used memory." , memstatToZapField (ms ))
182
179
183
- if ml .usageChecker .aboveHardLimit (ms ) {
184
- ml .logger .Warn ("Memory usage is above hard limit. Forcing a GC." , memstatToZapField (ms ))
185
- ms = ml .doGCandReadMemStats ()
186
- }
187
-
188
- // Remember current state.
189
- wasRefusing := ml .mustRefuse .Load ()
190
-
191
- // Check if the memory usage is above the soft limit.
192
- mustRefuse := ml .usageChecker .aboveSoftLimit (ms )
193
-
194
- if wasRefusing && ! mustRefuse {
195
- // Was previously refusing but enough memory is available now, no need to limit.
196
- ml .logger .Info ("Memory usage back within limits. Resuming normal operation." , memstatToZapField (ms ))
180
+ // Check if we are below the soft limit.
181
+ aboveSoftLimit := ml .usageChecker .aboveSoftLimit (ms )
182
+ if ! aboveSoftLimit {
183
+ if ml .mustRefuse .Load () {
184
+ // Was previously refusing but enough memory is available now, no need to limit.
185
+ ml .logger .Info ("Memory usage back within limits. Resuming normal operation." , memstatToZapField (ms ))
186
+ }
187
+ ml .mustRefuse .Store (aboveSoftLimit )
188
+ return
197
189
}
198
190
199
- if ! wasRefusing && mustRefuse {
191
+ if ml .usageChecker .aboveHardLimit (ms ) {
192
+ // We are above hard limit, do a GC if it wasn't done recently and see if
193
+ // it brings memory usage below the soft limit.
194
+ if time .Since (ml .lastGCDone ) > ml .minGCIntervalWhenHardLimited {
195
+ ml .logger .Warn ("Memory usage is above hard limit. Forcing a GC." , memstatToZapField (ms ))
196
+ ms = ml .doGCandReadMemStats ()
197
+ // Check the limit again to see if GC helped.
198
+ aboveSoftLimit = ml .usageChecker .aboveSoftLimit (ms )
199
+ }
200
+ } else {
200
201
// We are above soft limit, do a GC if it wasn't done recently and see if
201
202
// it brings memory usage below the soft limit.
202
- if time .Since (ml .lastGCDone ) > minGCIntervalWhenSoftLimited {
203
+ if time .Since (ml .lastGCDone ) > ml . minGCIntervalWhenSoftLimited {
203
204
ml .logger .Info ("Memory usage is above soft limit. Forcing a GC." , memstatToZapField (ms ))
204
205
ms = ml .doGCandReadMemStats ()
205
206
// Check the limit again to see if GC helped.
206
- mustRefuse = ml .usageChecker .aboveSoftLimit (ms )
207
+ aboveSoftLimit = ml .usageChecker .aboveSoftLimit (ms )
207
208
}
209
+ }
208
210
209
- if mustRefuse {
210
- ml .logger .Warn ("Memory usage is above soft limit. Refusing data." , memstatToZapField (ms ))
211
- }
211
+ if ! ml .mustRefuse .Load () && aboveSoftLimit {
212
+ ml .logger .Warn ("Memory usage is above soft limit. Refusing data." , memstatToZapField (ms ))
212
213
}
213
214
214
- ml .mustRefuse .Store (mustRefuse )
215
+ ml .mustRefuse .Store (aboveSoftLimit )
215
216
}
216
217
217
218
type memUsageChecker struct {
0 commit comments