Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAAS-22402: add early exits when possible memusage #87

Merged
merged 6 commits into from
Aug 1, 2023
Merged
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
6 changes: 5 additions & 1 deletion .evg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ tasks:
export PATH=$ROOT_DIR/go/bin:$PATH

cd goja
go test -v ./...
go test -v ./... >> ${workdir}/goja.suite
- command: gotest.parse_files
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[drive-by] to get tests under tests tab in EVG

params:
files:
- "${workdir}/goja.suite"

buildvariants:
- name: linux-64
Expand Down
13 changes: 3 additions & 10 deletions array.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,14 +547,13 @@
}

func toIdx(v valueInt) uint32 {
if v >= 0 && v < math.MaxUint32 {

Check failure on line 550 in array.go

View workflow job for this annotation

GitHub Actions / test (1.16.x, ubuntu-latest, 386)

constant 4294967295 overflows valueInt
return uint32(v)
}
return math.MaxUint32
}

var (
errMemUsageExceedsLimitNil = errors.New("error checking mem usage limit")
errArrayLenExceedsThresholdNil = errors.New("error checking array len threshold")
)

Expand Down Expand Up @@ -582,11 +581,8 @@
if err != nil {
return runningEstimate, err
}
if ctx.MemUsageExceedsLimit == nil {
return runningEstimate, errMemUsageExceedsLimitNil
}
if ctx.MemUsageExceedsLimit(total) {
return runningEstimate, nil
if exceeded := ctx.MemUsageLimitExceeded(total); exceeded {
return total, nil
}
}

Expand Down Expand Up @@ -634,12 +630,9 @@
if err != nil {
return total, err
}
if ctx.MemUsageExceedsLimit == nil {
return total, errMemUsageExceedsLimitNil
}
// This is an early exit in case we reach the mem usage
// limit before we get to scan the whole array.
if ctx.MemUsageExceedsLimit(total) {
if exceeded := ctx.MemUsageLimitExceeded(total); exceeded {
return total, nil
}
}
Expand Down
3 changes: 3 additions & 0 deletions array_sparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,9 @@ func (a *sparseArrayObject) MemUsage(ctx *MemUsageContext) (uint64, error) {
if err != nil {
return total, err
}
if exceeded := ctx.MemUsageLimitExceeded(total); exceeded {
return total, nil
}
}
}

Expand Down
74 changes: 74 additions & 0 deletions array_sparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,77 @@ func TestSparseArrayExportToSlice(t *testing.T) {
}
}
}

func TestSparseArrayObjectMemUsage(t *testing.T) {
tests := []struct {
name string
mu *MemUsageContext
sao *sparseArrayObject
expected uint64
errExpected error
}{
{
name: "mem below threshold",
mu: NewMemUsageContext(New(), 88, 5000, 50, 50, TestNativeMemUsageChecker{}),
sao: &sparseArrayObject{
items: []sparseArrayItem{
{
idx: 1,
value: New()._newString(newStringValue("key"), nil),
},
},
},
expected: 45,
errExpected: nil,
},
{
name: "mem way above threshold returns first crossing of threshold",
mu: NewMemUsageContext(New(), 88, 100, 50, 50, TestNativeMemUsageChecker{}),
sao: &sparseArrayObject{
items: []sparseArrayItem{
{
idx: 1,
value: New()._newString(newStringValue("key"), nil),
},
{
idx: 2,
value: New()._newString(newStringValue("key1"), nil),
},
{
idx: 3,
value: New()._newString(newStringValue("key2"), nil),
},
{
idx: 4,
value: New()._newString(newStringValue("key3"), nil),
},
{
idx: 5,
value: New()._newString(newStringValue("key4"), nil),
},
{
idx: 6,
value: New()._newString(newStringValue("key5"), nil),
},
},
},
expected: 127,
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, err := tc.sao.MemUsage(tc.mu)
if err == nil && tc.errExpected != nil || err != nil && tc.errExpected == nil {
t.Fatalf("Unexpected error. Actual: %v Expected; %v", err, tc.errExpected)
}
if err != nil && tc.errExpected != nil && err.Error() != tc.errExpected.Error() {
t.Fatalf("Errors do not match. Actual: %v Expected: %v", err, tc.errExpected)
}
if total != tc.expected {
t.Fatalf("Unexpected memory return. Actual: %v Expected: %v", total, tc.expected)
}
})
}
}
76 changes: 76 additions & 0 deletions array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,79 @@ func BenchmarkArraySetEmpty(b *testing.B) {
a.self.setOwnIdx(0, valueTrue, true)
}
}

