You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR: google-cloud-bigquery 2.69.0 causes a native SIGABRT in Conscrypt's JNI_OnLoad (could not find method serverCertificateRequested) on macOS arm64 / JDK 21, killing the JVM; 2.68.0 with a byte-identical grpc/conscrypt graph is fine. Bisect, crash reports, and thread-dump analysis below.
Correction (supersedes "What hangs" below): this is a crash, not a hang
We initially reported this as an indefinite hang because the observable symptom — from inside our sbt build — is a forked test JVM that goes silent at 0% CPU forever. Capturing thread dumps forced us to actually find the forked JVM's OS process, and it wasn't there: the forked JVM had already terminated, non-gracefully, seconds after printing the fingerprint lines. macOS's crash reporter caught it. See "Thread dump / crash analysis" below for the full evidence — this changes our diagnosis substantially and we believe it now, not the TLS-handshake framing, is the actionable root cause.
Environment
Component
Value
com.google.cloud:google-cloud-bigquery
2.69.0 (hangs) vs 2.68.0 (clean) — adjacent releases, nothing between them on Maven Central
JDK
Temurin 21.0.11 (macOS arm64) — not JDK 17; see correction below
OS
macOS (Darwin 25.5.0), Apple Silicon (arm64)
Build tool
sbt 1.11.5, Scala 2.13.18
io.grpc:* (api/netty-shaded/etc.)
1.82.2 — identical in both the hanging and the passing configuration
org.conscrypt:conscrypt-openjdk-uber
2.6.0 — identical in both the hanging and the passing configuration
com.google.protobuf:protobuf-java
Reproduces with both 4.33.2 and 4.35.1 (dependency override) — not protobuf-version-dependent
Spark on classpath
4.1.3 (see "what we could not reproduce" below — Spark's presence does not appear to matter)
Correction to environment assumptions: earlier internal notes assumed JDK 17. We re-verified: the sbt launcher and the forked test JVM both run on Temurin 21.0.11 (JAVA_HOME resolves to a 21.0.11 install; sbt itself prints Eclipse Adoptium Java 21.0.11 at startup; no Test / javaHome override exists in the build). The failing environment is JDK 21, not 17.
What hangs
A forked JVM launched by sbt testOnly (via Test / fork := true, SubProcess(ForkOptions()...)) to run a single ScalaTest suite. The suite calls into a Starlake Main().run(Array("transform", ...)) code path that ends up submitting a SQL query job to BigQuery via the plain com.google.cloud.bigquery.BigQuery client (BigQueryOptions.getDefaultInstance().getService() → bigQuery.query(QueryJobConfiguration...)). This is the plain Java client's query/job-polling path — not the BigQuery Storage Read/Write API, and not a Spark connector call (Spark is present on the classpath and initializes earlier in the same process, but the BigQuery call itself goes through google-cloud-bigquery directly).
The forked JVM logs two lines and then goes silent forever — 0% CPU, no further output, no exception, no stack trace — right at the point where it should be opening its first network connection to bigquery.googleapis.com:
could not find method serverCertificateRequested
Warning: Unable to read from client, please check on client for further details of the problem.
The sbt parent process then blocks forever in ServerSocket.accept() waiting for the forked test JVM's handshake, which never arrives. We have let it sit for 5+ minutes with zero CPU activity and zero additional output; it never recovers or times out on its own.
These two lines are the JNI method-lookup error emitted by Conscrypt's native layer (common/src/jni/main/include/conscrypt/jniutil.h) when it can't resolve a Java-side callback method (serverCertificateRequested, part of the client-socket TLS handshake callback interface) via GetMethodID at native-library-load time. The lines never appear on a passing run.
Decisive bisect
Methodology: hold every other dependency at a fixed baseline, flip one artifact at a time, run the live suite (SL_REMOTE_TEST=true sbt testOnly *TransformIntegration2Spec*), and classify the result as a ~45–60s pass or a >5-minute 0%-CPU hang.
google-cloud-bigquery version
Result
2.60.0
PASS (43s)
2.65.0
PASS (51s)
2.67.0
PASS (45s)
2.68.0
PASS (47s)
2.69.0
HANGS (reproduces the exact fingerprint above, every time)
2.68.0 and 2.69.0 are adjacent on Maven Central — no intermediate release exists — so this bisect is as precise as it can be: the regression appeared in exactly the 2.69.0 release.
We initially suspected a transitive-dependency clash, since 2.69.0's own POM bumps grpc-netty-shaded 1.81.0→1.82.2 and conscrypt-openjdk-uber 2.5.2→2.6.0 in place. That hypothesis does not survive a second data point: in our final configuration, google-cloud-datacatalog 1.101.0 independently pulls the exact same grpc-netty-shaded 1.82.2 / conscrypt-openjdk-uber 2.6.0 onto the classpath alongside google-cloud-bigquery2.68.0 (not 2.69.0) — same resolved grpc/conscrypt versions as the hanging configuration — and that combination passes cleanly (48s, verified via sbt evicted showing a single consistent resolution, no version conflicts). Since the grpc/conscrypt versions are byte-identical between one hanging and one passing configuration, the regression is not a transitive-version clash; it appears to be something inside the google-cloud-bigquery 2.69.0 artifact itself (client-side behavior — e.g. a changed connection-setup path, transport configuration, or TLS-provider selection). We were not able to pin down the exact code change: google-cloud-bigquery versions above 2.60.0 are no longer tagged in the googleapis/java-bigquery repository (it was archived and folded into googleapis/google-cloud-java around 2026-03-23), and we could not locate a source tag or changelog entry for 2.69.0 specifically to diff against 2.68.0 — only the resolved POM, which is included below.
POM diff excerpt (2.68.0 → 2.69.0, full diff available on request):
What we could NOT reproduce (important, please read before investigating)
We tried to build a minimal standalone repro to hand you something more actionable than "run our whole test suite." None of the three standalone attempts reproduced the hang, each run against the live project with ambient Application Default Credentials, JDK 21.0.11, in under 3 seconds:
A bare java -cp <coursier-resolved classpath for google-cloud-bigquery:2.69.0 only> process doing BigQueryOptions.getDefaultInstance().getService().query(QueryJobConfiguration.newBuilder("SELECT 1 AS one").build()) — completed in 1.5s.
The same program run against the exact classpath used by our failing sbt-forked test (all ~150 jars, with only the google-cloud-bigquery jar swapped from 2.68.0 to 2.69.0 — every transitive dependency, including grpc-netty-shaded 1.82.2 and conscrypt-openjdk-uber 2.6.0, byte-identical to the failing run) — completed in 1.0s.
The same, plus the exact Test / javaOptions JVM flags our build passes to the forked test JVM (--add-opens for java.base/{java.lang,java.lang.invoke,java.lang.reflect,java.io,java.net,java.nio,java.util,java.util.concurrent,java.util.concurrent.atomic,jdk.internal.ref,sun.nio.ch,sun.nio.cs,sun.security.action,sun.util.calendar}=ALL-UNNAMED, -Xmx4g -Xss4m -XX:ReservedCodeCacheSize=128m) — completed in 1.5s.
The same as Make authentication simpler #3, but with a local SparkSession (local[2], Spark 4.1.3) fully initialized before the BigQuery call, to mirror what our real failing test does before it reaches the query — completed in ~3s including Spark startup/shutdown.
So we cannot currently hand you a bare-JVM repro that hangs. The hang appears to require something specific to our real failing process that none of the above capture — possibly sbt's ForkMain launcher/IPC mechanism itself, possibly our own Main().run() initialization sequence (which touches HdfsStorageHandler/a custom ProxyLocalFileSystem, Jinja templating, and config loading before the BigQuery call), or possibly a race/timing condition that these short-lived standalone programs don't hit. We flag this openly: this weakens confidence that the defect is purely inside google-cloud-bigquery 2.69.0, but the bisect result itself (2.68.0 clean / 2.69.0 hangs, all else byte-identical) is solid and repeatable, so we're reporting it as-is rather than sitting on it.
Steps to reproduce (as best we can generalize)
We don't have a reduction that reproduces outside our own project yet (see above). What we can say generically:
Use google-cloud-bigquery 2.69.0 on JDK 21 (Temurin, macOS arm64 confirmed; other platforms untested) with grpc-netty-shaded/conscrypt-openjdk-uber resolved to 1.82.2/2.6.0.
From a forked JVM launched by sbt (Test / fork := true, SubProcess(ForkOptions(...))) — not a plain java invocation — run a JVM process that: initializes a local Spark 4.1.3 session and Hadoop's local filesystem shims, then calls BigQueryOptions.getDefaultInstance().getService() and submits a QueryJobConfiguration via .query(...), using ambient Application Default Credentials.
On 2.68.0 this completes in under a minute. On 2.69.0, the process prints the two fingerprint lines above and then hangs indefinitely (0% CPU) before completing the TLS handshake to bigquery.googleapis.com.
What we checked and ruled out
Not a protobuf-java version issue: reproduces identically with protobuf-java 4.33.2 and 4.35.1.
Not a grpc/conscrypt transitive-version clash: the passing 2.68.0 configuration resolves the identical grpc-netty-shaded 1.82.2 / conscrypt-openjdk-uber 2.6.0 as the hanging 2.69.0 configuration (confirmed via sbt evicted).
Not JDK 17 vs 21 as originally suspected in our internal notes — we corrected this; the actual environment is JDK 21 throughout, so this is not a candidate explanation for a 17-vs-21 discrepancy (there isn't one).
Thread dump / crash analysis
We reproduced the failure again on 2026-08-20 specifically to capture a thread dump of the "hung" forked JVM. We could not get one — because there was no such process to dump. Here is what we found instead, which we believe is strictly better evidence.
Setup.SL_REMOTE_TEST=true sbt -batch "testOnly *TransformIntegration2Spec*" with google-cloud-bigquery bumped to 2.69.0 in the working tree. The forked test JVM logged the same two fingerprint lines as always:
could not find method serverCertificateRequested
Warning: Unable to read from client, please check on client for further details of the problem.
Within the same second, we went looking for the forked JVM's OS process to jstack it (searching by parent PID of the sbt launcher, by ForkMain/ScalaTest process name, and by full ps listing) and found no such process anywhere on the system — not hung, not a zombie, gone. The sbt parent process (PID 96392) itself sat at 0% CPU indefinitely, exactly as previously reported.
The forked JVM didn't hang — it crashed. macOS's crash reporter had written a .ips diagnostic report for a java process at the exact same timestamp as the fingerprint log lines, with "parentPid": 96392 (our sbt process) confirming it's the forked test JVM (PID 99102 in this run):
This directly ties the two fingerprint log lines to the crash: Conscrypt's native jniutil::init (called once, the first time the native library is loaded, from JNI_OnLoad) resolves a table of Java-side callback method IDs via GetMethodID — including serverCertificateRequested, part of the client-socket TLS handshake callback interface. When it can't resolve one, it logs could not find method <name> and then falls into a cold/error path (.cold.6) that calls native abort() — a deliberate SIGABRT, not a segfault from memory corruption. The Warning: Unable to read from client... line is emitted by the same code path (it's Conscrypt's generic native-layer error message, printed via its JNI error-reporting helper before aborting). The crash happens on a background thread ("pool-1-thread-1", a java.util.concurrent pool worker — consistent with a gRPC/Conscrypt executor thread lazily loading the native library on first TLS use), but abort() terminates the whole process, not just the thread.
Reproducible every time, not a one-off. We found three more .ips crash reports from earlier attempts earlier the same day (20:30, 20:48, 21:01, all before our final 22:00 run), each with a different crashing PID but the identical faulting-thread name, stack, signal, and termination reason (conscrypt::jniutil::init(JavaVM_*, JNIEnv_*) (.cold.6) → abort → SIGABRT). This is a deterministic crash, not a race.
Why our build reports it as a "hang."jstack on the sbt parent process (PID 96392, still alive and burning 0% CPU, exactly as expected since it has nothing to do but wait) shows the thread responsible for collecting test results blocked exactly where you'd expect if its counterpart never showed up:
"ScalaTestRunner$Skeleton$1" ... java.lang.Thread.State: RUNNABLE
at java.net.ServerSocket.accept(java.base/ServerSocket.java:543)
at org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1$React.tryReact(Framework.scala:906)
at org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1.run(Framework.scala:831)
This is ScalaTest's sbt-integration Framework, which opens a ServerSocket and blocks in accept() for the forked JVM to dial back and report results. Since the forked JVM aborted before ever making that connection, accept() blocks forever — there is no timeout anywhere in this path. So "the test suite hangs forever" is real and reproducible, but it is a downstream consequence of an upstream JVM crash, not a network/TLS-handshake stall as we originally described it. (We did not find a deadlock, a stuck Future, or a CountDownLatch anywhere — there was simply no live thread left on the "other side" to make progress.)
Revised understanding of "what we could not reproduce." Our three standalone minimal repros (see below) never triggered this, which now makes sense: they were all short-lived, single-shot processes that likely never happened to have Conscrypt's native library lazily loaded from a background thread pool worker under whatever load-order/timing condition triggers the method-lookup failure — or they simply exited before hitting the code path that loads Conscrypt's JNI library at all (e.g., if TLS/gRPC transport setup is lazy and only triggered by our particular call sequence, which touches Spark/Hadoop initialization, Jinja templating, and our own config loading before the BigQuery call). We were not able to reduce it further, but we no longer think that weakens the report: we now have four independent, byte-for-byte-identical native crash signatures directly implicating google-cloud-bigquery 2.69.0's resolved Conscrypt native library, which is categorically stronger evidence than log lines alone.
Open questions for the google-cloud-bigquery team
Did 2.69.0 change anything about gRPC channel/TLS provider selection (e.g. explicit Conscrypt provider registration, ALPN negotiation path, connection pooling/keepalive defaults) relative to 2.68.0? We could not find a changelog or source diff for 2.69.0 to check this ourselves (see the archived-repo note above).
Is there a version skew between the Conscrypt Java-side classes that end up on the classpath under 2.69.0's dependency resolution (interfaces/callback method signatures) and the nativeconscrypt-openjdk-uber:2.6.0 binary's compiled expectations (the method IDs jniutil::init tries to resolve via GetMethodID)? Our bisect showed conscrypt-openjdk-uber itself resolves to the identical 2.6.0 in both the passing and failing configurations, so if this is the mechanism, the skew would have to be on the Java-class side — e.g. a relocated/shaded copy of a Conscrypt-adjacent interface bundled inside google-cloud-bigquery 2.69.0 or one of its other bumped transitives (google-auth-library-* 1.49.0→1.50.0, protobuf-java-util 4.33.2→4.33.6, google-cloud-bigquerystorage gapic 3.30.0→3.31.0) that shadows or conflicts with the real Conscrypt Java API at classload time.
Should Conscrypt's native jniutil::init treat a GetMethodID miss for serverCertificateRequested as fatal (abort()/SIGABRT) rather than a recoverable initialization failure? A hard process abort on a background thread, triggered by ordinary classpath composition rather than corrupt/malicious input, seems more severe than the failure mode warrants — but this may be intentional/by-design in Conscrypt and out of google-cloud-bigquery's hands; we're not sure who owns this.
Separately, and lower priority: sbt/ScalaTest's fork-test reporting channel (org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1$React.tryReact, blocked in ServerSocket.accept()) has no timeout for a forked JVM that dies before connecting back. That's arguably a robustness gap worth reporting to scalatest/sbt independently of this issue, since any forked-JVM crash (not just this one) would produce the same indefinite hang on the harness side.
Raw evidence
Full .ips crash reports (4 independent repros, byte-identical faulting-thread stacks) and the sbt-parent jstack dump captured during this investigation are available on request; the crash reports live under ~/Library/Logs/DiagnosticReports/java-2026-08-20-*.ips on the machine where this was reproduced (macOS 26.5.2, Apple Silicon, Temurin 21.0.11).
We're happy to run more diagnostics (thread dump of the hung process, -Djavax.net.debug=ssl:handshake, strace/dtruss on the forked JVM) if that would help narrow this down — we didn't want to over-invest before checking whether this was already known.
TL;DR:
google-cloud-bigquery2.69.0 causes a native SIGABRT in Conscrypt'sJNI_OnLoad(could not find method serverCertificateRequested) on macOS arm64 / JDK 21, killing the JVM; 2.68.0 with a byte-identical grpc/conscrypt graph is fine. Bisect, crash reports, and thread-dump analysis below.Correction (supersedes "What hangs" below): this is a crash, not a hang
We initially reported this as an indefinite hang because the observable symptom — from inside our sbt build — is a forked test JVM that goes silent at 0% CPU forever. Capturing thread dumps forced us to actually find the forked JVM's OS process, and it wasn't there: the forked JVM had already terminated, non-gracefully, seconds after printing the fingerprint lines. macOS's crash reporter caught it. See "Thread dump / crash analysis" below for the full evidence — this changes our diagnosis substantially and we believe it now, not the TLS-handshake framing, is the actionable root cause.
Environment
com.google.cloud:google-cloud-bigqueryio.grpc:*(api/netty-shaded/etc.)org.conscrypt:conscrypt-openjdk-ubercom.google.protobuf:protobuf-javaCorrection to environment assumptions: earlier internal notes assumed JDK 17. We re-verified: the sbt launcher and the forked test JVM both run on Temurin 21.0.11 (
JAVA_HOMEresolves to a 21.0.11 install; sbt itself printsEclipse Adoptium Java 21.0.11at startup; noTest / javaHomeoverride exists in the build). The failing environment is JDK 21, not 17.What hangs
A forked JVM launched by
sbt testOnly(viaTest / fork := true,SubProcess(ForkOptions()...)) to run a single ScalaTest suite. The suite calls into a StarlakeMain().run(Array("transform", ...))code path that ends up submitting a SQL query job to BigQuery via the plaincom.google.cloud.bigquery.BigQueryclient (BigQueryOptions.getDefaultInstance().getService()→bigQuery.query(QueryJobConfiguration...)). This is the plain Java client's query/job-polling path — not the BigQuery Storage Read/Write API, and not a Spark connector call (Spark is present on the classpath and initializes earlier in the same process, but the BigQuery call itself goes throughgoogle-cloud-bigquerydirectly).The forked JVM logs two lines and then goes silent forever — 0% CPU, no further output, no exception, no stack trace — right at the point where it should be opening its first network connection to
bigquery.googleapis.com:The sbt parent process then blocks forever in
ServerSocket.accept()waiting for the forked test JVM's handshake, which never arrives. We have let it sit for 5+ minutes with zero CPU activity and zero additional output; it never recovers or times out on its own.These two lines are the JNI method-lookup error emitted by Conscrypt's native layer (
common/src/jni/main/include/conscrypt/jniutil.h) when it can't resolve a Java-side callback method (serverCertificateRequested, part of the client-socket TLS handshake callback interface) viaGetMethodIDat native-library-load time. The lines never appear on a passing run.Decisive bisect
Methodology: hold every other dependency at a fixed baseline, flip one artifact at a time, run the live suite (
SL_REMOTE_TEST=true sbt testOnly *TransformIntegration2Spec*), and classify the result as a ~45–60s pass or a >5-minute 0%-CPU hang.google-cloud-bigqueryversion2.68.0 and 2.69.0 are adjacent on Maven Central — no intermediate release exists — so this bisect is as precise as it can be: the regression appeared in exactly the 2.69.0 release.
We initially suspected a transitive-dependency clash, since 2.69.0's own POM bumps
grpc-netty-shaded1.81.0→1.82.2 andconscrypt-openjdk-uber2.5.2→2.6.0 in place. That hypothesis does not survive a second data point: in our final configuration,google-cloud-datacatalog1.101.0 independently pulls the exact samegrpc-netty-shaded1.82.2 /conscrypt-openjdk-uber2.6.0 onto the classpath alongsidegoogle-cloud-bigquery2.68.0 (not 2.69.0) — same resolved grpc/conscrypt versions as the hanging configuration — and that combination passes cleanly (48s, verified viasbt evictedshowing a single consistent resolution, no version conflicts). Since the grpc/conscrypt versions are byte-identical between one hanging and one passing configuration, the regression is not a transitive-version clash; it appears to be something inside thegoogle-cloud-bigquery2.69.0 artifact itself (client-side behavior — e.g. a changed connection-setup path, transport configuration, or TLS-provider selection). We were not able to pin down the exact code change:google-cloud-bigqueryversions above 2.60.0 are no longer tagged in thegoogleapis/java-bigqueryrepository (it was archived and folded intogoogleapis/google-cloud-javaaround 2026-03-23), and we could not locate a source tag or changelog entry for 2.69.0 specifically to diff against 2.68.0 — only the resolved POM, which is included below.POM diff excerpt (2.68.0 → 2.69.0, full diff available on request):
What we could NOT reproduce (important, please read before investigating)
We tried to build a minimal standalone repro to hand you something more actionable than "run our whole test suite." None of the three standalone attempts reproduced the hang, each run against the live project with ambient Application Default Credentials, JDK 21.0.11, in under 3 seconds:
java -cp <coursier-resolved classpath for google-cloud-bigquery:2.69.0 only>process doingBigQueryOptions.getDefaultInstance().getService().query(QueryJobConfiguration.newBuilder("SELECT 1 AS one").build())— completed in 1.5s.google-cloud-bigqueryjar swapped from 2.68.0 to 2.69.0 — every transitive dependency, including grpc-netty-shaded 1.82.2 and conscrypt-openjdk-uber 2.6.0, byte-identical to the failing run) — completed in 1.0s.Test / javaOptionsJVM flags our build passes to the forked test JVM (--add-opensforjava.base/{java.lang,java.lang.invoke,java.lang.reflect,java.io,java.net,java.nio,java.util,java.util.concurrent,java.util.concurrent.atomic,jdk.internal.ref,sun.nio.ch,sun.nio.cs,sun.security.action,sun.util.calendar}=ALL-UNNAMED,-Xmx4g -Xss4m -XX:ReservedCodeCacheSize=128m) — completed in 1.5s.SparkSession(local[2], Spark 4.1.3) fully initialized before the BigQuery call, to mirror what our real failing test does before it reaches the query — completed in ~3s including Spark startup/shutdown.So we cannot currently hand you a bare-JVM repro that hangs. The hang appears to require something specific to our real failing process that none of the above capture — possibly sbt's
ForkMainlauncher/IPC mechanism itself, possibly our ownMain().run()initialization sequence (which touchesHdfsStorageHandler/a customProxyLocalFileSystem, Jinja templating, and config loading before the BigQuery call), or possibly a race/timing condition that these short-lived standalone programs don't hit. We flag this openly: this weakens confidence that the defect is purely insidegoogle-cloud-bigquery2.69.0, but the bisect result itself (2.68.0 clean / 2.69.0 hangs, all else byte-identical) is solid and repeatable, so we're reporting it as-is rather than sitting on it.Steps to reproduce (as best we can generalize)
We don't have a reduction that reproduces outside our own project yet (see above). What we can say generically:
google-cloud-bigquery2.69.0 on JDK 21 (Temurin, macOS arm64 confirmed; other platforms untested) withgrpc-netty-shaded/conscrypt-openjdk-uberresolved to 1.82.2/2.6.0.Test / fork := true,SubProcess(ForkOptions(...))) — not a plainjavainvocation — run a JVM process that: initializes a local Spark 4.1.3 session and Hadoop's local filesystem shims, then callsBigQueryOptions.getDefaultInstance().getService()and submits aQueryJobConfigurationvia.query(...), using ambient Application Default Credentials.bigquery.googleapis.com.What we checked and ruled out
sbt evicted).Thread dump / crash analysis
We reproduced the failure again on 2026-08-20 specifically to capture a thread dump of the "hung" forked JVM. We could not get one — because there was no such process to dump. Here is what we found instead, which we believe is strictly better evidence.
Setup.
SL_REMOTE_TEST=true sbt -batch "testOnly *TransformIntegration2Spec*"withgoogle-cloud-bigquerybumped to 2.69.0 in the working tree. The forked test JVM logged the same two fingerprint lines as always:Within the same second, we went looking for the forked JVM's OS process to
jstackit (searching by parent PID of the sbt launcher, byForkMain/ScalaTest process name, and by fullpslisting) and found no such process anywhere on the system — not hung, not a zombie, gone. The sbt parent process (PID 96392) itself sat at 0% CPU indefinitely, exactly as previously reported.The forked JVM didn't hang — it crashed. macOS's crash reporter had written a
.ipsdiagnostic report for ajavaprocess at the exact same timestamp as the fingerprint log lines, with"parentPid": 96392(our sbt process) confirming it's the forked test JVM (PID 99102 in this run):The faulting thread's native stack is unambiguous:
This directly ties the two fingerprint log lines to the crash: Conscrypt's native
jniutil::init(called once, the first time the native library is loaded, fromJNI_OnLoad) resolves a table of Java-side callback method IDs viaGetMethodID— includingserverCertificateRequested, part of the client-socket TLS handshake callback interface. When it can't resolve one, it logscould not find method <name>and then falls into a cold/error path (.cold.6) that calls nativeabort()— a deliberateSIGABRT, not a segfault from memory corruption. TheWarning: Unable to read from client...line is emitted by the same code path (it's Conscrypt's generic native-layer error message, printed via its JNI error-reporting helper before aborting). The crash happens on a background thread ("pool-1-thread-1", ajava.util.concurrentpool worker — consistent with a gRPC/Conscrypt executor thread lazily loading the native library on first TLS use), butabort()terminates the whole process, not just the thread.Reproducible every time, not a one-off. We found three more
.ipscrash reports from earlier attempts earlier the same day (20:30, 20:48, 21:01, all before our final 22:00 run), each with a different crashing PID but the identical faulting-thread name, stack, signal, and termination reason (conscrypt::jniutil::init(JavaVM_*, JNIEnv_*) (.cold.6)→abort→SIGABRT). This is a deterministic crash, not a race.Why our build reports it as a "hang."
jstackon the sbt parent process (PID 96392, still alive and burning 0% CPU, exactly as expected since it has nothing to do but wait) shows the thread responsible for collecting test results blocked exactly where you'd expect if its counterpart never showed up:This is ScalaTest's sbt-integration
Framework, which opens aServerSocketand blocks inaccept()for the forked JVM to dial back and report results. Since the forked JVM aborted before ever making that connection,accept()blocks forever — there is no timeout anywhere in this path. So "the test suite hangs forever" is real and reproducible, but it is a downstream consequence of an upstream JVM crash, not a network/TLS-handshake stall as we originally described it. (We did not find a deadlock, a stuckFuture, or aCountDownLatchanywhere — there was simply no live thread left on the "other side" to make progress.)Revised understanding of "what we could not reproduce." Our three standalone minimal repros (see below) never triggered this, which now makes sense: they were all short-lived, single-shot processes that likely never happened to have Conscrypt's native library lazily loaded from a background thread pool worker under whatever load-order/timing condition triggers the method-lookup failure — or they simply exited before hitting the code path that loads Conscrypt's JNI library at all (e.g., if TLS/gRPC transport setup is lazy and only triggered by our particular call sequence, which touches Spark/Hadoop initialization, Jinja templating, and our own config loading before the BigQuery call). We were not able to reduce it further, but we no longer think that weakens the report: we now have four independent, byte-for-byte-identical native crash signatures directly implicating
google-cloud-bigquery2.69.0's resolved Conscrypt native library, which is categorically stronger evidence than log lines alone.Open questions for the google-cloud-bigquery team
conscrypt-openjdk-uber:2.6.0binary's compiled expectations (the method IDsjniutil::inittries to resolve viaGetMethodID)? Our bisect showedconscrypt-openjdk-uberitself resolves to the identical 2.6.0 in both the passing and failing configurations, so if this is the mechanism, the skew would have to be on the Java-class side — e.g. a relocated/shaded copy of a Conscrypt-adjacent interface bundled insidegoogle-cloud-bigquery2.69.0 or one of its other bumped transitives (google-auth-library-*1.49.0→1.50.0,protobuf-java-util4.33.2→4.33.6,google-cloud-bigquerystoragegapic 3.30.0→3.31.0) that shadows or conflicts with the real Conscrypt Java API at classload time.jniutil::inittreat aGetMethodIDmiss forserverCertificateRequestedas fatal (abort()/SIGABRT) rather than a recoverable initialization failure? A hard process abort on a background thread, triggered by ordinary classpath composition rather than corrupt/malicious input, seems more severe than the failure mode warrants — but this may be intentional/by-design in Conscrypt and out of google-cloud-bigquery's hands; we're not sure who owns this.org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1$React.tryReact, blocked inServerSocket.accept()) has no timeout for a forked JVM that dies before connecting back. That's arguably a robustness gap worth reporting to scalatest/sbt independently of this issue, since any forked-JVM crash (not just this one) would produce the same indefinite hang on the harness side.Raw evidence
Full
.ipscrash reports (4 independent repros, byte-identical faulting-thread stacks) and the sbt-parentjstackdump captured during this investigation are available on request; the crash reports live under~/Library/Logs/DiagnosticReports/java-2026-08-20-*.ipson the machine where this was reproduced (macOS 26.5.2, Apple Silicon, Temurin 21.0.11).We're happy to run more diagnostics (thread dump of the hung process,
-Djavax.net.debug=ssl:handshake,strace/dtrusson the forked JVM) if that would help narrow this down — we didn't want to over-invest before checking whether this was already known.