Skip to content

Commit 5b26fd0

Browse files
committed
feat!: ship v1 release with simplified APIs
1 parent 6d12049 commit 5b26fd0

20 files changed

Lines changed: 1170 additions & 891 deletions

README.md

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ See more details about `GOMEMLIMIT` [here](https://tip.golang.org/doc/gc-guide#M
1010

1111
## Notice
1212

13-
Version `v0.5.0` introduces a fallback to system memory limits as an experimental feature when cgroup limits are unavailable. Activate this by setting `AUTOMEMLIMIT_EXPERIMENT=system`.
14-
You can also use system memory limits via `memlimit.FromSystem` provider directly.
13+
Version `v1.0.0` introduces breaking changes to improve usability by simplifying multiple functions into a single `memlimit.Set()` function with options, and to provide more consistent and predictable behavior.
1514

16-
This feature is under evaluation and might become a default or be removed based on user feedback.
17-
If you have any feedback about this feature, please open an issue.
15+
Key changes from v0.x:
16+
- `SetGoMemLimitWithOpts`, `SetGoMemLimit`, `SetGoMemLimitWithProvider`, `SetGoMemLimitWithEnv` removed; use `Set` instead
17+
- `FromCgroupV1`, `FromCgroupV2`, `FromCgroupHybrid` removed; use `FromCgroup` instead
18+
- `import _ "github.com/KimMachineGun/automemlimit"` now automatically falls back to system memory when cgroup is unavailable
19+
- When `ErrNoLimit` is returned from the provider, GOMEMLIMIT is set to `math.MaxInt64` (unlimited) and returned as a successful result
20+
- `Set` now returns the previous GOMEMLIMIT value instead of 0 when skipping (e.g., `GOMEMLIMIT` already set or `AUTOMEMLIMIT=off`) or when an error occurs
21+
- `WithRefreshInterval(ctx, duration)` now requires a `context.Context` to control the refresh goroutine
22+
- New `WithMin(bytes)` option to set a minimum memory limit
1823

1924
## Installation
2025

@@ -27,41 +32,39 @@ go get github.com/KimMachineGun/automemlimit@latest
2732
```go
2833
package main
2934

30-
// By default, it sets `GOMEMLIMIT` to 90% of cgroup's memory limit.
31-
// This is equivalent to `memlimit.SetGoMemLimitWithOpts(memlimit.WithLogger(slog.Default()))`
32-
// To disable logging, use `memlimit.SetGoMemLimitWithOpts` directly.
35+
// Sets GOMEMLIMIT to 90% of cgroup's memory limit.
36+
// Falls back to system memory if cgroup is unavailable.
3337
import _ "github.com/KimMachineGun/automemlimit"
3438
```
3539

36-
or
40+
> **Note:** `memlimit.Set()` defaults to `memlimit.FromCgroup` only. Use `memlimit.ApplyFallback(memlimit.FromCgroup, memlimit.FromSystem)` explicitly if you need system memory fallback.
41+
42+
For more control, use `memlimit.Set()` directly:
3743

3844
```go
3945
package main
4046

41-
import "github.com/KimMachineGun/automemlimit/memlimit"
47+
import (
48+
"context"
49+
"log/slog"
50+
"time"
4251

43-
func init() {
44-
memlimit.SetGoMemLimitWithOpts(
45-
memlimit.WithRatio(0.9),
46-
memlimit.WithProvider(memlimit.FromCgroup),
47-
memlimit.WithLogger(slog.Default()),
48-
memlimit.WithRefreshInterval(1*time.Minute),
49-
)
50-
memlimit.SetGoMemLimitWithOpts(
51-
memlimit.WithRatio(0.9),
52+
"github.com/KimMachineGun/automemlimit/memlimit"
53+
)
54+
55+
func main() {
56+
ctx, cancel := context.WithCancel(context.Background())
57+
defer cancel()
58+
59+
memlimit.Set(
60+
// Fallback to system memory if cgroup is unavailable
5261
memlimit.WithProvider(
53-
memlimit.ApplyFallback(
54-
memlimit.FromCgroup,
55-
memlimit.FromSystem,
56-
),
62+
memlimit.ApplyFallback(memlimit.FromCgroup, memlimit.FromSystem),
5763
),
58-
memlimit.WithRefreshInterval(1*time.Minute),
64+
memlimit.WithRatio(0.9), // Use 90% of the limit (default)
65+
memlimit.WithMin(512 * 1024 * 1024), // At least 512MB
66+
memlimit.WithRefreshInterval(ctx, time.Minute), // Refresh every minute
67+
memlimit.WithLogger(slog.Default()),
5968
)
60-
memlimit.SetGoMemLimit(0.9)
61-
memlimit.SetGoMemLimitWithProvider(memlimit.Limit(1024*1024), 0.9)
62-
memlimit.SetGoMemLimitWithProvider(memlimit.FromCgroup, 0.9)
63-
memlimit.SetGoMemLimitWithProvider(memlimit.FromCgroupV1, 0.9)
64-
memlimit.SetGoMemLimitWithProvider(memlimit.FromCgroupHybrid, 0.9)
65-
memlimit.SetGoMemLimitWithProvider(memlimit.FromCgroupV2, 0.9)
6669
}
6770
```

automemlimit.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
// Package automemlimit provides automatic GOMEMLIMIT configuration.
2+
//
3+
// Importing this package automatically sets GOMEMLIMIT based on cgroup memory limits,
4+
// falling back to system memory if cgroup is unavailable.
5+
//
6+
// import _ "github.com/KimMachineGun/automemlimit"
7+
//
8+
// For more control, use the memlimit package directly.
19
package automemlimit
210

311
import (
@@ -7,7 +15,13 @@ import (
715
)
816

917
func init() {
10-
memlimit.SetGoMemLimitWithOpts(
18+
memlimit.Set(
19+
memlimit.WithProvider(
20+
memlimit.ApplyFallback(
21+
memlimit.FromCgroup,
22+
memlimit.FromSystem,
23+
),
24+
),
1125
memlimit.WithLogger(slog.Default()),
1226
)
1327
}

examples/dynamic/main.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"context"
56
"errors"
67
"log/slog"
78
"os"
@@ -15,11 +16,12 @@ import (
1516
func init() {
1617
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, nil)))
1718

18-
memlimit.SetGoMemLimitWithOpts(
19+
refreshCtx := context.Background()
20+
memlimit.Set(
1921
memlimit.WithProvider(
2022
FileProvider("limit.txt"),
2123
),
22-
memlimit.WithRefreshInterval(5*time.Second),
24+
memlimit.WithRefreshInterval(refreshCtx, 5*time.Second),
2325
memlimit.WithLogger(slog.Default()),
2426
)
2527
}
@@ -37,7 +39,7 @@ func FileProvider(path string) memlimit.Provider {
3739
b, err := os.ReadFile(path)
3840
if err != nil {
3941
if errors.Is(err, os.ErrNotExist) {
40-
return memlimit.ApplyFallback(memlimit.FromCgroup, memlimit.FromSystem)()
42+
return memlimit.FromCgroup()
4143
}
4244
return 0, err
4345
}

examples/logger/main.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import (
88
)
99

1010
func init() {
11-
memlimit.SetGoMemLimitWithOpts(
12-
memlimit.WithProvider(
13-
memlimit.Limit(1024*1024*1024),
14-
),
11+
memlimit.Set(
1512
memlimit.WithLogger(slog.New(slog.NewJSONHandler(os.Stderr, nil))),
1613
)
1714
}

examples/system/main.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package main
22

3-
import (
4-
"github.com/KimMachineGun/automemlimit/memlimit"
5-
)
3+
import "github.com/KimMachineGun/automemlimit/memlimit"
64

75
func init() {
8-
memlimit.SetGoMemLimitWithOpts(
6+
memlimit.Set(
97
memlimit.WithProvider(
108
memlimit.ApplyFallback(
119
memlimit.FromCgroup,

memlimit/cgroups.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func readMemoryLimitV1FromPath(cgroupPath string) (uint64, error) {
222222
if err != nil {
223223
return 0, fmt.Errorf("failed to parse memory.limit_in_bytes value: %w", err)
224224
} else if lib == 0 {
225-
hml = math.MaxUint64
225+
lib = math.MaxUint64
226226
}
227227

228228
// use the minimum value between hierarchical_memory_limit and memory.limit_in_bytes.

memlimit/cgroups_linux.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,3 @@ package memlimit
77
func FromCgroup() (uint64, error) {
88
return fromCgroup(detectCgroupVersion)
99
}
10-
11-
// FromCgroupV1 retrieves the memory limit from the cgroup v1 controller.
12-
// After v1.0.0, this function could be removed and FromCgroup should be used instead.
13-
func FromCgroupV1() (uint64, error) {
14-
return fromCgroup(func(_ []mountInfo) (bool, bool) {
15-
return true, false
16-
})
17-
}
18-
19-
// FromCgroupHybrid retrieves the memory limit from the cgroup v2 and v1 controller sequentially,
20-
// basically, it is equivalent to FromCgroup.
21-
// After v1.0.0, this function could be removed and FromCgroup should be used instead.
22-
func FromCgroupHybrid() (uint64, error) {
23-
return FromCgroup()
24-
}
25-
26-
// FromCgroupV2 retrieves the memory limit from the cgroup v2 controller.
27-
// After v1.0.0, this function could be removed and FromCgroup should be used instead.
28-
func FromCgroupV2() (uint64, error) {
29-
return fromCgroup(func(_ []mountInfo) (bool, bool) {
30-
return false, true
31-
})
32-
}

memlimit/cgroups_linux_test.go

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package memlimit
55

66
import (
7+
"errors"
78
"testing"
89
)
910

@@ -13,58 +14,16 @@ func TestFromCgroup(t *testing.T) {
1314
}
1415

1516
limit, err := FromCgroup()
16-
if cgVersion == 0 && err != ErrNoCgroup {
17-
t.Fatalf("FromCgroup() error = %v, wantErr %v", err, ErrNoCgroup)
17+
if cgVersion == 0 {
18+
if !errors.Is(err, ErrNoCgroup) {
19+
t.Errorf("FromCgroup() error = %v, wantErr %v", err, ErrNoCgroup)
20+
}
21+
return
1822
}
19-
2023
if err != nil {
2124
t.Fatalf("FromCgroup() error = %v, wantErr %v", err, nil)
2225
}
2326
if limit != expected {
2427
t.Fatalf("FromCgroup() got = %v, want %v", limit, expected)
2528
}
2629
}
27-
28-
func TestFromCgroupHybrid(t *testing.T) {
29-
if expected == 0 {
30-
t.Skip()
31-
}
32-
33-
limit, err := FromCgroupHybrid()
34-
if cgVersion == 0 && err != ErrNoCgroup {
35-
t.Fatalf("FromCgroupHybrid() error = %v, wantErr %v", err, ErrNoCgroup)
36-
}
37-
38-
if err != nil {
39-
t.Fatalf("FromCgroupHybrid() error = %v, wantErr %v", err, nil)
40-
}
41-
if limit != expected {
42-
t.Fatalf("FromCgroupHybrid() got = %v, want %v", limit, expected)
43-
}
44-
}
45-
46-
func TestFromCgroupV1(t *testing.T) {
47-
if expected == 0 || cgVersion != 1 {
48-
t.Skip()
49-
}
50-
limit, err := FromCgroupV1()
51-
if err != nil {
52-
t.Fatalf("FromCgroupV1() error = %v, wantErr %v", err, nil)
53-
}
54-
if limit != expected {
55-
t.Fatalf("FromCgroupV1() got = %v, want %v", limit, expected)
56-
}
57-
}
58-
59-
func TestFromCgroupV2(t *testing.T) {
60-
if expected == 0 || cgVersion != 2 {
61-
t.Skip()
62-
}
63-
limit, err := FromCgroupV2()
64-
if err != nil {
65-
t.Fatalf("FromCgroupV2() error = %v, wantErr %v", err, nil)
66-
}
67-
if limit != expected {
68-
t.Fatalf("FromCgroupV2() got = %v, want %v", limit, expected)
69-
}
70-
}

0 commit comments

Comments
 (0)