mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 20:58:33 +10:00
159 lines
3.3 KiB
Go
159 lines
3.3 KiB
Go
package oomkiller
|
|
|
|
import (
|
|
runtimeDebug "runtime/debug"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing/common/memory"
|
|
)
|
|
|
|
const (
|
|
defaultChecksBeforeLimit = 4
|
|
defaultMinInterval = 500 * time.Millisecond
|
|
defaultMaxInterval = 10 * time.Second
|
|
defaultSafetyMargin = 5 * 1024 * 1024
|
|
)
|
|
|
|
type adaptiveTimer struct {
|
|
logger log.ContextLogger
|
|
router adapter.Router
|
|
memoryLimit uint64
|
|
safetyMargin uint64
|
|
minInterval time.Duration
|
|
maxInterval time.Duration
|
|
checksBeforeLimit int
|
|
useAvailable bool
|
|
|
|
access sync.Mutex
|
|
timer *time.Timer
|
|
previousUsage uint64
|
|
lastInterval time.Duration
|
|
}
|
|
|
|
type timerConfig struct {
|
|
memoryLimit uint64
|
|
safetyMargin uint64
|
|
minInterval time.Duration
|
|
maxInterval time.Duration
|
|
checksBeforeLimit int
|
|
useAvailable bool
|
|
}
|
|
|
|
func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig) *adaptiveTimer {
|
|
return &adaptiveTimer{
|
|
logger: logger,
|
|
router: router,
|
|
memoryLimit: config.memoryLimit,
|
|
safetyMargin: config.safetyMargin,
|
|
minInterval: config.minInterval,
|
|
maxInterval: config.maxInterval,
|
|
checksBeforeLimit: config.checksBeforeLimit,
|
|
useAvailable: config.useAvailable,
|
|
}
|
|
}
|
|
|
|
func (t *adaptiveTimer) start(_ uint64) {
|
|
t.access.Lock()
|
|
defer t.access.Unlock()
|
|
t.startLocked()
|
|
}
|
|
|
|
func (t *adaptiveTimer) startNow() {
|
|
t.access.Lock()
|
|
t.startLocked()
|
|
t.access.Unlock()
|
|
t.poll()
|
|
}
|
|
|
|
func (t *adaptiveTimer) startLocked() {
|
|
if t.timer != nil {
|
|
return
|
|
}
|
|
t.previousUsage = memory.Total()
|
|
t.lastInterval = t.minInterval
|
|
t.timer = time.AfterFunc(t.minInterval, t.poll)
|
|
}
|
|
|
|
func (t *adaptiveTimer) stop() {
|
|
t.access.Lock()
|
|
defer t.access.Unlock()
|
|
t.stopLocked()
|
|
}
|
|
|
|
func (t *adaptiveTimer) stopLocked() {
|
|
if t.timer != nil {
|
|
t.timer.Stop()
|
|
t.timer = nil
|
|
}
|
|
}
|
|
|
|
func (t *adaptiveTimer) running() bool {
|
|
t.access.Lock()
|
|
defer t.access.Unlock()
|
|
return t.timer != nil
|
|
}
|
|
|
|
func (t *adaptiveTimer) poll() {
|
|
t.access.Lock()
|
|
defer t.access.Unlock()
|
|
if t.timer == nil {
|
|
return
|
|
}
|
|
|
|
usage := memory.Total()
|
|
delta := int64(usage) - int64(t.previousUsage)
|
|
t.previousUsage = usage
|
|
|
|
var remaining uint64
|
|
var triggered bool
|
|
|
|
if t.memoryLimit > 0 {
|
|
if usage >= t.memoryLimit {
|
|
remaining = 0
|
|
triggered = true
|
|
} else {
|
|
remaining = t.memoryLimit - usage
|
|
}
|
|
} else if t.useAvailable {
|
|
available := memory.Available()
|
|
if available <= t.safetyMargin {
|
|
remaining = 0
|
|
triggered = true
|
|
} else {
|
|
remaining = available - t.safetyMargin
|
|
}
|
|
} else {
|
|
remaining = 0
|
|
}
|
|
|
|
if triggered {
|
|
t.logger.Error("memory threshold reached, usage: ", usage/(1024*1024), " MiB, resetting network")
|
|
t.router.ResetNetwork()
|
|
runtimeDebug.FreeOSMemory()
|
|
}
|
|
|
|
var interval time.Duration
|
|
if triggered {
|
|
interval = t.maxInterval
|
|
} else if delta <= 0 {
|
|
interval = t.maxInterval
|
|
} else if t.checksBeforeLimit <= 0 {
|
|
interval = t.maxInterval
|
|
} else {
|
|
timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval))
|
|
interval = timeToLimit / time.Duration(t.checksBeforeLimit)
|
|
if interval < t.minInterval {
|
|
interval = t.minInterval
|
|
}
|
|
if interval > t.maxInterval {
|
|
interval = t.maxInterval
|
|
}
|
|
}
|
|
|
|
t.lastInterval = interval
|
|
t.timer.Reset(interval)
|
|
}
|