Skip to content

Commit a5be759

Browse files
committed
feat_: implement low memory mode with garbage collection logging
1 parent e1096ea commit a5be759

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

mobile/status.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"os"
99
"path"
1010
"runtime"
11+
"runtime/debug"
12+
"sync"
1113
"time"
1214
"unsafe"
1315

@@ -57,6 +59,15 @@ import (
5759
"github.com/status-im/status-go/signal"
5860
)
5961

62+
var (
63+
lastGCTime time.Time
64+
lastGCTimeLock sync.Mutex
65+
)
66+
67+
const (
68+
gcInterval = 10 * time.Second
69+
)
70+
6071
func call(fn any, params ...any) any {
6172
return callog.Call(logutils.ZapLogger(), requestlog.GetRequestLogger(), fn, params...)
6273
}
@@ -2429,3 +2440,52 @@ func IntendedPanic(message string) string {
24292440
panic(err)
24302441
})
24312442
}
2443+
2444+
func SwitchToLowMemoryMode() string {
2445+
return callWithResponse(switchToLowMemoryMode)
2446+
}
2447+
2448+
func switchToLowMemoryMode() string {
2449+
lastGCTimeLock.Lock()
2450+
defer lastGCTimeLock.Unlock()
2451+
2452+
var err error
2453+
// Only run GC if 10 seconds have passed since the last call
2454+
if time.Since(lastGCTime) >= gcInterval {
2455+
releaseMemory()
2456+
lastGCTime = time.Now()
2457+
} else {
2458+
err = errors.New("skipping GC because it was called too recently")
2459+
}
2460+
2461+
return makeJSONResponse(err)
2462+
}
2463+
2464+
func releaseMemory() {
2465+
logger := logutils.ZapLogger()
2466+
var before, after runtime.MemStats
2467+
2468+
// Collect stats before GC
2469+
runtime.ReadMemStats(&before)
2470+
logger.Info("Before garbage collection",
2471+
zap.Uint64("heap_alloc_mb", before.HeapAlloc/1024/1024),
2472+
zap.Uint64("heap_objects", before.HeapObjects),
2473+
zap.Uint64("sys_mb", before.Sys/1024/1024),
2474+
)
2475+
2476+
runtime.GC()
2477+
debug.FreeOSMemory()
2478+
2479+
// Collect stats after GC
2480+
runtime.ReadMemStats(&after)
2481+
2482+
// Log results with differences
2483+
logger.Info("After garbage collection",
2484+
zap.Uint64("heap_alloc_mb", after.HeapAlloc/1024/1024),
2485+
zap.Uint64("heap_objects", after.HeapObjects),
2486+
zap.Uint64("sys_mb", after.Sys/1024/1024),
2487+
zap.Int64("freed_mb", int64(before.HeapAlloc-after.HeapAlloc)/1024/1024),
2488+
zap.Int64("objects_freed", int64(before.HeapObjects-after.HeapObjects)),
2489+
zap.Int64("released_mb", int64(after.HeapReleased-before.HeapReleased)/1024/1024),
2490+
)
2491+
}

mobile/status_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package statusgo
22

33
import (
44
"encoding/json"
5+
"sync"
56
"testing"
7+
"time"
68

79
"github.com/stretchr/testify/require"
810

@@ -47,3 +49,72 @@ func TestIntendedPanic(t *testing.T) {
4749
IntendedPanic(message)
4850
})
4951
}
52+
53+
func TestSwitchToLowMemoryMode(t *testing.T) {
54+
// Reset the lastGCTime to ensure we can trigger GC
55+
lastGCTimeLock.Lock()
56+
lastGCTime = time.Time{}
57+
lastGCTimeLock.Unlock()
58+
59+
// First call should succeed
60+
result := SwitchToLowMemoryMode()
61+
62+
var response APIResponse
63+
err := json.Unmarshal([]byte(result), &response)
64+
if err != nil {
65+
t.Fatalf("Failed to unmarshal response: %v", err)
66+
}
67+
68+
if response.Error != "" {
69+
t.Errorf("Expected no error on first call, got: %s", response.Error)
70+
}
71+
72+
// Second immediate call should be skipped due to rate limiting
73+
result = SwitchToLowMemoryMode()
74+
75+
err = json.Unmarshal([]byte(result), &response)
76+
if err != nil {
77+
t.Fatalf("Failed to unmarshal response: %v", err)
78+
}
79+
80+
if response.Error == "" {
81+
t.Error("Expected error on second immediate call, but got none")
82+
}
83+
84+
// The error should indicate that GC was skipped
85+
if response.Error != "skipping GC because it was called too recently" {
86+
t.Errorf("Unexpected error message: %s", response.Error)
87+
}
88+
89+
// Test concurrent access
90+
var wg sync.WaitGroup
91+
for i := 0; i < 5; i++ {
92+
wg.Add(1)
93+
go func() {
94+
defer wg.Done()
95+
SwitchToLowMemoryMode()
96+
}()
97+
}
98+
wg.Wait()
99+
100+
// After waiting, we should be able to call it again
101+
lastGCTimeLock.Lock()
102+
lastGCTime = time.Now().Add(-(gcInterval + time.Second))
103+
lastGCTimeLock.Unlock()
104+
105+
result = SwitchToLowMemoryMode()
106+
107+
err = json.Unmarshal([]byte(result), &response)
108+
if err != nil {
109+
t.Fatalf("Failed to unmarshal response: %v", err)
110+
}
111+
112+
if response.Error != "" {
113+
t.Errorf("Expected no error after waiting, got: %s", response.Error)
114+
}
115+
}
116+
117+
// TestReleaseMemory is a simple test to ensure the function doesn't panic
118+
func TestReleaseMemory(t *testing.T) {
119+
releaseMemory()
120+
}

0 commit comments

Comments
 (0)