Skip to content

Commit 1f5da7f

Browse files
committed
1 parent 0020c7e commit 1f5da7f

File tree

2 files changed

+157
-2
lines changed

2 files changed

+157
-2
lines changed

driver-core/src/main/com/mongodb/internal/async/AsyncRunnable.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.mongodb.internal.async;
1818

19+
import com.mongodb.assertions.Assertions;
1920
import com.mongodb.internal.TimeoutContext;
2021
import com.mongodb.internal.async.function.AsyncCallbackLoop;
2122
import com.mongodb.internal.async.function.LoopState;
@@ -206,6 +207,15 @@ default AsyncRunnable thenRunIf(final Supplier<Boolean> condition, final AsyncRu
206207
};
207208
}
208209

210+
/**
211+
* @param condition The condition to check before each iteration
212+
* @param body The body to run on each iteration
213+
* @return the composition of this runnable and the loop, a runnable
214+
*/
215+
default AsyncRunnable loopWhile(final BooleanSupplier condition, final AsyncRunnable body) {
216+
throw Assertions.fail("Not implemented");
217+
}
218+
209219
/**
210220
* @param supplier The supplier to supply using after this runnable
211221
* @return the composition of this runnable and the supplier, a supplier

driver-core/src/test/unit/com/mongodb/internal/async/AsyncFunctionsAbstractTest.java

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@
1818
import com.mongodb.MongoException;
1919
import com.mongodb.internal.TimeoutContext;
2020
import com.mongodb.internal.TimeoutSettings;
21+
import org.junit.jupiter.api.Disabled;
2122
import org.junit.jupiter.api.Test;
2223

24+
import java.util.concurrent.atomic.AtomicInteger;
2325
import java.util.function.BiConsumer;
2426
import java.util.function.Consumer;
2527
import java.util.function.Supplier;
2628

2729
import static com.mongodb.assertions.Assertions.assertNotNull;
2830
import static com.mongodb.internal.async.AsyncRunnable.beginAsync;
31+
import static org.junit.jupiter.api.Assertions.assertTrue;
2932

3033
abstract class AsyncFunctionsAbstractTest extends AsyncFunctionsTestBase {
3134
private static final TimeoutContext TIMEOUT_CONTEXT = new TimeoutContext(new TimeoutSettings(0, 0, 0, 0L, 0));
@@ -724,7 +727,85 @@ void testTryCatchTestAndRethrow() {
724727
}
725728

726729
@Test
730+
@Disabled("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop")
731+
void testWhile() {
732+
assertBehavesSameVariations(10, // TODO check expected variations
733+
() -> {
734+
int i = 0;
735+
while (i < 3 && plainTest(i)) {
736+
i++;
737+
sync(i);
738+
}
739+
},
740+
(callback) -> {
741+
final int[] i = new int[1];
742+
beginAsync().loopWhile(() -> i[0] < 3 && plainTest(i[0]), (c2) -> {
743+
i[0]++;
744+
async(i[0], c2);
745+
}).finish(callback);
746+
});
747+
}
748+
749+
@Test
750+
@Disabled("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop")
751+
void testWhile2() {
752+
assertBehavesSameVariations(14, // TODO check expected variations
753+
() -> {
754+
int i = 0;
755+
while (i < 3 && plainTest(i)) {
756+
i++;
757+
sync(i);
758+
}
759+
sync(i + 100);
760+
},
761+
(callback) -> {
762+
final int[] i = new int[1];
763+
beginAsync().thenRun(c -> {
764+
beginAsync().loopWhile(() -> i[0] < 3 && plainTest(i[0]), (c2) -> {
765+
i[0]++;
766+
async(i[0], c2);
767+
}).finish(c);
768+
}).thenRun(c -> {
769+
async(i[0] + 100, c);
770+
}).finish(callback);
771+
});
772+
}
773+
774+
@Test
775+
@Disabled("Tests AsyncRunnable.loopWhile, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop")
727776
void testRetryLoop() {
777+
assertBehavesSameVariations(InvocationTracker.DEPTH_LIMIT * 2 + 1,
778+
() -> {
779+
while (true) {
780+
try {
781+
sync(plainTest(0) ? 1 : 2);
782+
} catch (RuntimeException e) {
783+
if (e.getMessage().equals("exception-1")) {
784+
continue;
785+
}
786+
throw e;
787+
}
788+
break;
789+
}
790+
},
791+
(callback) -> {
792+
final boolean[] shouldContinue = new boolean[]{true};
793+
beginAsync().loopWhile(() -> shouldContinue[0], (c) -> {
794+
beginAsync().thenRun(c2 -> {
795+
async(plainTest(0) ? 1 : 2, c2);
796+
}).thenRun(c2 -> {
797+
shouldContinue[0] = false;
798+
c2.complete(c2);
799+
}).onErrorIf(e -> e.getMessage().equals("exception-1"), (e, c2) -> {
800+
c2.complete(c2);
801+
}).finish(c);
802+
}).finish(callback);
803+
});
804+
}
805+
806+
@Test
807+
void testThenRunRetryingWhile() {
808+
for (int i = 0; i < 1000; i++) {
728809
assertBehavesSameVariations(InvocationTracker.DEPTH_LIMIT * 2 + 1,
729810
() -> {
730811
while (true) {
@@ -746,10 +827,11 @@ void testRetryLoop() {
746827
e -> e.getMessage().equals("exception-1")
747828
).finish(callback);
748829
});
749-
}
830+
}}
750831

751832
@Test
752833
void testDoWhileLoop() {
834+
for (int i = 0; i < 1000; i++) {
753835
assertBehavesSameVariations(67,
754836
() -> {
755837
do {
@@ -766,7 +848,7 @@ void testDoWhileLoop() {
766848
() -> plainTest(2)
767849
).finish(finalCallback);
768850
});
769-
}
851+
}}
770852

771853
@Test
772854
void testDoWhileLoop2() {
@@ -1009,4 +1091,67 @@ void testDerivation() {
10091091
}).finish(callback);
10101092
});
10111093
}
1094+
1095+
@Test
1096+
@Disabled("Tests AsyncRunnable.thenRun/finish, but we agreed to improve and test AsyncRunnable.thenRunDoWhileLoop")
1097+
void testStackDepthBounded() {
1098+
AtomicInteger maxDepth = new AtomicInteger(0);
1099+
AtomicInteger minDepth = new AtomicInteger(Integer.MAX_VALUE);
1100+
AtomicInteger maxMongoDepth = new AtomicInteger(0);
1101+
AtomicInteger minMongoDepth = new AtomicInteger(Integer.MAX_VALUE);
1102+
AtomicInteger stepCount = new AtomicInteger(0);
1103+
// Capture one sample of mongodb package frames for printing
1104+
String[][] sampleMongoFrames = {null};
1105+
1106+
AsyncRunnable chain = beginAsync();
1107+
for (int i = 0; i < 1000; i++) {
1108+
chain = chain.thenRun(c -> {
1109+
stepCount.incrementAndGet();
1110+
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
1111+
int depth = stack.length;
1112+
maxDepth.updateAndGet(current -> Math.max(current, depth));
1113+
minDepth.updateAndGet(current -> Math.min(current, depth));
1114+
int mongoFrames = 0;
1115+
for (StackTraceElement frame : stack) {
1116+
if (frame.getClassName().startsWith("com.mongodb")) {
1117+
mongoFrames++;
1118+
}
1119+
}
1120+
int mf = mongoFrames;
1121+
maxMongoDepth.updateAndGet(current -> Math.max(current, mf));
1122+
minMongoDepth.updateAndGet(current -> Math.min(current, mf));
1123+
// Capture first sample
1124+
if (sampleMongoFrames[0] == null) {
1125+
String[] frames = new String[mf];
1126+
int idx = 0;
1127+
for (StackTraceElement frame : stack) {
1128+
if (frame.getClassName().startsWith("com.mongodb")) {
1129+
frames[idx++] = frame.getClassName() + "." + frame.getMethodName()
1130+
+ "(" + frame.getFileName() + ":" + frame.getLineNumber() + ")";
1131+
}
1132+
}
1133+
sampleMongoFrames[0] = frames;
1134+
}
1135+
c.complete(c);
1136+
});
1137+
}
1138+
1139+
chain.finish((v, e) -> {
1140+
assertTrue(stepCount.get() == 1000, "Expected 1000 steps, got " + stepCount.get());
1141+
int depth = maxDepth.get();
1142+
int mongoDepth = maxMongoDepth.get();
1143+
String summary = "Stack depth: min=" + minDepth.get() + ", max=" + depth
1144+
+ " | MongoDB frames: min=" + minMongoDepth.get() + ", max=" + mongoDepth;
1145+
System.out.printf(summary + "%n");
1146+
if (sampleMongoFrames[0] != null) {
1147+
System.out.printf("MongoDB stack frames (sample):%n");
1148+
for (int i = 0; i < sampleMongoFrames[0].length; i++) {
1149+
System.out.printf(" " + (i + 1) + ". " + sampleMongoFrames[0][i] + "%n");
1150+
}
1151+
}
1152+
assertTrue(depth < 200,
1153+
"Stack depth too deep (min=" + minDepth.get() + ", max=" + depth
1154+
+ "). Trampoline may not be working correctly.");
1155+
});
1156+
}
10121157
}

0 commit comments

Comments
 (0)