summaryrefslogtreecommitdiff
path: root/vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go')
-rw-r--r--vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go283
1 files changed, 0 insertions, 283 deletions
diff --git a/vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go b/vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go
deleted file mode 100644
index cbd53ce3a..000000000
--- a/vendor/github.com/KimMachineGun/automemlimit/memlimit/memlimit.go
+++ /dev/null
@@ -1,283 +0,0 @@
-package memlimit
-
-import (
- "errors"
- "fmt"
- "log/slog"
- "math"
- "os"
- "runtime/debug"
- "strconv"
- "time"
-)
-
-const (
- envGOMEMLIMIT = "GOMEMLIMIT"
- envAUTOMEMLIMIT = "AUTOMEMLIMIT"
- // Deprecated: use memlimit.WithLogger instead
- envAUTOMEMLIMIT_DEBUG = "AUTOMEMLIMIT_DEBUG"
-
- defaultAUTOMEMLIMIT = 0.9
-)
-
-// ErrNoLimit is returned when the memory limit is not set.
-var ErrNoLimit = errors.New("memory is not limited")
-
-type config struct {
- logger *slog.Logger
- ratio float64
- provider Provider
- refresh time.Duration
-}
-
-// Option is a function that configures the behavior of SetGoMemLimitWithOptions.
-type Option func(cfg *config)
-
-// WithRatio configures the ratio of the memory limit to set as GOMEMLIMIT.
-//
-// Default: 0.9
-func WithRatio(ratio float64) Option {
- return func(cfg *config) {
- cfg.ratio = ratio
- }
-}
-
-// WithProvider configures the provider.
-//
-// Default: FromCgroup
-func WithProvider(provider Provider) Option {
- return func(cfg *config) {
- cfg.provider = provider
- }
-}
-
-// WithLogger configures the logger.
-// It automatically attaches the "package" attribute to the logs.
-//
-// Default: slog.New(noopLogger{})
-func WithLogger(logger *slog.Logger) Option {
- return func(cfg *config) {
- cfg.logger = memlimitLogger(logger)
- }
-}
-
-// WithRefreshInterval configures the refresh interval for automemlimit.
-// If a refresh interval is greater than 0, automemlimit periodically fetches
-// the memory limit from the provider and reapplies it if it has changed.
-// If the provider returns an error, it logs the error and continues.
-// ErrNoLimit is treated as math.MaxInt64.
-//
-// Default: 0 (no refresh)
-func WithRefreshInterval(refresh time.Duration) Option {
- return func(cfg *config) {
- cfg.refresh = refresh
- }
-}
-
-// WithEnv configures whether to use environment variables.
-//
-// Default: false
-//
-// Deprecated: currently this does nothing.
-func WithEnv() Option {
- return func(cfg *config) {}
-}
-
-func memlimitLogger(logger *slog.Logger) *slog.Logger {
- if logger == nil {
- return slog.New(noopLogger{})
- }
- return logger.With(slog.String("package", "github.com/KimMachineGun/automemlimit/memlimit"))
-}
-
-// SetGoMemLimitWithOpts sets GOMEMLIMIT with options and environment variables.
-//
-// You can configure how much memory of the cgroup's memory limit to set as GOMEMLIMIT
-// through AUTOMEMLIMIT environment variable in the half-open range (0.0,1.0].
-//
-// If AUTOMEMLIMIT is not set, it defaults to 0.9. (10% is the headroom for memory sources the Go runtime is unaware of.)
-// If GOMEMLIMIT is already set or AUTOMEMLIMIT=off, this function does nothing.
-//
-// If AUTOMEMLIMIT_EXPERIMENT is set, it enables experimental features.
-// Please see the documentation of Experiments for more details.
-//
-// Options:
-// - WithRatio
-// - WithProvider
-// - WithLogger
-func SetGoMemLimitWithOpts(opts ...Option) (_ int64, _err error) {
- // init config
- cfg := &config{
- logger: slog.New(noopLogger{}),
- ratio: defaultAUTOMEMLIMIT,
- provider: FromCgroup,
- }
- // TODO: remove this
- if debug, ok := os.LookupEnv(envAUTOMEMLIMIT_DEBUG); ok {
- defaultLogger := memlimitLogger(slog.Default())
- defaultLogger.Warn("AUTOMEMLIMIT_DEBUG is deprecated, use memlimit.WithLogger instead")
- if debug == "true" {
- cfg.logger = defaultLogger
- }
- }
- for _, opt := range opts {
- opt(cfg)
- }
-
- // log error if any on return
- defer func() {
- if _err != nil {
- cfg.logger.Error("failed to set GOMEMLIMIT", slog.Any("error", _err))
- }
- }()
-
- // parse experiments
- exps, err := parseExperiments()
- if err != nil {
- return 0, fmt.Errorf("failed to parse experiments: %w", err)
- }
- if exps.System {
- cfg.logger.Info("system experiment is enabled: using system memory limit as a fallback")
- cfg.provider = ApplyFallback(cfg.provider, FromSystem)
- }
-
- // rollback to previous memory limit on panic
- snapshot := debug.SetMemoryLimit(-1)
- defer rollbackOnPanic(cfg.logger, snapshot, &_err)
-
- // check if GOMEMLIMIT is already set
- if val, ok := os.LookupEnv(envGOMEMLIMIT); ok {
- cfg.logger.Info("GOMEMLIMIT is already set, skipping", slog.String(envGOMEMLIMIT, val))
- return 0, nil
- }
-
- // parse AUTOMEMLIMIT
- ratio := cfg.ratio
- if val, ok := os.LookupEnv(envAUTOMEMLIMIT); ok {
- if val == "off" {
- cfg.logger.Info("AUTOMEMLIMIT is set to off, skipping")
- return 0, nil
- }
- ratio, err = strconv.ParseFloat(val, 64)
- if err != nil {
- return 0, fmt.Errorf("cannot parse AUTOMEMLIMIT: %s", val)
- }
- }
-
- // apply ratio to the provider
- provider := capProvider(ApplyRatio(cfg.provider, ratio))
-
- // set the memory limit and start refresh
- limit, err := updateGoMemLimit(uint64(snapshot), provider, cfg.logger)
- go refresh(provider, cfg.logger, cfg.refresh)
- if err != nil {
- if errors.Is(err, ErrNoLimit) {
- cfg.logger.Info("memory is not limited, skipping")
- // TODO: consider returning the snapshot
- return 0, nil
- }
- return 0, fmt.Errorf("failed to set GOMEMLIMIT: %w", err)
- }
-
- return int64(limit), nil
-}
-
-// updateGoMemLimit updates the Go's memory limit, if it has changed.
-func updateGoMemLimit(currLimit uint64, provider Provider, logger *slog.Logger) (uint64, error) {
- newLimit, err := provider()
- if err != nil {
- return 0, err
- }
-
- if newLimit == currLimit {
- logger.Debug("GOMEMLIMIT is not changed, skipping", slog.Uint64(envGOMEMLIMIT, newLimit))
- return newLimit, nil
- }
-
- debug.SetMemoryLimit(int64(newLimit))
- logger.Info("GOMEMLIMIT is updated", slog.Uint64(envGOMEMLIMIT, newLimit), slog.Uint64("previous", currLimit))
-
- return newLimit, nil
-}
-
-// refresh periodically fetches the memory limit from the provider and reapplies it if it has changed.
-// See more details in the documentation of WithRefreshInterval.
-func refresh(provider Provider, logger *slog.Logger, refresh time.Duration) {
- if refresh == 0 {
- return
- }
-
- provider = noErrNoLimitProvider(provider)
-
- t := time.NewTicker(refresh)
- for range t.C {
- err := func() (_err error) {
- snapshot := debug.SetMemoryLimit(-1)
- defer rollbackOnPanic(logger, snapshot, &_err)
-
- _, err := updateGoMemLimit(uint64(snapshot), provider, logger)
- if err != nil {
- return err
- }
-
- return nil
- }()
- if err != nil {
- logger.Error("failed to refresh GOMEMLIMIT", slog.Any("error", err))
- }
- }
-}
-
-// rollbackOnPanic rollbacks to the snapshot on panic.
-// Since it uses recover, it should be called in a deferred function.
-func rollbackOnPanic(logger *slog.Logger, snapshot int64, err *error) {
- panicErr := recover()
- if panicErr != nil {
- if *err != nil {
- logger.Error("failed to set GOMEMLIMIT", slog.Any("error", *err))
- }
- *err = fmt.Errorf("panic during setting the Go's memory limit, rolling back to previous limit %d: %v",
- snapshot, panicErr,
- )
- debug.SetMemoryLimit(snapshot)
- }
-}
-
-// SetGoMemLimitWithEnv sets GOMEMLIMIT with the value from the environment variables.
-// Since WithEnv is deprecated, this function is equivalent to SetGoMemLimitWithOpts().
-// Deprecated: use SetGoMemLimitWithOpts instead.
-func SetGoMemLimitWithEnv() {
- _, _ = SetGoMemLimitWithOpts()
-}
-
-// SetGoMemLimit sets GOMEMLIMIT with the value from the cgroup's memory limit and given ratio.
-func SetGoMemLimit(ratio float64) (int64, error) {
- return SetGoMemLimitWithOpts(WithRatio(ratio))
-}
-
-// SetGoMemLimitWithProvider sets GOMEMLIMIT with the value from the given provider and ratio.
-func SetGoMemLimitWithProvider(provider Provider, ratio float64) (int64, error) {
- return SetGoMemLimitWithOpts(WithProvider(provider), WithRatio(ratio))
-}
-
-func noErrNoLimitProvider(provider Provider) Provider {
- return func() (uint64, error) {
- limit, err := provider()
- if errors.Is(err, ErrNoLimit) {
- return math.MaxInt64, nil
- }
- return limit, err
- }
-}
-
-func capProvider(provider Provider) Provider {
- return func() (uint64, error) {
- limit, err := provider()
- if err != nil {
- return 0, err
- } else if limit > math.MaxInt64 {
- return math.MaxInt64, nil
- }
- return limit, nil
- }
-}