Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions api/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1926,3 +1926,17 @@ func TestRestoreKeycardAccountAndLogin(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, acc)
}

func TestReleaseOSMemory(t *testing.T) {
b := NewGethStatusBackend(tt.MustCreateTestLogger())

// the first call should succeed
require.NoError(t, b.ReleaseOSMemory())

// the second immediate call should be skipped due to rate limiting
require.Error(t, b.ReleaseOSMemory())

// reset the lastGCTime to allow the next call
b.lastGCTime = time.Now().Add(-(gcInterval + time.Second))
require.NoError(t, b.ReleaseOSMemory())
}
50 changes: 50 additions & 0 deletions api/geth_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"math/big"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -66,6 +68,10 @@ import (
"github.com/status-im/status-go/walletdatabase"
)

const (
gcInterval = 10 * time.Second
)

var (
// ErrWhisperClearIdentitiesFailure clearing whisper identities has failed.
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
Expand Down Expand Up @@ -110,6 +116,9 @@ type GethStatusBackend struct {

logger *zap.Logger
preLoginLogConfig *logutils.PreLoginLogConfig

lastGCTime time.Time
lastGCTimeLock sync.Mutex
}

// NewGethStatusBackend create a new GethStatusBackend instance
Expand Down Expand Up @@ -3036,3 +3045,44 @@ func (b *GethStatusBackend) SetPreLoginLogLevel(level string) error {
}
return logutils.OverrideRootLoggerWithConfig(b.preLoginLogConfig.ConvertToLogSettings())
}

func (b *GethStatusBackend) ReleaseOSMemory() error {
b.lastGCTimeLock.Lock()
defer b.lastGCTimeLock.Unlock()

// Only run GC if 10 seconds have passed since the last call
if time.Since(b.lastGCTime) < gcInterval {
return errors.New("skipping GC because it was called too recently")
}
b.releaseOSMemory()
b.lastGCTime = time.Now()
return nil
}

func (b *GethStatusBackend) releaseOSMemory() {
var before, after runtime.MemStats

// Collect stats before GC
runtime.ReadMemStats(&before)
b.logger.Debug("Before garbage collection",
zap.Uint64("heap_alloc_mb", before.HeapAlloc/1024/1024),
zap.Uint64("heap_objects", before.HeapObjects),
zap.Uint64("sys_mb", before.Sys/1024/1024),
)

runtime.GC()
debug.FreeOSMemory()

// Collect stats after GC
runtime.ReadMemStats(&after)

// Log results with differences
b.logger.Debug("After garbage collection",
zap.Uint64("heap_alloc_mb", after.HeapAlloc/1024/1024),
zap.Uint64("heap_objects", after.HeapObjects),
zap.Uint64("sys_mb", after.Sys/1024/1024),
zap.Int64("freed_mb", int64(before.HeapAlloc-after.HeapAlloc)/1024/1024),
zap.Int64("objects_freed", int64(before.HeapObjects-after.HeapObjects)),
zap.Int64("released_mb", int64(after.HeapReleased-before.HeapReleased)/1024/1024),
)
}
9 changes: 9 additions & 0 deletions mobile/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2429,3 +2429,12 @@ func IntendedPanic(message string) string {
panic(err)
})
}

func ReleaseOSMemory() string {
return callWithResponse(releaseOSMemory)
}

func releaseOSMemory() string {
err := statusBackend.ReleaseOSMemory()
return makeJSONResponse(err)
}
7 changes: 7 additions & 0 deletions mobile/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,10 @@ func TestIntendedPanic(t *testing.T) {
IntendedPanic(message)
})
}

// TestReleaseOSMemory is a simple test to ensure the function doesn't panic
func TestReleaseOSMemory(t *testing.T) {
require.NotPanics(t, func() {
ReleaseOSMemory()
})
}