This is an automated email from the ASF dual-hosted git repository.
SteNicholas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/celeborn.git
The following commit(s) were added to refs/heads/main by this push:
new ee7529f83 [CELEBORN-2310] Reject RESERVE_SLOTS when disks are full
ee7529f83 is described below
commit ee7529f83a1994c2d8f92a5d12166939b7cce895
Author: Saurabh Dubey <[email protected]>
AuthorDate: Mon May 18 10:53:33 2026 +0800
[CELEBORN-2310] Reject RESERVE_SLOTS when disks are full
### What changes were proposed in this pull request?
Disk full only lead to HARD_SPLITs as a response to writes. However,
doesn't lead to reserve slot rejections. This means too many write retries (due
to HARD_SPLITs on each write attempt) leads to wasted network I/O. We can
reject RESERVE_SLOT during disk full to avoid the wasted data write network IO.
### Why are the changes needed?
Reject reserve slots during disk full, avoid unnecessary network IO.
### Does this PR resolve a correctness bug?
No.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Added UTs, CI.
Closes #3666 from saurabhd336/diskFullReserveSlotsRejection.
Authored-by: Saurabh Dubey <[email protected]>
Signed-off-by: SteNicholas <[email protected]>
---
.../apache/celeborn/common/meta/DeviceInfo.scala | 4 ++
.../deploy/worker/storage/StorageManager.scala | 7 ++-
.../worker/storage/StorageManagerSuite.scala | 57 +++++++++++++++++++++-
3 files changed, 64 insertions(+), 4 deletions(-)
diff --git
a/common/src/main/scala/org/apache/celeborn/common/meta/DeviceInfo.scala
b/common/src/main/scala/org/apache/celeborn/common/meta/DeviceInfo.scala
index f66df6711..baec385c5 100644
--- a/common/src/main/scala/org/apache/celeborn/common/meta/DeviceInfo.scala
+++ b/common/src/main/scala/org/apache/celeborn/common/meta/DeviceInfo.scala
@@ -88,6 +88,10 @@ class DiskInfo(
lazy val shuffleAllocations = new util.HashMap[String, Integer]()
lazy val applicationAllocations = new util.HashMap[String, Integer]()
+ def isHealthy: Boolean = {
+ DiskStatus.HEALTHY.equals(status) && actualUsableSpace > 0
+ }
+
def setStorageType(storageType: StorageInfo.Type) = {
this.storageType = storageType
}
diff --git
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
index cde13f29a..9a2d4a8a7 100644
---
a/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
+++
b/worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManager.scala
@@ -115,7 +115,7 @@ final private[worker] class StorageManager(conf:
CelebornConf, workerSource: Abs
}
def healthyLocalWorkingDirs(): List[File] =
- localDisksSnapshot().filter(_.status == DiskStatus.HEALTHY).flatMap(_.dirs)
+ localDisksSnapshot().filter(_.isHealthy).flatMap(_.dirs)
private val diskOperators: ConcurrentHashMap[String, ThreadPoolExecutor] = {
val cleaners = JavaUtils.newConcurrentHashMap[String, ThreadPoolExecutor]()
@@ -1143,11 +1143,14 @@ final private[worker] class StorageManager(conf:
CelebornConf, workerSource: Abs
while (retryCount < conf.workerCreateWriterMaxAttempts) {
val diskInfo = diskInfos.get(suggestedMountPoint)
val dirs =
- if (diskInfo != null && diskInfo.status.equals(DiskStatus.HEALTHY)) {
+ if (diskInfo != null && diskInfo.isHealthy) {
diskInfo.dirs
} else {
if (suggestedMountPoint.isEmpty) {
logDebug(s"Location suggestedMountPoint is not set, return all
healthy working dirs.")
+ } else if (diskInfo == null) {
+ logInfo(s"Disk info not found for suggestedMountPoint
$suggestedMountPoint, return all healthy " +
+ s"working dirs.")
} else {
logInfo(s"Disk(${diskInfo.mountPoint}) unavailable for
$suggestedMountPoint, return all healthy" +
s" working dirs.")
diff --git
a/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
b/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
index fb1d3e2d2..6107faf98 100644
---
a/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
+++
b/worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/StorageManagerSuite.scala
@@ -17,14 +17,19 @@
package org.apache.celeborn.service.deploy.worker.storage
+import java.{lang, util}
+import java.io.IOException
+
import org.mockito.{Mockito, MockitoSugar}
import org.mockito.ArgumentMatchersSugar.any
import org.mockito.stubbing.Stubber
import org.apache.celeborn.CelebornFunSuite
import org.apache.celeborn.common.CelebornConf
-import org.apache.celeborn.common.CelebornConf.{WORKER_DISK_RESERVE_SIZE,
WORKER_GRACEFUL_SHUTDOWN_ENABLED, WORKER_GRACEFUL_SHUTDOWN_RECOVER_PATH}
-import org.apache.celeborn.common.meta.DiskInfo
+import org.apache.celeborn.common.CelebornConf.{WORKER_DISK_RESERVE_SIZE,
WORKER_GRACEFUL_SHUTDOWN_ENABLED, WORKER_GRACEFUL_SHUTDOWN_RECOVER_PATH,
WORKER_STORAGE_DIRS}
+import org.apache.celeborn.common.identity.UserIdentifier
+import org.apache.celeborn.common.meta.{DiskInfo, DiskStatus}
+import org.apache.celeborn.common.protocol.{PartitionLocation, PartitionType,
StorageInfo}
import org.apache.celeborn.common.util.Utils
import org.apache.celeborn.service.deploy.worker.WorkerSource
@@ -108,4 +113,52 @@ class StorageManagerSuite extends CelebornFunSuite with
MockitoHelper {
spyStorageManager.updateDiskInfos()
assert(diskInfo.actualUsableSpace == 0L)
}
+
+ test("[CELEBORN-2310] Ensure createFile rejected with disks are full, but
status is HEALTHY") {
+ val conf = new CelebornConf().set(WORKER_DISK_RESERVE_SIZE,
Utils.byteStringAsBytes("5g")).set(
+ WORKER_STORAGE_DIRS,
+ Seq("/"))
+ val storageManager = new StorageManager(conf, new WorkerSource(conf))
+ val spyStorageManager = spy(storageManager)
+ val diskInfo = new DiskInfo("/", List.empty, null, conf)
+ diskInfo.setUsableSpace(-1L)
+ // Should fail even if the status is HEALTHY
+ diskInfo.setStatus(DiskStatus.HEALTHY)
+ doReturn(List(diskInfo)).when(spyStorageManager).localDisksSnapshot()
+
+ val partitionLocation = genPartitionLocation(0, Array(0L))
+
+ try {
+ val file = storageManager.createDiskFile(
+ partitionLocation,
+ "myAppId",
+ 0,
+ "myFile",
+ new UserIdentifier("t1", "u1"),
+ PartitionType.REDUCE,
+ partitionSplitEnabled = false)
+ fail("Should throw IOException when disks are full")
+ } catch {
+ case e: IOException =>
+ assert(e.getMessage.equals(
+ s"No available disks! suggested mountPoint
${partitionLocation.getStorageInfo.getMountPoint}"))
+ case e: Throwable =>
+ fail(s"Should throw IOException, but got ${e.getClass.getSimpleName}",
e)
+ }
+ }
+
+ private def genPartitionLocation(epoch: Int, offsets: Array[Long]):
PartitionLocation = {
+ val location: PartitionLocation =
+ new PartitionLocation(0, epoch, "localhost", 0, 0, 0, 0,
PartitionLocation.Mode.PRIMARY)
+ val storageInfo: StorageInfo = new StorageInfo(
+ StorageInfo.Type.HDD,
+ "/",
+ false,
+ "filePath",
+ StorageInfo.ALL_TYPES_AVAILABLE_MASK,
+ offsets(offsets.length - 1),
+ new util.ArrayList[lang.Long]())
+ location.setStorageInfo(storageInfo)
+ location
+ }
}