This is an automated email from the ASF dual-hosted git repository.

HyukjinKwon pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 4a0eda595206 [SPARK-58028][INFRA][TEST] Retry Docker image pull in 
DockerJDBCIntegrationSuite to avoid transient 502 aborts
4a0eda595206 is described below

commit 4a0eda59520641ee5cbbf537ebccb390b3326fd8
Author: Hyukjin Kwon <[email protected]>
AuthorDate: Wed Jul 8 13:21:04 2026 +0900

    [SPARK-58028][INFRA][TEST] Retry Docker image pull in 
DockerJDBCIntegrationSuite to avoid transient 502 aborts
    
    ### What changes were proposed in this pull request?
    
    Docker-backed JDBC integration suites (e.g. `PostgresKrbIntegrationSuite`) 
pull their Docker image in `beforeAll` with a single attempt. A transient 
registry/proxy error (observed on GitHub Actions: `HTTP 502 Bad Gateway`) or a 
one-off pull timeout aborts the entire suite even though the rest of the run 
had zero test failures.
    
    This wraps the image pull in a bounded retry loop 
(`spark.test.docker.imagePullAttempts`, default `3`) with linear backoff, so a 
transient failure no longer aborts the suite. The final failure is rethrown 
once all attempts are exhausted.
    
    ### Why are the changes needed?
    
    A single transient registry hiccup should not abort a whole Docker 
integration suite that otherwise passes. This was seen aborting the scheduled 
`build_java17` workflow on `branch-4.1` (`PostgresKrbIntegrationSuite` aborted 
in `beforeAll` on a 502). Targeting `master` so it can be backported to the 
maintenance branches where the flake was observed.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Test-infrastructure change only.
    
    ### How was this patch tested?
    
    Fork CI (`build_java17`, `Run Docker integration tests`): ✅ **PASS** — 
https://github.com/HyukjinKwon/spark/actions/runs/28906195126
    `Run completed ... Tests: succeeded 606, failed 0, canceled 0, ignored 151` 
— "All tests passed", with `PostgresKrbIntegrationSuite` (the suite that had 
aborted on the 502) running clean. (Verification ran on `branch-4.1`; the 
changed pull block is identical on `master`.)
    
    JIRA: https://issues.apache.org/jira/browse/SPARK-58028
    
    Closes #57111 from HyukjinKwon/DO-NOT-MERGE/deflake-docker-master.
    
    Authored-by: Hyukjin Kwon <[email protected]>
    Signed-off-by: Hyukjin Kwon <[email protected]>
    (cherry picked from commit 1f8715707d25cafd43d60065786ae6a4a9780e80)
    Signed-off-by: Hyukjin Kwon <[email protected]>
---
 .../sql/jdbc/DockerJDBCIntegrationSuite.scala      | 62 +++++++++++++++-------
 1 file changed, 44 insertions(+), 18 deletions(-)

diff --git 
a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/DockerJDBCIntegrationSuite.scala
 
b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/DockerJDBCIntegrationSuite.scala
index 48e21f0d6c12..8bb54460869f 100644
--- 
a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/DockerJDBCIntegrationSuite.scala
+++ 
b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/DockerJDBCIntegrationSuite.scala
@@ -109,6 +109,11 @@ abstract class DockerJDBCIntegrationSuite
     sys.props.getOrElse("spark.test.docker.removePulledImage", 
"true").toBoolean
   protected val imagePullTimeout: Long =
     
timeStringAsSeconds(sys.props.getOrElse("spark.test.docker.imagePullTimeout", 
"5min"))
+  // Number of attempts to pull the Docker image before giving up. Image pulls 
occasionally fail
+  // with transient registry/proxy errors (e.g. HTTP 502 Bad Gateway) that 
abort the whole suite
+  // in beforeAll even though a retry would succeed, so retry a few times with 
backoff.
+  protected val imagePullAttempts: Int =
+    sys.props.getOrElse("spark.test.docker.imagePullAttempts", "3").toInt
   protected val startContainerTimeout: Long =
     
timeStringAsSeconds(sys.props.getOrElse("spark.test.docker.startContainerTimeout",
 "5min"))
   protected val connectionTimeout: PatienceConfiguration.Timeout = {
@@ -145,27 +150,48 @@ abstract class DockerJDBCIntegrationSuite
       } catch {
         case e: NotFoundException =>
           log.warn(s"Docker image ${db.imageName} not found; pulling image 
from registry")
-          val callback = new PullImageResultCallback {
-            override def onNext(item: PullResponseItem): Unit = {
-              super.onNext(item)
-              val status = item.getStatus
-              if (status != null && status != "Downloading" && status != 
"Extracting") {
-                logInfo(s"$status ${item.getId}")
+          // Retry the pull a few times: it can fail with transient 
registry/proxy errors
+          // (e.g. HTTP 502 Bad Gateway) or a timeout that would otherwise 
abort the suite.
+          var attempt = 0
+          var lastError: Throwable = null
+          while (!pulled && attempt < imagePullAttempts) {
+            attempt += 1
+            val callback = new PullImageResultCallback {
+              override def onNext(item: PullResponseItem): Unit = {
+                super.onNext(item)
+                val status = item.getStatus
+                if (status != null && status != "Downloading" && status != 
"Extracting") {
+                  logInfo(s"$status ${item.getId}")
+                }
               }
             }
+            try {
+              val (success, time) = Utils.timeTakenMs(
+                docker.pullImageCmd(db.imageName)
+                  .exec(callback)
+                  .awaitCompletion(imagePullTimeout, TimeUnit.SECONDS))
+
+              if (success) {
+                pulled = true
+                logInfo(s"Successfully pulled image ${db.imageName} in $time 
ms")
+              } else {
+                lastError = new TimeoutException(
+                  s"Timeout('$imagePullTimeout secs') waiting for image 
${db.imageName} " +
+                    "to be pulled")
+              }
+            } catch {
+              case NonFatal(e) =>
+                lastError = e
+            }
+            if (!pulled && attempt < imagePullAttempts) {
+              log.warn(s"Failed to pull image ${db.imageName} " +
+                s"(attempt $attempt/$imagePullAttempts); retrying", lastError)
+              // Linear backoff between attempts to let a transient registry 
issue recover.
+              Thread.sleep(TimeUnit.SECONDS.toMillis(5L * attempt))
+            }
           }
-
-          val (success, time) = Utils.timeTakenMs(
-            docker.pullImageCmd(db.imageName)
-              .exec(callback)
-              .awaitCompletion(imagePullTimeout, TimeUnit.SECONDS))
-
-          if (success) {
-            pulled = success
-            logInfo(s"Successfully pulled image ${db.imageName} in $time ms")
-          } else {
-            throw new TimeoutException(
-              s"Timeout('$imagePullTimeout secs') waiting for image 
${db.imageName} to be pulled")
+          if (!pulled) {
+            throw lastError
           }
       }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to