diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/ProjectGitHandler.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/ProjectGitHandler.java index 00a398ae..ec54c510 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/ProjectGitHandler.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/ProjectGitHandler.java @@ -17,7 +17,6 @@ package org.springframework.cloud.release.internal.git; import java.io.File; -import java.nio.file.Files; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +29,7 @@ import org.springframework.cloud.release.internal.ReleaserProperties; import org.springframework.cloud.release.internal.ReleaserPropertiesAware; import org.springframework.cloud.release.internal.pom.ProjectVersion; import org.springframework.cloud.release.internal.pom.Projects; +import org.springframework.cloud.release.internal.tech.TemporaryFileStorage; import org.springframework.util.StringUtils; /** @@ -61,6 +61,11 @@ public class ProjectGitHandler implements ReleaserPropertiesAware { this.properties = properties; this.githubMilestones = new GithubMilestones(properties); this.githubIssues = new GithubIssues(properties); + registerShutdownHook(); + } + + private void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(TemporaryFileStorage::cleanup)); } public void commitAndTagIfApplicable(File project, ProjectVersion version) { @@ -213,7 +218,7 @@ public class ProjectGitHandler implements ReleaserPropertiesAware { File destinationDir = this.properties.getGit() .getCloneDestinationDir() != null ? new File(this.properties.getGit().getCloneDestinationDir()) - : Files.createTempDirectory("releaser").toFile(); + : TemporaryFileStorage.createTempDir("releaser"); File clonedLocation = gitRepo(destinationDir).cloneProject(urIish); CACHE.put(urIish, clonedLocation); return clonedLocation; diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/tech/TemporaryFileStorage.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/tech/TemporaryFileStorage.java new file mode 100644 index 00000000..55671fc2 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/tech/TemporaryFileStorage.java @@ -0,0 +1,118 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.release.internal.tech; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Stores all generated temporary folders. + * + * @author Marcin Grzejszczak + */ +public final class TemporaryFileStorage { + + private static final Log log = LogFactory.getLog(TemporaryFileStorage.class); + + private static final int TEMP_DIR_ATTEMPTS = 10000; + + /** + * There are problems with removal of temporary files. That's why we're creating a + * bounded in-memory storage of unpacked files and later we register a shutdown hook + * to remove all these files. + */ + private static final Queue TEMP_FILES_LOG = new LinkedBlockingQueue<>(1000); + + private TemporaryFileStorage() { + throw new IllegalStateException("Can't instantiate a utility class"); + } + + private static void add(File file) { + TEMP_FILES_LOG.add(file); + } + + private static Queue files() { + return TEMP_FILES_LOG; + } + + public static void cleanup() { + try { + for (File file : TemporaryFileStorage.files()) { + if (file.isDirectory()) { + Files.walkFileTree(file.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) throws IOException { + if (log.isTraceEnabled()) { + log.trace("Removing file [" + file + "]"); + } + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, + IOException exc) throws IOException { + if (log.isTraceEnabled()) { + log.trace("Removing dir [" + dir + "]"); + } + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + else { + Files.delete(file.toPath()); + } + } + } + catch (NoClassDefFoundError | IOException e) { + // Added NoClassDefFoundError cause sometimes it's visible in the builds + // this error is completely harmless + if (log.isTraceEnabled()) { + log.trace("Failed to remove temporary file", e); + } + } + } + + // taken from Guava + public static File createTempDir(String tempDirPrefix) { + File baseDir = new File(System.getProperty("java.io.tmpdir")); + String baseName = tempDirPrefix + "-" + System.currentTimeMillis() + "-"; + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + add(tempDir); + return tempDir; + } + } + throw new IllegalStateException("Failed to create directory within " + + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + + (TEMP_DIR_ATTEMPTS - 1) + ")"); + } + +}