Repository: ant-ivy Updated Branches: refs/heads/master 12d3f2818 -> 5240c8825
IVY-1430 : dynamic revisions are not cached per resolver Thanks to Stephen Haberman Project: http://git-wip-us.apache.org/repos/asf/ant-ivy/repo Commit: http://git-wip-us.apache.org/repos/asf/ant-ivy/commit/5240c882 Tree: http://git-wip-us.apache.org/repos/asf/ant-ivy/tree/5240c882 Diff: http://git-wip-us.apache.org/repos/asf/ant-ivy/diff/5240c882 Branch: refs/heads/master Commit: 5240c8825896997f0845e354a51b1ac725dfe06b Parents: 12d3f28 Author: Nicolas Lalevée <[email protected]> Authored: Sun Sep 6 14:14:58 2015 +0200 Committer: Nicolas Lalevée <[email protected]> Committed: Sun Sep 6 14:14:58 2015 +0200 ---------------------------------------------------------------------- doc/release-notes.html | 1 + .../cache/DefaultRepositoryCacheManager.java | 41 +++++++++- .../ivy/core/cache/RepositoryCacheManager.java | 15 ++++ .../ivy/plugins/resolver/AbstractResolver.java | 10 +-- .../DefaultRepositoryCacheManagerTest.java | 80 +++++++++++++++++++- 5 files changed, 134 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ant-ivy/blob/5240c882/doc/release-notes.html ---------------------------------------------------------------------- diff --git a/doc/release-notes.html b/doc/release-notes.html index 63c4fcf..12624af 100644 --- a/doc/release-notes.html +++ b/doc/release-notes.html @@ -60,6 +60,7 @@ List of changes since Ivy 2.4.0: - FIX: ArrayIndexOutOfBoundsException when using a p2 repository for dependencies (IVY-1504) - FIX: fixdeps remove transitive 'kept' dependencies - FIX: PomModuleDescriptorParser should parse licenses from parent POM (IVY-1526) (Thanks to Jaikiran Pai) +- FIX: dynamic revisions are not cached per resolver (IVY-1430) (Thanks to Stephen Haberman) - IMPROVEMENT: Optimization: limit the revision numbers scanned if revision prefix is specified (Thanks to Ernestas Vaiciukevičius) http://git-wip-us.apache.org/repos/asf/ant-ivy/blob/5240c882/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java b/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java index a950c0b..f6b2206 100644 --- a/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java +++ b/src/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManager.java @@ -667,6 +667,17 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv getDataFilePattern(), mRevId)), "ivy cached data file for " + mRevId); } + /** + * A resolver-specific ivydata file, only used for caching dynamic revisions, e.g. + * integration-repo. + */ + private PropertiesFile getCachedDataFile(String resolverName, ModuleRevisionId mRevId) { + // we append ".${resolverName} onto the end of the regular ivydata location + return new PropertiesFile(new File(getRepositoryCacheRoot(), + IvyPatternHelper.substitute(getDataFilePattern(), mRevId) + "." + resolverName), + "ivy cached data file for " + mRevId); + } + public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) { @@ -693,7 +704,7 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv try { if (settings.getVersionMatcher().isDynamic(mrid)) { - String resolvedRevision = getResolvedRevision(mrid, options); + String resolvedRevision = getResolvedRevision(expectedResolver, mrid, options); if (resolvedRevision != null) { Message.verbose("found resolved revision in cache: " + mrid + " => " + resolvedRevision); @@ -832,7 +843,11 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv return cache.getStale(ivyFile, settings, options.isValidate(), mdProvider); } - private String getResolvedRevision(ModuleRevisionId mrid, CacheMetadataOptions options) { + /** + * Called by doFindModuleInCache to lookup the dynamic {@code mrid} in the ivycache's ivydata + * file. + */ + private String getResolvedRevision(String expectedResolver, ModuleRevisionId mrid, CacheMetadataOptions options) { if (!lockMetadataArtifact(mrid)) { Message.error("impossible to acquire lock for " + mrid); return null; @@ -843,7 +858,13 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv Message.verbose("refresh mode: no check for cached resolved revision for " + mrid); return null; } - PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid); + // If a resolver is asking for its specific dynamic revision, avoid looking at a different one + PropertiesFile cachedResolvedRevision; + if (expectedResolver != null) { + cachedResolvedRevision = getCachedDataFile(expectedResolver, mrid); + } else { + cachedResolvedRevision = getCachedDataFile(mrid); + } resolvedRevision = cachedResolvedRevision.getProperty("resolved.revision"); if (resolvedRevision == null) { Message.verbose(getName() + ": no cached resolved revision for " + mrid); @@ -873,15 +894,27 @@ public class DefaultRepositoryCacheManager implements RepositoryCacheManager, Iv } public void saveResolvedRevision(ModuleRevisionId mrid, String revision) { + saveResolvedRevision(null, mrid, revision); + } + + public void saveResolvedRevision(String resolverName, ModuleRevisionId mrid, String revision) { if (!lockMetadataArtifact(mrid)) { Message.error("impossible to acquire lock for " + mrid); return; } try { - PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid); + PropertiesFile cachedResolvedRevision; + if (resolverName == null) { + cachedResolvedRevision = getCachedDataFile(mrid); + } else { + cachedResolvedRevision = getCachedDataFile(resolverName, mrid); + } cachedResolvedRevision.setProperty("resolved.time", String.valueOf(System.currentTimeMillis())); cachedResolvedRevision.setProperty("resolved.revision", revision); + if (resolverName != null) { + cachedResolvedRevision.setProperty("resolver", resolverName); + } cachedResolvedRevision.save(); } finally { unlockMetadataArtifact(mrid); http://git-wip-us.apache.org/repos/asf/ant-ivy/blob/5240c882/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java b/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java index f6c5c24..a5effa1 100644 --- a/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java +++ b/src/java/org/apache/ivy/core/cache/RepositoryCacheManager.java @@ -188,7 +188,22 @@ public interface RepositoryCacheManager { * the dynamic module revision id * @param revision * the resolved revision + * @deprecated See {@link #saveResolvedRevision(String, ModuleRevisionId, String)} which + * prevents cache + * thrashing when multiple resolvers store the same dynamicMrid */ public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision); + /** + * Caches a dynamic revision constraint resolution for a specific resolver. + * + * @param resolverName + * the resolver in which this dynamic revision was resolved + * @param dynamicMrid + * the dynamic module revision id + * @param revision + * the resolved revision + */ + public void saveResolvedRevision(String resolverName, ModuleRevisionId dynamicMrid, + String revision); + } http://git-wip-us.apache.org/repos/asf/ant-ivy/blob/5240c882/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java b/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java index 6030c9f..ef422d4 100644 --- a/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java +++ b/src/java/org/apache/ivy/plugins/resolver/AbstractResolver.java @@ -513,22 +513,22 @@ public abstract class AbstractResolver implements DependencyResolver, HasLatestS Checks.checkNotNull(dd, "dd"); Checks.checkNotNull(data, "data"); + // always cache dynamic mrids because we can store per-resolver values + saveModuleRevisionIfNeeded(dd, newModuleFound); + // check if latest is asked and compare to return the most recent ResolvedModuleRevision previousModuleFound = data.getCurrentResolvedModuleRevision(); String newModuleDesc = describe(newModuleFound); Message.debug("\tchecking " + newModuleDesc + " against " + describe(previousModuleFound)); if (previousModuleFound == null) { Message.debug("\tmodule revision kept as first found: " + newModuleDesc); - saveModuleRevisionIfNeeded(dd, newModuleFound); return newModuleFound; } else if (isAfter(newModuleFound, previousModuleFound, data.getDate())) { Message.debug("\tmodule revision kept as younger: " + newModuleDesc); - saveModuleRevisionIfNeeded(dd, newModuleFound); return newModuleFound; } else if (!newModuleFound.getDescriptor().isDefault() && previousModuleFound.getDescriptor().isDefault()) { Message.debug("\tmodule revision kept as better (not default): " + newModuleDesc); - saveModuleRevisionIfNeeded(dd, newModuleFound); return newModuleFound; } else { Message.debug("\tmodule revision discarded as older: " + newModuleDesc); @@ -540,8 +540,8 @@ public abstract class AbstractResolver implements DependencyResolver, HasLatestS ResolvedModuleRevision newModuleFound) { if (newModuleFound != null && getSettings().getVersionMatcher().isDynamic(dd.getDependencyRevisionId())) { - getRepositoryCacheManager().saveResolvedRevision(dd.getDependencyRevisionId(), - newModuleFound.getId().getRevision()); + getRepositoryCacheManager().saveResolvedRevision(getName(), + dd.getDependencyRevisionId(), newModuleFound.getId().getRevision()); } } http://git-wip-us.apache.org/repos/asf/ant-ivy/blob/5240c882/test/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManagerTest.java ---------------------------------------------------------------------- diff --git a/test/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManagerTest.java b/test/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManagerTest.java index 0e1b147..61ac601 100644 --- a/test/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManagerTest.java +++ b/test/java/org/apache/ivy/core/cache/DefaultRepositoryCacheManagerTest.java @@ -18,16 +18,33 @@ package org.apache.ivy.core.cache; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.ParseException; import java.util.Date; import junit.framework.TestCase; import org.apache.ivy.Ivy; +import org.apache.ivy.core.IvyContext; import org.apache.ivy.core.module.descriptor.Artifact; import org.apache.ivy.core.module.descriptor.DefaultArtifact; +import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; +import org.apache.ivy.core.module.descriptor.DependencyDescriptor; +import org.apache.ivy.core.module.descriptor.ModuleDescriptor; import org.apache.ivy.core.module.id.ModuleId; import org.apache.ivy.core.module.id.ModuleRevisionId; +import org.apache.ivy.core.resolve.ResolvedModuleRevision; import org.apache.ivy.core.settings.IvySettings; +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter; +import org.apache.ivy.plugins.repository.BasicResource; +import org.apache.ivy.plugins.repository.Resource; +import org.apache.ivy.plugins.repository.ResourceDownloader; +import org.apache.ivy.plugins.resolver.MockResolver; +import org.apache.ivy.plugins.resolver.util.ResolvedResource; +import org.apache.ivy.util.DefaultMessageLogger; +import org.apache.ivy.util.Message; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Delete; @@ -35,16 +52,19 @@ import org.apache.tools.ant.taskdefs.Delete; * @see DefaultResolutionCacheManager */ public class DefaultRepositoryCacheManagerTest extends TestCase { + private DefaultRepositoryCacheManager cacheManager; - private Artifact artifact; - private ArtifactOrigin origin; + private Ivy ivy; protected void setUp() throws Exception { File f = File.createTempFile("ivycache", ".dir"); - Ivy ivy = new Ivy(); + ivy = new Ivy(); ivy.configureDefault(); + ivy.getLoggerEngine().setDefaultLogger(new DefaultMessageLogger(Message.MSG_DEBUG)); + IvyContext.pushNewContext().setIvy(ivy); + IvySettings settings = ivy.getSettings(); f.delete(); // we want to use the file as a directory, so we delete the file itself cacheManager = new DefaultRepositoryCacheManager(); @@ -62,6 +82,7 @@ public class DefaultRepositoryCacheManagerTest extends TestCase { } protected void tearDown() throws Exception { + IvyContext.popContext(); Delete del = new Delete(); del.setProject(new Project()); del.setDir(cacheManager.getRepositoryCacheRoot()); @@ -106,7 +127,58 @@ public class DefaultRepositoryCacheManagerTest extends TestCase { assertTrue(ArtifactOrigin.isUnknown(found)); } - protected Artifact createArtifact(String org, String module, String rev, String name, + public void testLatestIntegrationIsCachedPerResolver() throws Exception { + // given a module org#module + ModuleId mi = new ModuleId("org", "module"); + + // and a latest.integration mrid/dd + ModuleRevisionId mridLatest = new ModuleRevisionId(mi, "trunk", "latest.integration"); + DependencyDescriptor ddLatest = new DefaultDependencyDescriptor(mridLatest, false); + + // and some random options + CacheMetadataOptions options = new CacheMetadataOptions().setCheckTTL(false); + + // setup resolver1 to download the static content so we can call cacheModuleDescriptor + MockResolver resolver1 = new MockResolver(); + resolver1.setName("resolver1"); + resolver1.setSettings(ivy.getSettings()); + ivy.getSettings().addResolver(resolver1); + ResourceDownloader downloader = new ResourceDownloader() { + public void download(Artifact artifact, Resource resource, File dest) + throws IOException { + String content = "<ivy-module version=\"2.0\"><info organisation=\"org\" module=\"module\" status=\"integration\" revision=\"1.1\" branch=\"trunk\"/></ivy-module>"; + dest.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(dest); + PrintWriter pw = new PrintWriter(out); + pw.write(content); + pw.flush(); + out.close(); + } + }; + ModuleDescriptorWriter writer = new ModuleDescriptorWriter() { + public void write(ResolvedResource originalMdResource, ModuleDescriptor md, File src, File dest) throws IOException, ParseException { + XmlModuleDescriptorWriter.write(md, dest); + } + }; + + // latest.integration will resolve to 1.1 in resolver1 + ModuleRevisionId mrid11 = new ModuleRevisionId(mi, "trunk", "1.1"); + DependencyDescriptor dd11 = new DefaultDependencyDescriptor(mrid11, false); + DefaultArtifact artifact11 = new DefaultArtifact(mrid11, new Date(), "module-1.1.ivy", "ivy", "ivy", true); + BasicResource resource11 = new BasicResource("/module-1-1.ivy", true, 1, 0, true); + ResolvedResource mdRef11 = new ResolvedResource(resource11, "1.1"); + + // tell the cache about 1.1 + ResolvedModuleRevision rmr11 = cacheManager.cacheModuleDescriptor(resolver1, mdRef11, dd11, artifact11, downloader, options); + cacheManager.originalToCachedModuleDescriptor(resolver1, mdRef11, artifact11, rmr11, writer); + // and use the new overload that passes in resolver name + cacheManager.saveResolvedRevision("resolver1", mridLatest, "1.1"); + + ResolvedModuleRevision rmrFromCache = cacheManager.findModuleInCache(ddLatest, mridLatest, options, "resolver1"); + assertEquals(rmr11, rmrFromCache); + } + + protected static DefaultArtifact createArtifact(String org, String module, String rev, String name, String type, String ext) { ModuleId mid = new ModuleId(org, module); ModuleRevisionId mrid = new ModuleRevisionId(mid, rev);
