From 5ea80d24ad3625106e0ec0e04a47d05d5f97b517 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Fri, 28 Aug 2026 05:07:04 +0200 Subject: [PATCH] Move instead of copy when committing a SafeFileOutputStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit step copied the temporary file over the target and then deleted it, so every byte was written twice. The workspace save uses this stream for the workspace tree and the .markers and .syncinfo files of every project, so the extra write is paid on every save and every snapshot. A crash during the copy could also leave a partially written target, which the recovery in the constructor never repairs because it only acts when the target is missing. Rename the temporary file atomically instead, and fall back to the previous copy only when the file system cannot move it atomically. Contributes to https://github.com/eclipse-platform/eclipse.platform/issues/2887 Assisted-by: multiple AI agents and layers of automated tooling 🤖 --- .../core/internal/localstore/SafeFileOutputStream.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java index b27771e3d03..bf6b314b414 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeFileOutputStream.java @@ -15,6 +15,7 @@ package org.eclipse.core.internal.localstore; import java.io.*; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -82,8 +83,13 @@ protected void commit() throws IOException { if (!temp.exists()) { return; } - Files.copy(temp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - temp.delete(); + try { + // a rename does not rewrite the bytes and never leaves a partially written target + Files.move(temp.toPath(), target.toPath(), StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.copy(temp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + temp.delete(); + } } protected void createTempFile(String tempPath) {