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 5 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
14 changes: 4 additions & 10 deletions array.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@
}

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
Expand Down Expand Up @@ -582,11 +582,8 @@
if err != nil {
return runningEstimate, err
}
if ctx.MemUsageExceedsLimit == nil {
return runningEstimate, errMemUsageExceedsLimitNil
}
if ctx.MemUsageExceedsLimit(total) {
return runningEstimate, nil
if exceeded, err := ctx.MemUsageLimitExceeded(total); exceeded || err != nil {
return total, err
}
}

Expand Down Expand Up @@ -634,13 +631,10 @@
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) {
return total, nil
if exceeded, err := ctx.MemUsageLimitExceeded(total); exceeded || err != nil {
return total, err
}
}

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, err := ctx.MemUsageLimitExceeded(total); exceeded || err != nil {
return total, err
}
}
}

Expand Down
100 changes: 100 additions & 0 deletions array_sparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,103 @@ 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,
},
{
name: "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{},
ArrayLenExceedsThreshold: func(arrayLen int) bool {
// array length threshold above which we should estimate mem usage
return arrayLen > 50
},
ObjectPropsLenExceedsThreshold: func(objPropsLen int) bool {
// number of obj props beyond which we should estimate mem usage
return objPropsLen > 50
},
},
sao: &sparseArrayObject{
items: []sparseArrayItem{
{
idx: 1,
value: New()._newString(newStringValue("key"), nil),
},
},
},
expected: 37,
errExpected: errMemUsageExceedsLimitNil,
},
}

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)
}
})
}
}
129 changes: 129 additions & 0 deletions array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,132 @@ 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: "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{},
ArrayLenExceedsThreshold: func(arrayLen int) bool {
// array length threshold above which we should estimate mem usage
return arrayLen > 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: 41,
errExpected: errMemUsageExceedsLimitNil,
},
Gabri3l marked this conversation as resolved.
Show resolved Hide resolved
{
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{},
MemUsageExceedsLimit: func(memUsage uint64) bool {
// memory usage limit above which we should stop mem usage computations
return memUsage > 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,
},
{
name: "limit function undefined throws error with array over threshold",
mu: &MemUsageContext{
visitTracker: visitTracker{
objsVisited: map[objectImpl]bool{},
stashesVisited: map[*stash]bool{},
},
depthTracker: &depthTracker{
curDepth: 0,
maxDepth: 50,
},
NativeMemUsageChecker: &TestNativeMemUsageChecker{},
ArrayLenExceedsThreshold: func(arrayLen int) bool {
// array length threshold above which we should estimate mem usage
return arrayLen > 0
},
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: 41,
errExpected: errMemUsageExceedsLimitNil,
},
}

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, err := ctx.MemUsageLimitExceeded(total); exceeded || err != nil {
return total, err
}
}

ctx.Ascend()
Expand Down
Loading
Loading