From 9997b1a1d39f5f75efc0a4d7b48bd0153210e6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Kubitz?= Date: Tue, 25 Jun 2024 14:28:52 +0200 Subject: [PATCH] [performance] IFile.write(byte[]): reduce store.fetchInfo() Optimistically assume the parent folder already exists on local File System when it exists in workspace (normal case). So do not explicitly check it. If it did not exist file write will fail and can be retried after creating the parent in File System. This optimization is only implemented for byte[] content in the not appending case: * InputStream content would need a reset. * In append mode it is not obvious if something was already appended. https://github.com/eclipse-platform/eclipse.platform/issues/1443 --- .../localstore/FileSystemResourceManager.java | 28 +++++++++++++++---- .../core/tests/resources/IFileTest.java | 22 +++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java index 5cc21b19a76..8b97e7907a1 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -1211,7 +1211,7 @@ public void write(IFile target, InputStream content, IFileInfo fileInfo, int upd try (content) { Resource targetResource = (Resource) target; IFileStore store = getStore(target); - prepareWrite(target, fileInfo, updateFlags, append, targetResource, store); + prepareWrite(target, fileInfo, updateFlags, append, targetResource, store, false); // On Windows an attempt to open an output stream on a hidden file results in FileNotFoundException. // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=194216 @@ -1243,7 +1243,7 @@ public void write(IFile target, InputStream content, IFileInfo fileInfo, int upd } private void prepareWrite(IFile target, IFileInfo fileInfo, int updateFlags, boolean append, - Resource targetResource, IFileStore store) throws CoreException { + Resource targetResource, IFileStore store, boolean assumeParentDirectoryExists) throws CoreException { if (fileInfo.getAttribute(EFS.ATTRIBUTE_READ_ONLY)) { String message = NLS.bind(Messages.localstore_couldNotWriteReadOnly, target.getFullPath()); throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, target.getFullPath(), message, null); @@ -1292,7 +1292,7 @@ private void prepareWrite(IFile target, IFileInfo fileInfo, int updateFlags, boo // never move to the history store, because then the file is missing if write // fails getHistoryStore().addState(target.getFullPath(), store, fileInfo, false); - if (!fileInfo.exists()) { + if (!assumeParentDirectoryExists && !fileInfo.exists()) { IFileStore parent = store.getParent(); IFileInfo parentInfo = parent.fetchInfo(); if (!parentInfo.exists()) { @@ -1327,7 +1327,7 @@ public void write(IFile target, byte[] content, IFileInfo fileInfo, int updateFl SubMonitor subMonitor = SubMonitor.convert(monitor, 4); Resource targetResource = (Resource) target; IFileStore store = getStore(target); - prepareWrite(target, fileInfo, updateFlags, append, targetResource, store); + prepareWrite(target, fileInfo, updateFlags, append, targetResource, store, true); // On Windows an attempt to open an output stream on a hidden file results in // FileNotFoundException. @@ -1342,7 +1342,25 @@ public void write(IFile target, byte[] content, IFileInfo fileInfo, int updateFl subMonitor.split(1); } int options = append ? EFS.APPEND : EFS.NONE; - store.write(content, options, subMonitor.split(1)); + try { + store.write(content, options, subMonitor.split(1)); + } catch (CoreException e) { + if (append) { + throw e; + } + if (e.getStatus().getCode() != EFS.ERROR_WRITE) { + throw e; + } + IFileStore parent = store.getParent(); + IFileInfo parentInfo = parent.fetchInfo(); + if (parentInfo.exists()) { + throw e; + } + // create missing folders: + parent.mkdir(EFS.NONE, null); + // try again: + store.write(content, options, null); + } if (restoreHiddenAttribute) { fileInfo.setAttribute(EFS.ATTRIBUTE_HIDDEN, true); store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, subMonitor.split(1)); diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java index f1738069852..8f38739d98a 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IFileTest.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFileState; @@ -507,6 +508,27 @@ public void testCreateBytes() throws CoreException { assertFalse(derived.isDerived()); assertFalse(derived.isTeamPrivateMember()); assertEquals("notDerived", derived.readString()); + + IFolder subFolder = projects[0].getFolder("subFolder"); + subFolder.create(true, true, null); + subFolder.getRawLocation().toFile().delete(); + + IFile orphan = subFolder.getFile("myParentDoesNotExist.txt"); + monitor.prepare(); + orphan.write("parentDoesNotExistInFileSystemButInWorkspace".getBytes(), true, false, false, monitor); + monitor.assertUsedUp(); + assertEquals("parentDoesNotExistInFileSystemButInWorkspace", orphan.readString()); + + monitor.prepare(); + orphan.getParent().delete(true, null); + // if the parent is deleted in workspace Exception is expected: + try { + orphan.write("parentDoesNotExist - not even in workspace".getBytes(), true, false, false, monitor); + assertFalse("should not be reached", true); + } catch (ResourceException expected) { + monitor.assertUsedUp(); + assertFalse(orphan.exists()); + } } @Test