Skip to content

Commit 654e141

Browse files
committed
test(jsonata): refine StackOverflow regression tests — shared 256k stack helper
Both Windows and Linux tests now use SMALL_STACK_BYTES = 256 KB via Thread(stackSize) so no -Xss JVM flag is needed in build config. Extract runOnSmallStack() helper to avoid duplication.
1 parent a55b948 commit 654e141

1 file changed

Lines changed: 31 additions & 35 deletions

File tree

plugin-transform-json/src/test/java/io/kestra/plugin/transform/jsonata/TransformValueTest.java

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -137,41 +137,50 @@ void shouldHandleNestedArrayExpressionFromIssue40() throws Exception {
137137
}
138138

139139
// Regression tests for https://github.com/dashjoin/jsonata-java/pull/107
140-
// Frame.lookup() was recursive — each JSONata recursive call adds one frame, so lookup()
141-
// recurses once per frame. On small stacks this causes StackOverflowError before JSONata's
142-
// own maxDepth guard can fire.
143-
144-
@Test
145-
void shouldNotCrashWithDeepRecursionOnWindowsStack() throws InterruptedException {
146-
// Windows JVM default thread stack ~256 KB; crashes at depth=999 before the fix.
147-
RunContext runContext = runContextFactory.of();
148-
TransformValue task = TransformValue.builder()
149-
.from(Property.ofValue("{}"))
150-
.expression(Property.ofValue(
151-
"($f := function($n) { $n > 0 ? $f($n - 1) : 0 }; $f(999))"
152-
))
153-
.maxDepth(Property.ofValue(1000))
154-
.build();
155-
140+
//
141+
// Frame.lookup() was recursive — each JSONata recursive call adds a scope frame, and lookup()
142+
// recurses once per frame when resolving a variable. With maxDepth=1000 (old default), a
143+
// 999-deep recursive expression causes lookup() to recurse 999 levels on top of JSONata's own
144+
// eval stack, overflowing the thread stack before maxDepth fires.
145+
//
146+
// Windows JVM default thread stack is ~256 KB; Linux is ~512 KB. Both are reproduced here
147+
// via Thread(stackSize) without requiring -Xss JVM flags in build config.
148+
private static final long SMALL_STACK_BYTES = 256 * 1024L;
149+
150+
private void runOnSmallStack(TransformValue task, RunContext runContext) throws InterruptedException {
156151
AtomicReference<Throwable> thrown = new AtomicReference<>();
157152
Thread t = new Thread(null, () -> {
158153
try {
159154
task.run(runContext);
160155
} catch (Throwable e) {
161156
thrown.set(e);
162157
}
163-
}, "windows-stack-sim", 256 * 1024);
158+
}, "small-stack-sim", SMALL_STACK_BYTES);
164159
t.start();
165160
t.join();
166-
167161
assertThat(thrown.get())
168-
.as("StackOverflowError on 256 KB stack (Windows default) — requires iterative Frame.lookup()")
162+
.as("StackOverflowError on %d KB stack — requires iterative Frame.lookup() fix", SMALL_STACK_BYTES / 1024)
169163
.isNull();
170164
}
171165

172166
@Test
173-
void shouldNotCrashWithDeepRecursionOnLinuxStack() throws InterruptedException {
174-
// Linux JVM default thread stack ~512 KB; needs higher depth to overflow than Windows.
167+
void shouldNotCrashWithDeepRecursionOnWindowsStack() throws Exception {
168+
// Simulates Windows JVM default (~256 KB): crashes at depth=999 with recursive lookup().
169+
RunContext runContext = runContextFactory.of();
170+
TransformValue task = TransformValue.builder()
171+
.from(Property.ofValue("{}"))
172+
.expression(Property.ofValue(
173+
"($f := function($n) { $n > 0 ? $f($n - 1) : 0 }; $f(999))"
174+
))
175+
.maxDepth(Property.ofValue(1000))
176+
.build();
177+
178+
runOnSmallStack(task, runContext);
179+
}
180+
181+
@Test
182+
void shouldNotCrashWithDeepRecursionOnLinuxStack() throws Exception {
183+
// Simulates a Linux worker explicitly launched with -Xss256k (e.g. constrained container).
175184
RunContext runContext = runContextFactory.of();
176185
TransformValue task = TransformValue.builder()
177186
.from(Property.ofValue("{}"))
@@ -181,19 +190,6 @@ void shouldNotCrashWithDeepRecursionOnLinuxStack() throws InterruptedException {
181190
.maxDepth(Property.ofValue(2000))
182191
.build();
183192

184-
AtomicReference<Throwable> thrown = new AtomicReference<>();
185-
Thread t = new Thread(null, () -> {
186-
try {
187-
task.run(runContext);
188-
} catch (Throwable e) {
189-
thrown.set(e);
190-
}
191-
}, "linux-stack-sim", 512 * 1024);
192-
t.start();
193-
t.join();
194-
195-
assertThat(thrown.get())
196-
.as("StackOverflowError on 512 KB stack (Linux default) — requires iterative Frame.lookup()")
197-
.isNull();
193+
runOnSmallStack(task, runContext);
198194
}
199195
}

0 commit comments

Comments
 (0)