Skip to content

Commit 9a68670

Browse files
committed
Included automated poc code and FINDINGS.md
1 parent 4274887 commit 9a68670

17 files changed

Lines changed: 2769 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ local.properties
5252
*.sh
5353
!.evergreen/*.sh
5454
!scripts/*.sh
55+
!testing/java-6279-poc/*.sh
5556

5657
# security-sensitive files
5758
*.gpg

testing/java-6279-poc/FINDINGS.md

Lines changed: 668 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Gradle init script used by run.sh to discover module runtime classpaths without touching any
2+
// build file. Applied with -I; adds no behaviour to a normal build.
3+
allprojects {
4+
tasks.register("printRuntimeCp") {
5+
doLast {
6+
def cp = project.configurations.findByName("runtimeClasspath")
7+
if (cp != null) {
8+
println "CP:" + cp.files.join(File.pathSeparator)
9+
}
10+
}
11+
}
12+
}

testing/java-6279-poc/run.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env bash
2+
# Runs the JAVA-6279 proofs of concept.
3+
#
4+
# Usage: ./testing/java-6279-poc/run.sh [primer|driver|executor|all]
5+
#
6+
# primer synthetic classes in a throwaway child class loader -- reproduces the finding that a live thread
7+
# started from a class's static initializer pins that class loader. No driver code.
8+
# driver the driver loaded into a child class loader -- shows that BufferPoolPruner keeps that loader, and
9+
# therefore every driver class and all its static state, strongly reachable after the client is closed.
10+
# executor no driver code and no class loaders: checks that a self-terminating, self-resurrecting pruner is
11+
# implementable on ScheduledThreadPoolExecutor without ever shutting it down. Run under every JDK found
12+
# on the machine, because it leans on unspecified implementation behaviour and the driver's baseline is
13+
# Java 8. Set JAVA_HOMES to a colon separated list to override JDK discovery.
14+
#
15+
# No MongoDB server is needed: the driver scenarios never issue an operation.
16+
# Nothing under any module's src/main is modified.
17+
set -euo pipefail
18+
19+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20+
POC_DIR="$REPO_ROOT/testing/java-6279-poc"
21+
WHAT="${1:-all}"
22+
cd "$REPO_ROOT"
23+
24+
OUT="$(mktemp -d)"
25+
trap 'rm -rf "$OUT"' EXIT
26+
STATUS=0
27+
28+
# ---------------------------------------------------------------------------------------------------------------------
29+
# executor mechanism: standalone, Java 8 clean, no driver jars needed
30+
# ---------------------------------------------------------------------------------------------------------------------
31+
if [ "$WHAT" = "executor" ] || [ "$WHAT" = "all" ]; then
32+
echo "== compiling the executor mechanism checks at --release 8 =="
33+
mkdir -p "$OUT/mechanism"
34+
javac -nowarn --release 8 -d "$OUT/mechanism" "$POC_DIR/src/java6279/ExecutorMechanism.java"
35+
36+
# Discover JDKs. JAVA_HOMES wins; otherwise ask java_home on macOS; always include whatever java is on the PATH.
37+
JDKS=""
38+
if [ -n "${JAVA_HOMES:-}" ]; then
39+
JDKS="$(echo "$JAVA_HOMES" | tr ':' '\n')"
40+
elif [ -x /usr/libexec/java_home ]; then
41+
JDKS="$(/usr/libexec/java_home -V 2>&1 | sed -n 's|.* \(/.*/Contents/Home\)$|\1|p' || true)"
42+
fi
43+
JDKS="$(printf '%s\n%s\n' "$JDKS" "$(dirname "$(dirname "$(command -v java)")")" | sed '/^$/d' | sort -u)"
44+
45+
for JDK in $JDKS; do
46+
[ -x "$JDK/bin/java" ] || continue
47+
echo
48+
echo "############ $("$JDK/bin/java" -version 2>&1 | head -1) ############"
49+
"$JDK/bin/java" ${POC_JAVA_ARGS:-} -cp "$OUT/mechanism" java6279.ExecutorMechanism || STATUS=1
50+
done
51+
fi
52+
53+
# ---------------------------------------------------------------------------------------------------------------------
54+
# class loader retention: needs the driver jars
55+
# ---------------------------------------------------------------------------------------------------------------------
56+
if [ "$WHAT" = "primer" ] || [ "$WHAT" = "driver" ] || [ "$WHAT" = "all" ]; then
57+
need_jar() {
58+
local pattern="$1"
59+
local found
60+
found="$(ls $pattern 2>/dev/null | grep -v -- '-sources' | grep -v -- '-javadoc' | head -1 || true)"
61+
if [ -z "$found" ]; then
62+
echo "missing jar: $pattern" >&2
63+
exit 1
64+
fi
65+
echo "$found"
66+
}
67+
68+
echo
69+
if [ -n "${JAVA6279_DRIVER_REPO:-}" ]; then
70+
# Point the harness at another checkout -- e.g. the backpressure branch in a git worktree -- to see what that
71+
# tree's threads do. The scenarios are branch agnostic; only the jars under test change.
72+
echo "== using driver jars from $JAVA6279_DRIVER_REPO =="
73+
DRIVER_REPO="$JAVA6279_DRIVER_REPO"
74+
else
75+
echo "== building driver jars =="
76+
./gradlew -q -PskipCryptVerify=true :bson:jar :driver-core:jar :driver-sync:jar
77+
DRIVER_REPO="$REPO_ROOT"
78+
fi
79+
80+
# The classpath handed to the *child* loader. It must be self-contained down to the platform class loader, so it
81+
# carries the third party jars as well, taken from driver-sync's own runtime classpath so versions match the build.
82+
DRIVER_CP="$(need_jar "$DRIVER_REPO/bson/build/libs/bson-*.jar")"
83+
DRIVER_CP="$DRIVER_CP:$(need_jar "$DRIVER_REPO/driver-core/build/libs/mongodb-driver-core-*.jar")"
84+
DRIVER_CP="$DRIVER_CP:$(need_jar "$DRIVER_REPO/driver-sync/build/libs/mongodb-driver-sync-*.jar")"
85+
86+
DEPS="$(./gradlew -q -I "$POC_DIR/classpath.gradle" :driver-sync:printRuntimeCp \
87+
| sed -n 's/^CP://p' | tr ':' '\n' | sort -u | grep -v -- '-SNAPSHOT.jar$' | paste -sd: -)"
88+
if [ -z "$DEPS" ]; then
89+
echo "could not resolve third party jars from Gradle" >&2
90+
exit 1
91+
fi
92+
DRIVER_CP="$DRIVER_CP:$DEPS"
93+
94+
echo "== compiling the class loader proof of concept =="
95+
find "$POC_DIR/src" -name '*.java' > "$OUT/sources.txt"
96+
javac -nowarn -d "$OUT/classes" @"$OUT/sources.txt"
97+
98+
# java6279.Poc goes on the application classpath; java6279.primer.* is also there, but the primer class loader
99+
# defines it from $OUT/classes itself rather than delegating, which is what gives each scenario a fresh loader.
100+
echo "== running =="
101+
SCENARIOS="$WHAT"
102+
[ "$WHAT" = "all" ] && SCENARIOS="all"
103+
java -cp "$OUT/classes" \
104+
-Djava6279.classesDir="$OUT/classes" \
105+
-Djava6279.driverCp="$DRIVER_CP" \
106+
${POC_JAVA_ARGS:-} \
107+
java6279.Poc "$SCENARIOS" || STATUS=1
108+
fi
109+
110+
exit $STATUS

0 commit comments

Comments
 (0)