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

cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 933a9caf0114 [SPARK-41246][CORE] Fail fast on RDD id overflow; parse 
negative block ids as safety net
933a9caf0114 is described below

commit 933a9caf0114cb9fa9840ba4f7a97d0c7fa1a080
Author: Giri Prakash reddy <[email protected]>
AuthorDate: Wed Aug 12 08:19:04 2026 +0800

    [SPARK-41246][CORE] Fail fast on RDD id overflow; parse negative block ids 
as safety net
    
    ### What changes were proposed in this pull request?
    
    1. **Fail-fast (primary fix):** `SparkContext.newRddId` throws if the 
32-bit counter would wrap to a negative value after `Int.MaxValue`, so 
long-running apps no longer mint negative RDD ids that break BlockManager / 
other id-keyed state.
    2. **Parse safety net:** `BlockId` RDD regex accepts an optional minus 
(`rdd_(-?[0-9]+)_...`) so names like `rdd_-1330910599_36` from pre-upgrade 
caches or tests still parse instead of throwing `UnrecognizedBlockId`.
    
    Under fail-fast, newly created RDDs never get negative ids; the parser 
change is defense in depth.
    
    ### Why are the changes needed?
    
    After more than `Int.MaxValue` RDDs, `AtomicInteger` wraps to negatives. 
Cached partitions then use names such as `rdd_-1330910599_36`, and 
`BlockId.apply` failed with `UnrecognizedBlockId` (e.g. on `UpdateBlockInfo`). 
Parsing negatives alone would let the app continue with wrapped ids and cover 
up overflow elsewhere. Fail-fast makes overflow an explicit error; the regex 
remains for in-flight / old negative names.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Creating an RDD after `Int.MaxValue` throws a clear `SparkException` 
instead of wrapping. Restart the application to reset the counter. Positive 
block-id parsing is unchanged. Java `int id = rdd.id()` still compiles.
    
    ### How was this patch tested?
    
    - `BlockIdSuite`: negative RDD block name round-trip (safety net)
    - `SparkContextSuite`: fail-fast when counter is at `Int.MaxValue`
    
    ```
    build/sbt "core/testOnly org.apache.spark.storage.BlockIdSuite"
    build/sbt "core/testOnly org.apache.spark.SparkContextSuite -- -z 
SPARK-41246"
    ```
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor Agent
    
    Closes #57893 from giriprakash/SPARK-41246-negative-rdd-block-id.
    
    Authored-by: Giri Prakash reddy <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit 3df71625ce243d4f76099cab3d4fa7b52f13006c)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../main/scala/org/apache/spark/SparkContext.scala | 30 ++++++++++++++++++++--
 .../scala/org/apache/spark/storage/BlockId.scala   |  6 ++++-
 .../scala/org/apache/spark/SparkContextSuite.scala | 13 ++++++++++
 .../org/apache/spark/storage/BlockIdSuite.scala    | 10 ++++++++
 4 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/core/src/main/scala/org/apache/spark/SparkContext.scala 
