From 8b8056a278e15dea09a92f674a55a20f15855231 Mon Sep 17 00:00:00 2001 From: Riccardo Sale Date: Tue, 6 Oct 2026 15:05:26 +0200 Subject: [PATCH] fix: `writeAtomically` fails for file names longer than 217 bytes FileUtils.writeAtomically named its temporary file "..", adding 38 characters to the target name. File names between 218 and 255 bytes were valid, but their temporary names were not, so the write failed with "File name too long". WorkerTaskManager persists tasks this way using the task ID as the file name, so tasks with such IDs, such as kill tasks on datasources with long names, could never start. Name the temporary file "." instead. The UUID keeps it unique, and the target file keeps its full name after the move. Fixes #20492. --- .../druid/indexing/worker/WorkerTaskManagerTest.java | 8 ++++++++ .../java/org/apache/druid/java/util/common/FileUtils.java | 2 +- .../org/apache/druid/java/util/common/FileUtilsTest.java | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java b/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java index 2eee78045611..75e5a677fc29 100644 --- a/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java +++ b/indexing-service/src/test/java/org/apache/druid/indexing/worker/WorkerTaskManagerTest.java @@ -47,6 +47,7 @@ import org.apache.druid.indexing.overlord.TestTaskRunner; import org.apache.druid.indexing.worker.config.WorkerConfig; import org.apache.druid.java.util.common.FileUtils; +import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.http.client.response.StringFullResponseHolder; import org.apache.druid.query.policy.NoopPolicyEnforcer; import org.apache.druid.rpc.HttpResponseException; @@ -309,6 +310,13 @@ public void testTaskRun() throws Exception Assertions.assertNotNull(update4.getTaskAnnouncement().getTaskLocation().getHost()); } + @Test + public void testAssignTaskWithMaxLengthTaskId() throws Exception + { + workerTaskManager.start(); + Assertions.assertDoesNotThrow(() -> workerTaskManager.assignTask(createNoopTask(StringUtils.repeat("x", 255)))); + } + @Test @Timeout(value = 30_000L, unit = TimeUnit.MILLISECONDS) public void testTaskStatusWhenTaskRunnerFutureThrowsException() throws Exception diff --git a/processing/src/main/java/org/apache/druid/java/util/common/FileUtils.java b/processing/src/main/java/org/apache/druid/java/util/common/FileUtils.java index fee9217db61a..df1c292b1751 100644 --- a/processing/src/main/java/org/apache/druid/java/util/common/FileUtils.java +++ b/processing/src/main/java/org/apache/druid/java/util/common/FileUtils.java @@ -262,7 +262,7 @@ public static T writeAtomically(final File file, OutputStreamConsumer f) */ public static T writeAtomically(final File file, final File tmpDir, OutputStreamConsumer f) throws IOException { - final File tmpFile = new File(tmpDir, StringUtils.format(".%s.%s", file.getName(), UUID.randomUUID())); + final File tmpFile = new File(tmpDir, "." + UUID.randomUUID()); //noinspection unused try (final Closeable ignoredDeleter = () -> Files.deleteIfExists(tmpFile.toPath())) { diff --git a/processing/src/test/java/org/apache/druid/java/util/common/FileUtilsTest.java b/processing/src/test/java/org/apache/druid/java/util/common/FileUtilsTest.java index ca23f891d33d..ef318d741582 100644 --- a/processing/src/test/java/org/apache/druid/java/util/common/FileUtilsTest.java +++ b/processing/src/test/java/org/apache/druid/java/util/common/FileUtilsTest.java @@ -174,6 +174,14 @@ public void testWriteAtomically() throws IOException Assertions.assertEquals("baz", StringUtils.fromUtf8(Files.readAllBytes(tmpFile.toPath()))); } + @Test + public void testWriteAtomicallyMaxLengthFileName() throws IOException + { + final File file = new File(temporaryFolder.getRoot(), StringUtils.repeat("a", 255)); + FileUtils.writeAtomically(file, out -> true); + Assertions.assertTrue(file.exists()); + } + @Test public void testResolveFileWithinDirectory() {