func TestArrayObjectMemUsage(t *testing.T) {
tests := []struct {
name string
mu *MemUsageContext
ao *arrayObject
expected uint64
errExpected error
}{
{
name: "mem below threshold",
mu: NewMemUsageContext(New(), 88, 5000, 50, 50, TestNativeMemUsageChecker{}),
ao: &arrayObject{
values: []Value{
New()._newString(newStringValue("key"), nil),
},
},
expected: 41,
errExpected: nil,
},
{
name: "mem way above threshold returns first crossing of threshold",
mu: NewMemUsageContext(New(), 88, 100, 50, 50, TestNativeMemUsageChecker{}),
ao: &arrayObject{
values: []Value{
New()._newString(newStringValue("key"), nil),
New()._newString(newStringValue("key1"), nil),
New()._newString(newStringValue("key2"), nil),
New()._newString(newStringValue("key3"), nil),
New()._newString(newStringValue("key4"), nil),
New()._newString(newStringValue("key5"), nil),
},
},
expected: 119,
errExpected: nil,
},
{
name: "array limit function undefined throws error",
mu: &MemUsageContext{
visitTracker: visitTracker{
objsVisited: map[objectImpl]bool{},
stashesVisited: map[*stash]bool{}},
depthTracker: &depthTracker{
curDepth: 0,
maxDepth: 50,
},
NativeMemUsageChecker: &TestNativeMemUsageChecker{},
memoryLimit: 50,
ObjectPropsLenExceedsThreshold: func(objPropsLen int) bool {
// number of obj props beyond which we should estimate mem usage
return objPropsLen > 50
},
},
ao: &arrayObject{
values: []Value{New()._newString(newStringValue("key"), nil)},
},
expected: 16,
errExpected: errArrayLenExceedsThresholdNil,
},
Gabri3l marked this conversation as resolved.
Show resolved Hide resolved
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, err := tc.ao.MemUsage(tc.mu)
if err == nil && tc.errExpected != nil || err != nil && tc.errExpected == nil {
t.Fatalf("Unexpected error. Actual: %v Expected; %v", err, tc.errExpected)
}
if err != nil && tc.errExpected != nil && err.Error() != tc.errExpected.Error() {
t.Fatalf("Errors do not match. Actual: %v Expected: %v", err, tc.errExpected)
}
if total != tc.expected {
t.Fatalf("Unexpected memory return. Actual: %v Expected: %v", total, tc.expected)
}
})
}
}
3 changes: 3 additions & 0 deletions builtin_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ func (mo *mapObject) MemUsage(ctx *MemUsageContext) (uint64, error) {
return total, err
}
}
if exceeded := ctx.MemUsageLimitExceeded(total); exceeded {
return total, nil
}
}

ctx.Ascend()
Expand Down
70 changes: 70 additions & 0 deletions builtin_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,73 @@ func BenchmarkMapDeleteJS(b *testing.B) {
}
}
}

func TestMapObjectMemUsage(t *testing.T) {
tests := []struct {
name string
mu *MemUsageContext
mo *mapObject
expected uint64
errExpected error
}{
{
name: "mem below threshold",
mu: NewMemUsageContext(New(), 88, 5000, 50, 50, TestNativeMemUsageChecker{}),
mo: &mapObject{
m: &orderedMap{
hashTable: map[uint64]*mapEntry{
1: {
key: New()._newString(newStringValue("key"), nil),
value: New()._newString(newStringValue("value"), nil),
},
},
},
},
expected: 60,
errExpected: nil,
},
{
name: "mem way above threshold returns first crossing of threshold",
mu: NewMemUsageContext(New(), 88, 100, 50, 50, TestNativeMemUsageChecker{}),
mo: &mapObject{
m: &orderedMap{
hashTable: map[uint64]*mapEntry{
1: {
key: New()._newString(newStringValue("key"), nil),
value: New()._newString(newStringValue("value"), nil),
},
2: {
key: New()._newString(newStringValue("key"), nil),
value: New()._newString(newStringValue("value"), nil),
},
3: {
key: New()._newString(newStringValue("key"), nil),
value: New()._newString(newStringValue("value"), nil),
},
4: {
key: New()._newString(newStringValue("key"), nil),
value: New()._newString(newStringValue("value"), nil),
},
},
},
},
expected: 112,
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, err := tc.mo.MemUsage(tc.mu)
if err == nil && tc.errExpected != nil || err != nil && tc.errExpected == nil {
t.Fatalf("Unexpected error. Actual: %v Expected; %v", err, tc.errExpected)
}
if err != nil && tc.errExpected != nil && err.Error() != tc.errExpected.Error() {
t.Fatalf("Errors do not match. Actual: %v Expected: %v", err, tc.errExpected)
}
if total != tc.expected {
t.Fatalf("Unexpected memory return. Actual: %v Expected: %v", total, tc.expected)
}
})
}
}
3 changes: 3 additions & 0 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ func (p *Program) MemUsage(ctx *MemUsageContext) (uint64, error) {
if err != nil {
return total, err
}
if exceeded := ctx.MemUsageLimitExceeded(total); exceeded {
return total, nil
}
}

return total, nil
Expand Down
57 changes: 57 additions & 0 deletions compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"sync"
"testing"
"time"
)

const TESTLIB = `
Expand Down Expand Up @@ -5645,3 +5646,59 @@ func BenchmarkCompile(b *testing.B) {
}
}
}

func TestProgramMemUsage(t *testing.T) {
tests := []struct {
name string
mu *MemUsageContext
p *Program
expected uint64
errExpected error
}{
{
name: "mem below threshold",
mu: NewMemUsageContext(New(), 88, 50, 50, 50, TestNativeMemUsageChecker{}),
p: &Program{
values: []Value{
New().newDateObject(time.Now(), true, nil),
},
},
expected: 16,
errExpected: nil,
},
{
name: "mem way above threshold returns first crossing of threshold",
mu: NewMemUsageContext(New(), 88, 50, 50, 50, TestNativeMemUsageChecker{}),
p: &Program{
values: []Value{
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
New().newDateObject(time.Now(), true, nil),
},
},
expected: 64,
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, err := tc.p.MemUsage(tc.mu)
if err == nil && tc.errExpected != nil || err != nil && tc.errExpected == nil {
t.Fatalf("Unexpected error. Actual: %v Expected; %v", err, tc.errExpected)
}
if err != nil && tc.errExpected != nil && err.Error() != tc.errExpected.Error() {
t.Fatalf("Errors do not match. Actual: %v Expected: %v", err, tc.errExpected)
}
if total != tc.expected {
t.Fatalf("Unexpected memory return. Actual: %v Expected: %v", total, tc.expected)
}
})
}
}
Loading
Loading