b/core/src/main/scala/org/apache/spark/SparkContext.scala
index 355549137077..c0203aa032ba 100644
--- a/core/src/main/scala/org/apache/spark/SparkContext.scala
+++ b/core/src/main/scala/org/apache/spark/SparkContext.scala
@@ -2903,8 +2903,34 @@ class SparkContext(config: SparkConf) extends Logging {
 
   private val nextRddId = new AtomicInteger(0)
 
-  /** Register a new RDD, returning its RDD ID */
-  private[spark] def newRddId(): Int = nextRddId.getAndIncrement()
+  /**
+   * Testing helper: set the next value that [[newRddId]] will return.
+   */
+  private[spark] def setNextRddIdForTesting(value: Int): Unit = 
nextRddId.set(value)
+
+  /**
+   * Register a new RDD, returning its RDD ID.
+   *
+   * Fails if the 32-bit counter would wrap to a negative value. Continuing 
with
+   * wrapped ids can break BlockManager, UI, and other id-keyed state.
+   * [[org.apache.spark.storage.BlockId]] still parses negative RDD names as a
+   * safety net for any in-flight or pre-upgrade cached blocks.
+   */
+  private[spark] def newRddId(): Int = {
+    val id = nextRddId.getAndIncrement()
+    if (id < 0) {
+      // Stay pegged so subsequent allocations keep failing clearly.
+      nextRddId.set(Int.MinValue)
+      throw new SparkException(
+        "RDD id counter overflowed Int.MaxValue (" + Int.MaxValue +
+          "). This application has created too many RDDs; restart it.")
+    }
+    if (id == Int.MaxValue) {
+      logWarning("Allocated the last valid RDD id (Int.MaxValue). " +
+        "Further RDD creation will fail.")
+    }
+    id
+  }
 
   /**
    * Registers listeners specified in spark.extraListeners, then starts the 
listener bus.
diff --git a/core/src/main/scala/org/apache/spark/storage/BlockId.scala 
b/core/src/main/scala/org/apache/spark/storage/BlockId.scala
index 3e46a53ee082..8d44e79a223a 100644
--- a/core/src/main/scala/org/apache/spark/storage/BlockId.scala
+++ b/core/src/main/scala/org/apache/spark/storage/BlockId.scala
@@ -263,7 +263,11 @@ case class CacheId(sessionUUID: String, hash: String) 
extends BlockId {
 
 @DeveloperApi
 object BlockId {
-  val RDD = "rdd_([0-9]+)_([0-9]+)".r
+  // Safety net: newRddId() fails fast before minting negative ids, but names 
like
+  // rdd_-1330910599_36 may still appear from blocks cached before upgrade or 
from
+  // tests. Accept an optional minus so BlockId.apply does not throw
+  // UnrecognizedBlockId for those names.
+  val RDD = "rdd_(-?[0-9]+)_([0-9]+)".r
   val SHUFFLE = "shuffle_([0-9]+)_([0-9]+)_([0-9]+)".r
   val SHUFFLE_BATCH = "shuffle_([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)".r
   val SHUFFLE_DATA = "shuffle_([0-9]+)_([0-9]+)_([0-9]+).data".r
diff --git a/core/src/test/scala/org/apache/spark/SparkContextSuite.scala 
b/core/src/test/scala/org/apache/spark/SparkContextSuite.scala
index 709f267de155..ef2d18f51306 100644
--- a/core/src/test/scala/org/apache/spark/SparkContextSuite.scala
+++ b/core/src/test/scala/org/apache/spark/SparkContextSuite.scala
@@ -1521,6 +1521,19 @@ class SparkContextSuite extends SparkFunSuite with 
LocalSparkContext with Eventu
     sc = new SparkContext(conf)
     assert(sc.env.memoryManager.maxOffHeapStorageMemory > 0)
   }
+
+  test("SPARK-41246: fail-fast on RDD id overflow") {
+    val conf = new SparkConf().setAppName("test").setMaster("local[1]")
+    sc = new SparkContext(conf)
+    sc.setNextRddIdForTesting(Int.MaxValue)
+    val last = sc.parallelize(Seq(1), 1)
+    assert(last.id === Int.MaxValue)
+    val err = intercept[SparkException] {
+      sc.parallelize(Seq(2), 1)
+    }
+    assert(err.getMessage.contains("Int.MaxValue"))
+    assert(err.getMessage.contains("overflowed"))
+  }
 }
 
 object SparkContextSuite {
diff --git a/core/src/test/scala/org/apache/spark/storage/BlockIdSuite.scala 
b/core/src/test/scala/org/apache/spark/storage/BlockIdSuite.scala
index 4b7f1fafb211..259b38c7f83d 100644
--- a/core/src/test/scala/org/apache/spark/storage/BlockIdSuite.scala
+++ b/core/src/test/scala/org/apache/spark/storage/BlockIdSuite.scala
@@ -51,6 +51,16 @@ class BlockIdSuite extends SparkFunSuite {
     assertSame(id, BlockId(id.toString))
   }
 
+  test("SPARK-41246: rdd with a negative id") {
+    // Safety net: fail-fast prevents minting negative ids, but BlockId must 
still
+    // parse names from pre-upgrade caches or tests.
+    val id = RDDBlockId(-1330910599, 36)
+    assert(id.name === "rdd_-1330910599_36")
+    assertSame(id, BlockId(id.name))
+    assertDifferent(id, RDDBlockId(1330910599, 36))
+    assertSame(RDDBlockId(Int.MinValue, 0), BlockId("rdd_-2147483648_0"))
+  }
+
   test("shuffle") {
     val id = ShuffleBlockId(1, 2, 3)
     assertSame(id, ShuffleBlockId(1, 2, 3))


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

Reply via email to