Skip to content

Commit 5b8f737

Browse files
committed
[SPARK-17547] Ensure temp shuffle data file is cleaned up after error
SPARK-8029 (#9610) modified shuffle writers to first stage their data to a temporary file in the same directory as the final destination file and then to atomically rename this temporary file at the end of the write job. However, this change introduced the potential for the temporary output file to be leaked if an exception occurs during the write because the shuffle writers' existing error cleanup code doesn't handle deletion of the temp file. This patch avoids this potential cause of disk-space leaks by adding `finally` blocks to ensure that temp files are always deleted if they haven't been renamed. Author: Josh Rosen <[email protected]> Closes #15104 from JoshRosen/cleanup-tmp-data-file-in-shuffle-writer.
1 parent 0ad8eeb commit 5b8f737

File tree

4 files changed

+73
-49
lines changed

4 files changed

+73
-49
lines changed

core/src/main/java/org/apache/spark/shuffle/sort/BypassMergeSortShuffleWriter.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,14 @@ public void write(Iterator<Product2<K, V>> records) throws IOException {
160160

161161
File output = shuffleBlockResolver.getDataFile(shuffleId, mapId);
162162
File tmp = Utils.tempFileWith(output);
163-
partitionLengths = writePartitionedFile(tmp);
164-
shuffleBlockResolver.writeIndexFileAndCommit(shuffleId, mapId, partitionLengths, tmp);
163+
try {
164+
partitionLengths = writePartitionedFile(tmp);
165+
shuffleBlockResolver.writeIndexFileAndCommit(shuffleId, mapId, partitionLengths, tmp);
166+
} finally {
167+
if (tmp.exists() && !tmp.delete()) {
168+
logger.error("Error while deleting temp file {}", tmp.getAbsolutePath());
169+
}
170+
}
165171
mapStatus = MapStatus$.MODULE$.apply(blockManager.shuffleServerId(), partitionLengths);
166172
}
167173

core/src/main/java/org/apache/spark/shuffle/sort/UnsafeShuffleWriter.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,21 @@ void closeAndWriteOutput() throws IOException {
210210
final File output = shuffleBlockResolver.getDataFile(shuffleId, mapId);
211211
final File tmp = Utils.tempFileWith(output);
212212
try {
213-
partitionLengths = mergeSpills(spills, tmp);
214-
} finally {
215-
for (SpillInfo spill : spills) {
216-
if (spill.file.exists() && ! spill.file.delete()) {
217-
logger.error("Error while deleting spill file {}", spill.file.getPath());
213+
try {
214+
partitionLengths = mergeSpills(spills, tmp);
215+
} finally {
216+
for (SpillInfo spill : spills) {
217+
if (spill.file.exists() && ! spill.file.delete()) {
218+
logger.error("Error while deleting spill file {}", spill.file.getPath());
219+
}
218220
}
219221
}
222+
shuffleBlockResolver.writeIndexFileAndCommit(shuffleId, mapId, partitionLengths, tmp);
223+
} finally {
224+
if (tmp.exists() && !tmp.delete()) {
225+
logger.error("Error while deleting temp file {}", tmp.getAbsolutePath());
226+
}
220227
}
221-
shuffleBlockResolver.writeIndexFileAndCommit(shuffleId, mapId, partitionLengths, tmp);
222228
mapStatus = MapStatus$.MODULE$.apply(blockManager.shuffleServerId(), partitionLengths);
223229
}
224230

core/src/main/scala/org/apache/spark/shuffle/IndexShuffleBlockResolver.scala

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -139,48 +139,54 @@ private[spark] class IndexShuffleBlockResolver(
139139
dataTmp: File): Unit = {
140140
val indexFile = getIndexFile(shuffleId, mapId)
141141
val indexTmp = Utils.tempFileWith(indexFile)
142-
val out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexTmp)))
143-
Utils.tryWithSafeFinally {
144-
// We take in lengths of each block, need to convert it to offsets.
145-
var offset = 0L
146-
out.writeLong(offset)
147-
for (length <- lengths) {
148-
offset += length
142+
try {
143+
val out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexTmp)))
144+
Utils.tryWithSafeFinally {
145+
// We take in lengths of each block, need to convert it to offsets.
146+
var offset = 0L
149147
out.writeLong(offset)
148+
for (length <- lengths) {
149+
offset += length
150+
out.writeLong(offset)
151+
}
152+
} {
153+
out.close()
150154
}
151-
} {
152-
out.close()
153-
}
154155

