dongjoon-hyun commented on code in PR #59331:
URL: https://github.com/apache/spark/pull/59331#discussion_r4252554517


##########
core/src/main/java/org/apache/spark/memory/TaskMemoryManager.java:
##########
@@ -113,10 +113,12 @@ public class TaskMemoryManager {
   final MemoryMode tungstenMemoryMode;
 
   /**
-   * Tracks spillable memory consumers.
+   * Tracks spillable memory consumers. Consumers are tracked by identity 
rather than by
+   * {@code equals}, so that distinct consumers which happen to be equal (for 
example, Scala case

Review Comment:
   This example is stale after the last commit. `MemoryConsumer` now defines 
identity `equals`/`hashCode`, so a Scala case class consumer is no longer equal 
to another instance. The identity set is now the safety net for subclasses that 
explicitly override `equals`, as the PR description says for (2) and (3). Could 
we say that here? For example:
   
   ```java
     /**
      * Tracks spillable memory consumers. Consumers are tracked by identity 
rather than by
      * {@code equals}, so that distinct consumers are all offered for spilling 
and reported in the
      * memory usage breakdown even if a subclass overrides {@code equals}.
      */
   ```



##########
core/src/main/java/org/apache/spark/memory/MemoryConsumer.java:
##########
@@ -28,6 +28,13 @@
  * A memory consumer of {@link TaskMemoryManager} that supports spilling.
  *
  * Note: this only supports allocation / spilling of Tungsten memory.
+ *
+ * Memory consumers have identity equality: {@link #equals} and {@link 
#hashCode} are defined here
+ * by reference, so a subclass, including a Scala case class, does not get 
value equality unless it

Review Comment:
   nit. This holds for subclasses compiled against this version. scalac decides 
at compile time whether a case class gets synthetic `equals`/`hashCode`, so a 
case-class consumer that was compiled against an earlier Spark, for example in 
a plugin jar, keeps its value equality on this version until it is rebuilt. 
`TaskMemoryManager` still offers each such instance for spilling thanks to the 
identity set, but the consumer's own `trigger == this` check still matches an 
equal but distinct trigger.
   
   Could we say "when compiled against this version" here, and in the PR 
description's "now has identity equality instead"? That would also record why 
the identity set and `ne` in `Spillable` are still needed next to this override.



##########
core/src/main/java/org/apache/spark/memory/MemoryConsumer.java:
##########
@@ -156,6 +166,21 @@ public void freeMemory(long size) {
     used.getAndAdd(-size);
   }
 
+  /**
+   * Memory consumers are compared by identity. This is not final so that 
existing subclasses which
+   * override it keep compiling, but its presence stops Scala case classes 
from generating value
+   * equality.
+   */
+  @Override
+  public boolean equals(Object other) {

Review Comment:
   A note on the merge order with #59282, which is still open. It adds this to 
`HybridQueue`:
   
   ```scala
     // A memory consumer must use identity equality. TaskMemoryManager tracks 
consumers in a
     // HashSet, so two queues that compare equal would be tracked (and offered 
for spilling) as one.
     // These are final so that no subclass, including a case class, can switch 
to value equality.
     final override def equals(other: Any): Boolean = ...
     final override def hashCode(): Int = System.identityHashCode(this)
   ```
   
   Keeping `equals`/`hashCode` non-final here is what lets that code compile on 
top of this PR, which is good. But once both PRs are on master, the `HashSet` 
sentence is wrong and the two overrides repeat this base class. Could whichever 
PR merges second reconcile that comment on master, for example by pointing it 
at the `MemoryConsumer` contract? On the maintenance branches, which get only 
#59282, the comment and the overrides stay accurate.



##########
core/src/main/java/org/apache/spark/memory/MemoryConsumer.java:
##########
@@ -28,6 +28,13 @@
  * A memory consumer of {@link TaskMemoryManager} that supports spilling.
  *
  * Note: this only supports allocation / spilling of Tungsten memory.
+ *
+ * Memory consumers have identity equality: {@link #equals} and {@link 
#hashCode} are defined here
+ * by reference, so a subclass, including a Scala case class, does not get 
value equality unless it
+ * explicitly overrides them. {@link TaskMemoryManager} also tracks consumers 
by identity, so every
+ * distinct instance is tracked, offered for spilling and reported in the 
memory usage breakdown,

Review Comment:
   nit. "every distinct instance is tracked, offered for spilling and reported 
in the memory usage breakdown" says a bit more than `TaskMemoryManager` does:
   - A consumer is added to the set only when it calls `acquireExecutionMemory` 
(`TaskMemoryManager.java` L256).
   - It is offered for spilling only while `getUsed() > 0`, and only to a 
requester with the same memory mode (L232, L355).
   - The error breakdown lists at most `oomErrorConsumerBreakdownLimit` 
consumers and folds the rest into "(N more consumers)" (L544-L556).
   
   Could we limit the sentence to what identity guarantees? For example: 
"distinct instances are never merged into one entry, so each of them can be 
offered for spilling and reported in the memory usage breakdown".



##########
core/src/test/scala/org/apache/spark/memory/MemoryConsumerSuite.scala:
##########
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.memory
+
+import org.apache.spark.{SparkConf, SparkFunSuite}
+
+class MemoryConsumerSuite extends SparkFunSuite {
+
+  test("case class consumers have identity equality") {
+    val taskMemoryManager = new TaskMemoryManager(new TestMemoryManager(new 
SparkConf()), 0)
+    val consumer = CaseClassConsumer(taskMemoryManager, 1)
+    val other = CaseClassConsumer(taskMemoryManager, 1)
+
+    // The case class does not generate value-based equals/hashCode because 
MemoryConsumer
+    // already defines them.
+    assert(consumer != other)
+    assert(consumer == consumer)
+    assert(consumer.hashCode === System.identityHashCode(consumer))
+    // A spill triggered by an equal-looking consumer is not mistaken for a 
self-triggered one.
+    assert(consumer.spill(Long.MaxValue, other) === 1)

Review Comment:
   This calls `spill` directly on consumers that are not registered with 
`taskMemoryManager`. It checks that `==` is identity for a case class, but not 
the end-to-end case that the description says is now fixed regardless of the 
merge order with #59282: two case-class consumers with equal arguments, both 
registered with `TaskMemoryManager`, where one requests more memory than is 
free and the other actually spills. `SpillableSuite` was changed to drive that 
through `acquireExecutionMemory` for the same reason. Could we do the same here?
   
   Separately, `CaseClassConsumer.spill` (L44-L45) uses `trigger == this` and 
says that is fine because `equals` is identity. The `spill` Javadoc added in 
the same commit tells implementers to compare `trigger` by reference. Since 
this is the only new example of a case-class consumer, maybe the comment could 
add that real consumers should still use `eq`, per that Javadoc.



##########
core/src/main/java/org/apache/spark/memory/MemoryConsumer.java:
##########
@@ -156,6 +166,21 @@ public void freeMemory(long size) {
     used.getAndAdd(-size);
   }
 
+  /**
+   * Memory consumers are compared by identity. This is not final so that 
existing subclasses which

Review Comment:
   nit. The PR title (and the SPARK-60112 title) still say "Track memory 
consumers by identity in TaskMemoryManager". But the description now calls this 
override (1) the root-cause fix, and the `TaskMemoryManager` set the safety 
net. Since the title becomes the commit subject, could we update it to cover 
this part too, e.g. "[SPARK-60112][CORE] Use identity equality for memory 
consumers"?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to