All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.MinimalRebuildCacheManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Google Inc.
 *
 * 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
 *
 * http://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 com.google.gwt.dev;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.cfg.PropertyCombinations.PermutationDescription;
import com.google.gwt.dev.util.CompilerVersion;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.cache.Cache;
import com.google.gwt.thirdparty.guava.common.cache.CacheBuilder;
import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures;
import com.google.gwt.thirdparty.guava.common.util.concurrent.MoreExecutors;
import com.google.gwt.util.tools.Utility;
import com.google.gwt.util.tools.shared.Md5Utils;
import com.google.gwt.util.tools.shared.StringUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Manages caching of MinimalRebuildCache instances.
 * 

* Changes are immediately performed in memory and are asynchronously persisted to disk in original * request order. */ public class MinimalRebuildCacheManager { private static final int MEMORY_CACHE_COUNT_LIMIT = 3; private static final String REBUILD_CACHE_PREFIX = "gwt-rebuildCache"; private final ExecutorService executorService = MoreExecutors.getExitingExecutorService((ThreadPoolExecutor) Executors.newFixedThreadPool(1)); private final TreeLogger logger; private final File minimalRebuildCacheDir; private final Cache minimalRebuildCachesByName = CacheBuilder.newBuilder().maximumSize(MEMORY_CACHE_COUNT_LIMIT).build(); private final Map options = new LinkedHashMap<>(); public MinimalRebuildCacheManager( TreeLogger logger, File baseCacheDir, Map options) { this.logger = logger; this.options.putAll(options); if (baseCacheDir != null) { minimalRebuildCacheDir = new File(baseCacheDir, REBUILD_CACHE_PREFIX); minimalRebuildCacheDir.mkdir(); } else { minimalRebuildCacheDir = null; } } /** * Synchronously delete all in memory caches managed here and all on disk in the managed folder. */ public synchronized void deleteCaches() { syncDeleteMemoryCaches(); if (haveCacheDir()) { Futures.getUnchecked(enqueueAsyncDeleteDiskCaches()); } } /** * Synchronously return the MinimalRebuildCache specific to the given module and binding * properties. *

* If no cache is found in memory then it will be synchronously loaded from disk. *

* If it is still not found a new empty cache will be returned. */ public synchronized MinimalRebuildCache getCache(String moduleName, PermutationDescription permutationDescription) { String cacheName = computeMinimalRebuildCacheName(moduleName, permutationDescription); MinimalRebuildCache minimalRebuildCache = minimalRebuildCachesByName.getIfPresent(cacheName); // If there's no cache already in memory, try to load a cache from disk. if (minimalRebuildCache == null && haveCacheDir()) { // Might return null. minimalRebuildCache = syncReadDiskCache(moduleName, permutationDescription); if (minimalRebuildCache != null) { minimalRebuildCachesByName.put(cacheName, minimalRebuildCache); } } // If there's still no cache loaded, just create a blank one. if (minimalRebuildCache == null) { minimalRebuildCache = new MinimalRebuildCache(); minimalRebuildCachesByName.put(cacheName, minimalRebuildCache); return minimalRebuildCache; } // Return a copy. MinimalRebuildCache mutableMinimalRebuildCache = new MinimalRebuildCache(); mutableMinimalRebuildCache.copyFrom(minimalRebuildCache); return mutableMinimalRebuildCache; } /** * Stores a MinimalRebuildCache specific to the given module and binding properties. *

* A copy of the cache will be lazily persisted to disk as well. */ public synchronized void putCache(String moduleName, PermutationDescription permutationDescription, MinimalRebuildCache knownGoodMinimalRebuildCache) { syncPutMemoryCache(moduleName, permutationDescription, knownGoodMinimalRebuildCache); if (haveCacheDir()) { enqueueAsyncWriteDiskCache(moduleName, permutationDescription, knownGoodMinimalRebuildCache); } } /** * Enqueue to asynchronously delete all on disk caches in the managed cache folder. */ @VisibleForTesting synchronized Future enqueueAsyncDeleteDiskCaches() { return executorService.submit(new Callable() { @Override public Void call() { for (File cacheFile : minimalRebuildCacheDir.listFiles()) { if (!cacheFile.delete()) { logger.log(TreeLogger.WARN, "Couldn't delete " + cacheFile); } } return null; } }); } /** * Enqueue to asynchronously find, read and return the MinimalRebuildCache unique to this module * and binding properties combination in the managed cache folder. */ @VisibleForTesting synchronized Future enqueueAsyncReadDiskCache(final String moduleName, final PermutationDescription permutationDescription) { return executorService.submit(new Callable() { @Override public MinimalRebuildCache call() { // Find the cache file unique to this module, binding properties and working directory. File minimalRebuildCacheFile = computeMinimalRebuildCacheFile(moduleName, permutationDescription); // If the file exists. if (minimalRebuildCacheFile.exists()) { ObjectInputStream objectInputStream = null; // Try to read it. try { objectInputStream = new ObjectInputStream( new BufferedInputStream(new FileInputStream(minimalRebuildCacheFile))); return (MinimalRebuildCache) objectInputStream.readObject(); } catch (IOException e) { logger.log(TreeLogger.WARN, "Unable to read the rebuild cache in " + minimalRebuildCacheFile + "."); Utility.close(objectInputStream); minimalRebuildCacheFile.delete(); } catch (ClassNotFoundException e) { logger.log(TreeLogger.WARN, "Unable to read the rebuild cache in " + minimalRebuildCacheFile + "."); Utility.close(objectInputStream); minimalRebuildCacheFile.delete(); } finally { Utility.close(objectInputStream); } } return null; } }); } /** * Enqueue to asynchronously write the provided MinimalRebuildCache to disk. *

* Persisted caches are uniquely named based on the compiler version, current module name, binding * properties and the location where the JVM was launched. *

* Care is taken to completely and successfully write a new cache (to a different location on * disk) before replacing the old cache (at the regular location on disk). *

* Write requests will occur in the order requested and will queue up if requests are made faster * than they can be completed. */ @VisibleForTesting synchronized Future enqueueAsyncWriteDiskCache(final String moduleName, final PermutationDescription permutationDescription, final MinimalRebuildCache minimalRebuildCache) { return executorService.submit(new Callable() { @Override public Void call() { File oldMinimalRebuildCacheFile = computeMinimalRebuildCacheFile(moduleName, permutationDescription); File newMinimalRebuildCacheFile = new File(oldMinimalRebuildCacheFile.getAbsoluteFile() + ".new"); // Ensure the cache folder exists. oldMinimalRebuildCacheFile.getParentFile().mkdirs(); // Write the new cache to disk. ObjectOutputStream objectOutputStream = null; try { objectOutputStream = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream(newMinimalRebuildCacheFile))); objectOutputStream.writeObject(minimalRebuildCache); Utility.close(objectOutputStream); // Replace the old cache file with the new one. oldMinimalRebuildCacheFile.delete(); newMinimalRebuildCacheFile.renameTo(oldMinimalRebuildCacheFile); } catch (IOException e) { logger.log(TreeLogger.WARN, "Unable to update the cache in " + oldMinimalRebuildCacheFile + "."); newMinimalRebuildCacheFile.delete(); } finally { if (objectOutputStream != null) { Utility.close(objectOutputStream); } } return null; } }); } /** * For testing only. Stops accepting any new tasks and waits for current tasks to complete. */ @VisibleForTesting boolean shutdown() throws InterruptedException { executorService.shutdown(); return executorService.awaitTermination(30, TimeUnit.SECONDS); } /** * Find, read and return the MinimalRebuildCache unique to this module, binding properties and * working directory. */ @VisibleForTesting synchronized MinimalRebuildCache syncReadDiskCache(String moduleName, PermutationDescription permutationDescription) { return Futures.getUnchecked(enqueueAsyncReadDiskCache(moduleName, permutationDescription)); } private File computeMinimalRebuildCacheFile(String moduleName, PermutationDescription permutationDescription) { return new File(minimalRebuildCacheDir, computeMinimalRebuildCacheName(moduleName, permutationDescription)); } private String computeMinimalRebuildCacheName(String moduleName, PermutationDescription permutationDescription) { String currentWorkingDirectory = System.getProperty("user.dir"); String compilerVersionHash = CompilerVersion.getHash(); String permutationDescriptionString = permutationDescription.toString(); String optionsDescriptionString = " Options ["; String separator = ""; for (Map.Entry entry : options.entrySet()) { optionsDescriptionString += String.format("%s%s = %s", separator, entry.getKey(), entry.getValue()); separator = ","; } optionsDescriptionString += "]"; String consistentHash = StringUtils.toHexString(Md5Utils.getMd5Digest(( compilerVersionHash + moduleName + currentWorkingDirectory + permutationDescriptionString + optionsDescriptionString) .getBytes())); return REBUILD_CACHE_PREFIX + "-" + consistentHash; } private boolean haveCacheDir() { return minimalRebuildCacheDir != null && minimalRebuildCacheDir.isDirectory(); } private void syncDeleteMemoryCaches() { minimalRebuildCachesByName.invalidateAll(); } private void syncPutMemoryCache(String moduleName, PermutationDescription permutationDescription, MinimalRebuildCache knownGoodMinimalRebuildCache) { String cacheName = computeMinimalRebuildCacheName(moduleName, permutationDescription); minimalRebuildCachesByName.put(cacheName, knownGoodMinimalRebuildCache); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy