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-27268: add early exit in mem usage where missing #109

Merged
merged 7 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
32 changes: 0 additions & 32 deletions memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,22 +654,6 @@ func TestMemArraysWithLenThreshold(t *testing.T) {
// [..other, true, 50]
+1,
},
{
desc: "array under threshold but over limit",
script: `y = []
y.push([]);
let i = 0;
checkMem();
for(i=0;i<10;i++){
y[0].push(i);
};
checkMem()`,
threshold: 200,
memLimit: 100,
// Array overhead, size of property values, only 3 values before we hit the mem limit
expectedSizeDiff: SizeEmptyStruct + (3 * SizeNumber),
expectedNewSizeDiff: SizeEmptyStruct + (3 * SizeNumber),
},
{
desc: "mixed array over threshold",
script: `y = []
Expand Down Expand Up @@ -795,22 +779,6 @@ func TestMemObjectsWithPropsLenThreshold(t *testing.T) {
// object overhead + len("i0") + value i
expectedNewSizeDiff: SizeEmptyStruct + 10*(2+SizeString) + 10*SizeNumber,
},
{
desc: "object under threshold but over limit",
script: `y = {}
let i = 0;
checkMem();
for (i=0;i<10;i++) {
y["i"+i] = i
}
checkMem()`,
threshold: 100,
memLimit: 40,
// object overhead + len("i0") + value i
expectedSizeDiff: SizeEmptyStruct + 10*(2+SizeString) + 10*SizeNumber,
// object overhead + len("i0") + value i
expectedNewSizeDiff: SizeEmptyStruct + 10*(2+SizeString) + 10*SizeNumber,
},
{
desc: "object over threshold",
script: `y = {}
Expand Down
3 changes: 3 additions & 0 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,9 @@ func (o *baseObject) MemUsage(ctx *MemUsageContext) (memUsage uint64, newMemUsag
if err != nil {
return memUsage, newMemUsage, err
}
if exceeded := ctx.MemUsageLimitExceeded(memUsage); exceeded {
return memUsage, newMemUsage, nil
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions object_gomap.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ func (o *objectGoMapSimple) MemUsage(ctx *MemUsageContext) (uint64, uint64, erro
if err != nil {
return mem, newMem, err
}
if exceeded := ctx.MemUsageLimitExceeded(mem); exceeded {
return mem, newMem, nil
}
}
return mem, newMem, nil
}
30 changes: 29 additions & 1 deletion object_gomap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ func TestGoMapMemUsage(t *testing.T) {
tests := []struct {
name string
val *objectGoMapSimple
memLimit uint64
expectedMem uint64
expectedNewMem uint64
errExpected error
Expand All @@ -365,6 +366,7 @@ func TestGoMapMemUsage(t *testing.T) {
"test1": valueInt(99),
},
},
memLimit: 100,
// baseObject overhead + len("testN") with string overhead + value
expectedMem: SizeEmptyStruct + ((5+SizeString)+SizeInt)*2,
// baseObject overhead + len("testN") with string overhead + value
Expand All @@ -382,6 +384,7 @@ func TestGoMapMemUsage(t *testing.T) {
"test1": 99,
},
},
memLimit: 100,
// baseObject overhead + len("testN") with string overhead + value
expectedMem: SizeEmptyStruct + ((5+SizeString)+SizeInt)*2,
// baseObject overhead + len("testN") with string overhead + value
Expand All @@ -398,6 +401,7 @@ func TestGoMapMemUsage(t *testing.T) {
"test": nil,
},
},
memLimit: 100,
// overhead + len("test") with string overhead + null
expectedMem: SizeEmptyStruct + (4 + SizeString) + SizeEmptyStruct,
// overhead + len("test") with string overhead + null
Expand All @@ -414,6 +418,7 @@ func TestGoMapMemUsage(t *testing.T) {
"test": nestedMap,
},
},
memLimit: 100,
// overhead + len("testN") with string overhead + (Object prototype with overhead + values with string overhead)
expectedMem: SizeEmptyStruct + (4 + SizeString) + nestedMapMemUsage,
// overhead + len("testN") with string overhead + (Object prototype with overhead + values with string overhead)
Expand All @@ -430,17 +435,40 @@ func TestGoMapMemUsage(t *testing.T) {
"test": &nestedMap,
},
},
memLimit: 100,
// overhead + len("testN") with string overhead + nested overhead
expectedMem: SizeEmptyStruct + (4 + SizeString) + SizeEmptyStruct,
// overhead + len("testN") with string overhead + nested overhead
expectedNewMem: SizeEmptyStruct + (4 + SizeString) + SizeEmptyStruct,
errExpected: nil,
},
{
name: "should exit early when exceeding memory limit",
val: &objectGoMapSimple{
baseObject: baseObject{
val: &Object{runtime: vm},
},
data: map[string]interface{}{
"test0": valueInt(99),
"test1": valueInt(99),
"test2": valueInt(99),
"test3": valueInt(99),
"test4": valueInt(99),
"test5": valueInt(99),
},
},
memLimit: 0,
nickpoindexter marked this conversation as resolved.
Show resolved Hide resolved
// baseObject overhead + len("testN") with string overhead + value
expectedMem: SizeEmptyStruct + ((5 + SizeString) + SizeInt),
// baseObject overhead + len("testN") with string overhead + value
expectedNewMem: SizeEmptyStruct + ((5 + SizeString) + SizeInt),
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(vm, 100, 100, 100, 100, nil))
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(vm, 100, tc.memLimit, 100, 100, nil))
if err != tc.errExpected {
t.Fatalf("Unexpected error. Actual: %v Expected: %v", err, tc.errExpected)
}
Expand Down
3 changes: 3 additions & 0 deletions object_goslice.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ func (o *objectGoSlice) MemUsage(ctx *MemUsageContext) (uint64, uint64, error) {
if err != nil {
return mem, newMem, err
}
if exceeded := ctx.MemUsageLimitExceeded(mem); exceeded {
return mem, newMem, nil
}
}
return mem, newMem, nil
}
27 changes: 26 additions & 1 deletion object_goslice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func TestGoSliceMemUsage(t *testing.T) {
tests := []struct {
name string
val *objectGoSlice
memLimit uint64
expectedMem uint64
expectedNewMem uint64
errExpected error
Expand All @@ -292,6 +293,7 @@ func TestGoSliceMemUsage(t *testing.T) {
valueInt(99),
},
},
memLimit: memUsageLimit,
// overhead + values
expectedMem: SizeEmptyStruct + SizeInt*2,
// overhead + values
Expand All @@ -305,6 +307,7 @@ func TestGoSliceMemUsage(t *testing.T) {
val: &Object{runtime: vm},
},
},
memLimit: memUsageLimit,
// overhead
expectedMem: SizeEmptyStruct,
// overhead
Expand All @@ -322,6 +325,7 @@ func TestGoSliceMemUsage(t *testing.T) {
99,
},
},
memLimit: memUsageLimit,
// overhead + values
expectedMem: SizeEmptyStruct + SizeInt*2,
// overhead + values
Expand All @@ -336,6 +340,7 @@ func TestGoSliceMemUsage(t *testing.T) {
},
data: &[]interface{}{nil},
},
memLimit: memUsageLimit,
// overhead + null
expectedMem: SizeEmptyStruct + SizeEmptyStruct,
// overhead + null
Expand All @@ -355,6 +360,7 @@ func TestGoSliceMemUsage(t *testing.T) {
},
},
},
memLimit: memUsageLimit,
// default + default since we don't account for objectGoSlice in (*Object).MemUsage
expectedMem: SizeEmptyStruct + SizeEmptyStruct,
// overhead + (value + len("length") with string overhead + "length".value + prototype + ints)
Expand All @@ -374,17 +380,36 @@ func TestGoSliceMemUsage(t *testing.T) {
},
},
},
memLimit: memUsageLimit,
// default + default since we don't account for objectGoSlice in (*Object).MemUsage
expectedMem: SizeEmptyStruct + SizeEmptyStruct,
// overhead + (value + len("length") with string overhead + "length".value + prototype + ints)
expectedNewMem: SizeEmptyStruct + (SizeEmptyStruct + (6 + SizeString) + SizeEmptyStruct + SizeEmptyStruct + SizeNumber*2),
errExpected: nil,
},
{
name: "should exit early when exceeding the memory limit",
val: &objectGoSlice{
baseObject: baseObject{
val: &Object{runtime: vm},
},
data: &[]interface{}{
valueInt(99),
valueInt(99),
},
},
memLimit: 0,
// overhead + values
expectedMem: SizeEmptyStruct + SizeInt,
// overhead + values
expectedNewMem: SizeEmptyStruct + SizeInt,
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(vm, 100, 100000, 100, 100, nil))
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(vm, 100, tc.memLimit, 100, 100, nil))
if err != tc.errExpected {
t.Fatalf("Unexpected error. Actual: %v Expected: %v", err, tc.errExpected)
}
Expand Down
33 changes: 32 additions & 1 deletion object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,13 +651,15 @@ func TestBaseObjectMemUsage(t *testing.T) {
name string
val *baseObject
threshold int
memLimit uint64
expectedMem uint64
expectedNewMem uint64
errExpected error
}{
{
name: "should have a value of SizeEmptyStruct given a nil object",
threshold: 100,
memLimit: 100,
val: nil,
expectedMem: SizeEmptyStruct,
expectedNewMem: SizeEmptyStruct,
Expand All @@ -666,6 +668,7 @@ func TestBaseObjectMemUsage(t *testing.T) {
{
name: "should have a value of SizeEmptyStruct given an empty object",
threshold: 100,
memLimit: 100,
val: &baseObject{},
expectedMem: SizeEmptyStruct,
expectedNewMem: SizeEmptyStruct,
Expand All @@ -674,6 +677,7 @@ func TestBaseObjectMemUsage(t *testing.T) {
{
name: "should account for each key value pair given a non-empty object",
threshold: 100,
memLimit: 100,
val: &baseObject{propNames: []unistring.String{"test"}, values: map[unistring.String]Value{"test": valueInt(99)}},
// overhead + len("test") with string overhead + value
expectedMem: SizeEmptyStruct + (4 + SizeString) + SizeInt,
Expand All @@ -684,6 +688,7 @@ func TestBaseObjectMemUsage(t *testing.T) {
{
name: "should account for each key value pair given a non-empty object with a nil value",
threshold: 100,
memLimit: 100,
val: &baseObject{propNames: []unistring.String{"test"}, values: map[unistring.String]Value{"test": nil}},
// overhead + len("test") with string overhead
expectedMem: SizeEmptyStruct + (4 + SizeString),
Expand All @@ -694,6 +699,7 @@ func TestBaseObjectMemUsage(t *testing.T) {
{
name: "should account for sampled key value pair given a non-empty object over threshold",
threshold: 20,
memLimit: 100,
val: &baseObject{
propNames: []unistring.String{
"test0",
Expand All @@ -717,18 +723,43 @@ func TestBaseObjectMemUsage(t *testing.T) {
{
name: "should account for prototype's given an object with a valid prototype",
threshold: 100,
memLimit: 100,
val: &baseObject{prototype: &Object{}},
// baseObject overhead + prototype overhead
expectedMem: SizeEmptyStruct + SizeEmptyStruct,
// baseObject overhead + prototype overhead
expectedNewMem: SizeEmptyStruct + SizeEmptyStruct,
errExpected: nil,
},
{
name: "should exit early on first iteration given an object over the memory limit",
threshold: 100,
memLimit: 0,
val: &baseObject{
propNames: []unistring.String{
"test0",
"test1",
"test2",
"test3",
},
values: map[unistring.String]Value{
"test0": valueInt(99),
"test1": valueInt(99),
"test2": valueInt(99),
"test3": valueInt(99),
},
},
// overhead + len("testN") with string overhead + value
expectedMem: SizeEmptyStruct + ((5 + SizeString) + SizeInt),
// overhead + len("testN") with string overhead + value
expectedNewMem: SizeEmptyStruct + ((5 + SizeString) + SizeInt),
errExpected: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(New(), 100, 100, 100, tc.threshold, nil))
total, newTotal, err := tc.val.MemUsage(NewMemUsageContext(New(), 100, tc.memLimit, 100, tc.threshold, nil))
if err != tc.errExpected {
t.Fatalf("Unexpected error. Actual: %v Expected: %v", err, tc.errExpected)
}
Expand Down
3 changes: 3 additions & 0 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@
}
r.vm.init()

r.SetRateLimiter(rate.NewLimiter(rate.Inf, maxInt))

Check failure on line 483 in runtime.go

View workflow job for this annotation

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

constant 9007199254740992 overflows int
}

func (r *Runtime) typeErrorResult(throw bool, args ...interface{}) {
Expand Down Expand Up @@ -515,6 +515,9 @@
if err != nil {
return memUsage, newMemUsage, err
}
if exceeded := ctx.MemUsageLimitExceeded(memUsage); exceeded {
Gabri3l marked this conversation as resolved.
Show resolved Hide resolved
return memUsage, newMemUsage, nil
}
}
}

Expand Down
Loading
Loading