Skip to content

Commit 4eef96c

Browse files
committed
refactor: clamp result to min/max range
1 parent 5191978 commit 4eef96c

File tree

2 files changed

+19
-5
lines changed

2 files changed

+19
-5
lines changed

packages/utilities/src/memory_calculator.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { all, create, type EvalFunction } from 'mathjs/number';
22

3+
import { ACTOR_LIMITS } from '@apify/consts';
34
import log from '@apify/log';
45

56
import type { LruCache } from '../../datastructures/src/lru_cache';
@@ -23,7 +24,7 @@ export const DEFAULT_MEMORY_MBYTES_MAX_CHARS = 1000;
2324

2425
/**
2526
* A Set of allowed keys from ActorRunOptions that can be used in
26-
* the {{variable}} syntax.
27+
* the {{runOptions.variable}} syntax.
2728
*/
2829
const ALLOWED_RUN_OPTION_KEYS = new Set<keyof ActorRunOptions>([
2930
'build',
@@ -75,8 +76,9 @@ const customGetFunc = (obj: any, path: string, defaultVal?: number) => {
7576

7677
/**
7778
* Rounds a number to the closest power of 2.
79+
* The result is clamped to the allowed range (ACTOR_LIMITS.MIN_RUN_MEMORY_MBYTES - ACTOR_LIMITS.MAX_RUN_MEMORY_MBYTES).
7880
* @param num The number to round.
79-
* @returns The closest power of 2.
81+
* @returns The closest power of 2 within min/max range.
8082
*/
8183
const roundToClosestPowerOf2 = (num: number): number | undefined => {
8284
// Handle 0 or negative values. The smallest power of 2 is 2^7 = 128.
@@ -91,13 +93,14 @@ const roundToClosestPowerOf2 = (num: number): number | undefined => {
9193
const log2n = Math.log2(num);
9294

9395
const roundedLog = Math.round(log2n);
96+
const result = 2 ** roundedLog;
9497

95-
return 2 ** roundedLog;
98+
return Math.max(ACTOR_LIMITS.MIN_RUN_MEMORY_MBYTES, Math.min(result, ACTOR_LIMITS.MAX_RUN_MEMORY_MBYTES));
9699
};
97100

98101
/**
99102
* Replaces `{{variable}}` placeholders in an expression string with the variable name.
100-
* Enforces strict validation to only allow `input.*` paths or whitelisted `runOptions.*` keys.
103+
* Enforces strict validation to allow `{{input.*}}` paths or whitelisted `{{runOptions.*}}` keys.
101104
*
102105
* @example
103106
* // Returns "runOptions.memoryMbytes + 1024"
@@ -119,12 +122,13 @@ const preprocessDefaultMemoryExpression = (defaultMemoryMbytes: string): string
119122
);
120123
}
121124

122-
// 2. Check if the variable is accessing Input (e.g. {{input.someValue}})
125+
// 2. Check if the variable is accessing input (e.g. {{input.someValue}})
123126
// We do not validate the specific property name because input is dynamic.
124127
if (variableName.startsWith('input.')) {
125128
return variableName;
126129
}
127130

131+
// 3. Check if the variable is accessing runOptions (e.g. {{runOptions.memoryMbytes}}) and validate the keys.
128132
if (variableName.startsWith('runOptions.')) {
129133
const key = variableName.slice('runOptions.'.length);
130134
if (!ALLOWED_RUN_OPTION_KEYS.has(key as keyof ActorRunOptions)) {

test/memory_calculator.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ describe('calculateDefaultMemoryFromExpression', () => {
106106
const result = calculateDefaultMemoryFromExpression('512', emptyContext);
107107
expect(result).toBe(512);
108108
});
109+
110+
it('should clamp to the minimum memory limit if the result is too low', () => {
111+
const result = calculateDefaultMemoryFromExpression('64', emptyContext);
112+
expect(result).toBe(128);
113+
});
114+
115+
it('should clamp to the maximum memory limit if the result is too high', () => {
116+
const result = calculateDefaultMemoryFromExpression('100000', emptyContext);
117+
expect(result).toBe(32768);
118+
});
109119
});
110120

111121
describe('Invalid/Error Handling', () => {

0 commit comments

Comments
 (0)