Bug 552185 - Bad performance of FileSystemResourceManager.write(IFile)

Don't call mkdir without checking if directory exists or not before -
NIO throws exceptions that will slowdown writing multiple files in same
directory.

Also added a benchmark in the form of a test. The test is not included
in automatic core.resources tests.

Change-Id: I47eeb8e87c5e9459c5df167c74c64a760a96305b
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Also-by: Christian Dietrich<christian.dietrich@itemis.de>
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
index b37b921..f034e98 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java
@@ -1147,8 +1147,13 @@
 			if (BitMask.isSet(updateFlags, IResource.KEEP_HISTORY) && fileInfo.exists())
 				//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())
-				store.getParent().mkdir(EFS.NONE, null);
+			if (!fileInfo.exists()) {
+				IFileStore parent = store.getParent();
+				IFileInfo parentInfo = parent.fetchInfo();
+				if (!parentInfo.exists()) {
+					parent.mkdir(EFS.NONE, null);
+				}
+			}
 
 			// 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
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/Bug552185PerformanceTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/Bug552185PerformanceTest.java
new file mode 100644
index 0000000..4c094e6
--- /dev/null
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/resources/Bug552185PerformanceTest.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2019 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.tests.internal.resources;
+
+import java.io.ByteArrayInputStream;
+import junit.framework.TestCase;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+
+/**
+ * A benchmark for bug 552185.
+ *
+ * Not included in actual tests, since performance is only printed and not
+ * asserted.
+ */
+public class Bug552185PerformanceTest extends TestCase {
+
+	public void testBug552185Performance() throws Exception {
+		// run inside a WorkspaceJob, in case there are listeners on workspace changes
+		WorkspaceJob testJob = new WorkspaceJob(Bug552185PerformanceTest.class.getName()) {
+			@Override
+			public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
+				if (monitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
+				}
+				try {
+					runBenchmark(monitor);
+				} catch (CoreException e) {
+					return new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, "Benchmark failed.", e);
+				}
+
+				return Status.OK_STATUS;
+			}
+
+		};
+		testJob.schedule();
+		testJob.join();
+	}
+
+	static void runBenchmark(IProgressMonitor monitor) throws CoreException {
+		runBenchmark(1, 10_000, monitor); // 1 directory with 10k files
+		runBenchmark(10_000, 1, monitor); // 10k directories with 1 file each
+		runBenchmark(10, 1_000, monitor); // 10 directories with 1k files each
+		runBenchmark(1, 100_000, monitor); // 1 directory with 100k files
+		runBenchmark(100_000, 1, monitor); // 100k directories with 1 file each
+	}
+
+	static void runBenchmark(int directoriesCount, int fileCountPerDirectory, IProgressMonitor monitor)
+			throws CoreException {
+		Workspace workspace = (Workspace) ResourcesPlugin.getWorkspace();
+		IWorkspaceRoot root = workspace.getRoot();
+		boolean local = false;
+
+		IProject project = root.getProject("TestBug552185ResourcesIO");
+
+		try {
+			project.create(monitor);
+			project.open(monitor);
+			System.out.println(
+					"Creating " + directoriesCount + " directories with " + fileCountPerDirectory + " files each");
+			long start = System.currentTimeMillis();
+
+			SubMonitor subMonitor = SubMonitor.convert(monitor, directoriesCount);
+			for (int i = 0; i < directoriesCount; ++i) {
+				IFolder folder = project.getFolder("folder" + i);
+				SubMonitor subMonitor2 = SubMonitor.convert(subMonitor, "Creating directory " + folder.getName(),
+						fileCountPerDirectory);
+				folder.create(IResource.FORCE, local, subMonitor);
+				for (int j = 0; j < fileCountPerDirectory; ++j) {
+					subMonitor2.checkCanceled();
+					IFile file = folder.getFile("file" + j);
+					String content = "file content " + j;
+					ByteArrayInputStream contentStream = new ByteArrayInputStream(content.getBytes());
+					file.create(contentStream, IResource.FORCE, subMonitor2);
+					subMonitor2.worked(1);
+				}
+				subMonitor.worked(1);
+			}
+			subMonitor.done();
+
+			long end = System.currentTimeMillis();
+			long elapsed = end - start;
+			System.out.println("Elapsed: " + elapsed + " ms");
+		} finally {
+			if (project.exists()) {
+				project.delete(IResource.FORCE, monitor);
+			}
+		}
+	}
+}