| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.storm.localizer; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| /** |
| * A set of resources that we can look at to see which ones we retain and which ones should be |
| * removed. |
| */ |
| public class LocalizedResourceRetentionSet { |
| public static final Logger LOG = LoggerFactory.getLogger(LocalizedResourceRetentionSet.class); |
| private long _delSize; |
| private long _currentSize; |
| // targetSize in Bytes |
| private long _targetSize; |
| private final SortedMap<LocalizedResource, LocalizedResourceSet> _noReferences; |
| |
| LocalizedResourceRetentionSet(long targetSize) { |
| this(targetSize, new LRUComparator()); |
| } |
| |
| LocalizedResourceRetentionSet(long targetSize, Comparator<? super LocalizedResource> cmp) { |
| this(targetSize, new TreeMap<LocalizedResource, LocalizedResourceSet>(cmp)); |
| } |
| |
| LocalizedResourceRetentionSet(long targetSize, |
| SortedMap<LocalizedResource, LocalizedResourceSet> retain) { |
| this._noReferences = retain; |
| this._targetSize = targetSize; |
| } |
| |
| // for testing |
| protected int getSizeWithNoReferences() { |
| return _noReferences.size(); |
| } |
| |
| protected void addResourcesForSet(Iterator<LocalizedResource> setIter, LocalizedResourceSet set) { |
| for (Iterator<LocalizedResource> iter = setIter; setIter.hasNext(); ) { |
| LocalizedResource lrsrc = iter.next(); |
| _currentSize += lrsrc.getSize(); |
| if (lrsrc.getRefCount() > 0) { |
| // always retain resources in use |
| continue; |
| } |
| LOG.debug("adding {} to be checked for cleaning", lrsrc.getKey()); |
| _noReferences.put(lrsrc, set); |
| } |
| } |
| |
| public void addResources(LocalizedResourceSet set) { |
| addResourcesForSet(set.getLocalFilesIterator(), set); |
| addResourcesForSet(set.getLocalArchivesIterator(), set); |
| } |
| |
| public void cleanup() { |
| LOG.debug("cleanup target size: {} current size is: {}", _targetSize, _currentSize); |
| for (Iterator<Map.Entry<LocalizedResource, LocalizedResourceSet>> i = |
| _noReferences.entrySet().iterator(); |
| _currentSize - _delSize > _targetSize && i.hasNext();) { |
| Map.Entry<LocalizedResource, LocalizedResourceSet> rsrc = i.next(); |
| LocalizedResource resource = rsrc.getKey(); |
| LocalizedResourceSet set = rsrc.getValue(); |
| if (resource != null && set.remove(resource)) { |
| if (deleteResource(resource)) { |
| _delSize += resource.getSize(); |
| LOG.info("deleting: " + resource.getFilePath() + " size of: " + resource.getSize()); |
| i.remove(); |
| } else { |
| // since it failed to delete add it back so it gets retried |
| set.add(resource.getKey(), resource, resource.isUncompressed()); |
| } |
| } |
| } |
| } |
| |
| protected boolean deleteResource(LocalizedResource resource){ |
| try { |
| String fileWithVersion = resource.getFilePathWithVersion(); |
| String currentSymlinkName = resource.getCurrentSymlinkPath(); |
| String versionFile = resource.getVersionFilePath(); |
| File deletePath = new File(fileWithVersion); |
| if (resource.isUncompressed()) { |
| // this doesn't follow symlinks, which is what we want |
| FileUtils.deleteDirectory(deletePath); |
| } else { |
| Files.delete(deletePath.toPath()); |
| } |
| Files.delete(new File(currentSymlinkName).toPath()); |
| Files.delete(new File(versionFile).toPath()); |
| return true; |
| } catch (IOException e) { |
| LOG.warn("Could not delete: {}", resource.getFilePath()); |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Cache: ").append(_currentSize).append(", "); |
| sb.append("Deleted: ").append(_delSize); |
| return sb.toString(); |
| } |
| |
| static class LRUComparator implements Comparator<LocalizedResource> { |
| public int compare(LocalizedResource r1, LocalizedResource r2) { |
| long ret = r1.getLastAccessTime() - r2.getLastAccessTime(); |
| if (0 == ret) { |
| return System.identityHashCode(r1) - System.identityHashCode(r2); |
| } |
| return ret > 0 ? 1 : -1; |
| } |
| } |
| } |