155-
val dataFile = getDataFile(shuffleId, mapId)
156-
// There is only one IndexShuffleBlockResolver per executor, this synchronization make sure
157-
// the following check and rename are atomic.
158-
synchronized {
159-
val existingLengths = checkIndexAndDataFile(indexFile, dataFile, lengths.length)
160-
if (existingLengths != null) {
161-
// Another attempt for the same task has already written our map outputs successfully,
162-
// so just use the existing partition lengths and delete our temporary map outputs.
163-
System.arraycopy(existingLengths, 0, lengths, 0, lengths.length)
164-
if (dataTmp != null && dataTmp.exists()) {
165-
dataTmp.delete()
166-
}
167-
indexTmp.delete()
168-
} else {
169-
// This is the first successful attempt in writing the map outputs for this task,
170-
// so override any existing index and data files with the ones we wrote.
171-
if (indexFile.exists()) {
172-
indexFile.delete()
173-
}
174-
if (dataFile.exists()) {
175-
dataFile.delete()
176-
}
177-
if (!indexTmp.renameTo(indexFile)) {
178-
throw new IOException("fail to rename file " + indexTmp + " to " + indexFile)
179-
}
180-
if (dataTmp != null && dataTmp.exists() && !dataTmp.renameTo(dataFile)) {
181-
throw new IOException("fail to rename file " + dataTmp + " to " + dataFile)
156+
val dataFile = getDataFile(shuffleId, mapId)
157+
// There is only one IndexShuffleBlockResolver per executor, this synchronization make sure
158+
// the following check and rename are atomic.
159+
synchronized {
160+
val existingLengths = checkIndexAndDataFile(indexFile, dataFile, lengths.length)
161+
if (existingLengths != null) {
162+
// Another attempt for the same task has already written our map outputs successfully,
163+
// so just use the existing partition lengths and delete our temporary map outputs.
164+
System.arraycopy(existingLengths, 0, lengths, 0, lengths.length)
165+
if (dataTmp != null && dataTmp.exists()) {
166+
dataTmp.delete()
167+
}
168+
indexTmp.delete()
169+
} else {
170+
// This is the first successful attempt in writing the map outputs for this task,
171+
// so override any existing index and data files with the ones we wrote.
172+
if (indexFile.exists()) {
173+
indexFile.delete()
174+
}
175+
if (dataFile.exists()) {
176+
dataFile.delete()
177+
}
178+
if (!indexTmp.renameTo(indexFile)) {
179+
throw new IOException("fail to rename file " + indexTmp + " to " + indexFile)
180+
}
181+
if (dataTmp != null && dataTmp.exists() && !dataTmp.renameTo(dataFile)) {
182+
throw new IOException("fail to rename file " + dataTmp + " to " + dataFile)
183+
}
182184
}
183185
}
186+
} finally {
187+
if (indexTmp.exists() && !indexTmp.delete()) {
188+
logError(s"Failed to delete temporary index file at ${indexTmp.getAbsolutePath}")
189+
}
184190
}
185191
}
186192

core/src/main/scala/org/apache/spark/shuffle/sort/SortShuffleWriter.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,16 @@ private[spark] class SortShuffleWriter[K, V, C](
6767
// (see SPARK-3570).
6868
val output = shuffleBlockResolver.getDataFile(dep.shuffleId, mapId)
6969
val tmp = Utils.tempFileWith(output)
70-
val blockId = ShuffleBlockId(dep.shuffleId, mapId, IndexShuffleBlockResolver.NOOP_REDUCE_ID)
71-
val partitionLengths = sorter.writePartitionedFile(blockId, tmp)
72-
shuffleBlockResolver.writeIndexFileAndCommit(dep.shuffleId, mapId, partitionLengths, tmp)
73-
mapStatus = MapStatus(blockManager.shuffleServerId, partitionLengths)
70+
try {
71+
val blockId = ShuffleBlockId(dep.shuffleId, mapId, IndexShuffleBlockResolver.NOOP_REDUCE_ID)
72+
val partitionLengths = sorter.writePartitionedFile(blockId, tmp)
73+
shuffleBlockResolver.writeIndexFileAndCommit(dep.shuffleId, mapId, partitionLengths, tmp)
74+
mapStatus = MapStatus(blockManager.shuffleServerId, partitionLengths)
75+
} finally {
76+
if (tmp.exists() && !tmp.delete()) {
77+
logError(s"Error while deleting temp file ${tmp.getAbsolutePath}")
78+
}
79+
}
7480
}
7581

7682
/** Close this writer, passing along whether the map completed */

0 commit comments

Comments
 (0)