Skip to content

Commit

Permalink
The caches are not all cleaned when deleting the local repository, fixes
Browse files Browse the repository at this point in the history
 #312

* The caches are not all cleaned when deleting the local repository, fixes #312
* Try to workaround file locking on windows
* Split the integration test
* Raise watch service sensitivity, add a delay in the test for windows to make sure the poll has been done
* Disable test on windows because all jar files are locked and the repository can't even be deleted
  • Loading branch information
gnodet authored Jan 13, 2021
2 parents 123ee63 + 8d92988 commit cee0ba6
Show file tree
Hide file tree
Showing 22 changed files with 1,305 additions and 532 deletions.
12 changes: 12 additions & 0 deletions daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@
import org.apache.maven.extension.internal.CoreExtensionEntry;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.PluginArtifactsCache;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactsCache;
import org.apache.maven.properties.internal.EnvironmentUtils;
import org.apache.maven.properties.internal.SystemProperties;
import org.apache.maven.session.scope.internal.SessionScopeModule;
Expand All @@ -89,6 +93,10 @@
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.transfer.TransferListener;
import org.mvndaemon.mvnd.cache.impl.CliExtensionRealmCache;
import org.mvndaemon.mvnd.cache.impl.CliPluginArtifactsCache;
import org.mvndaemon.mvnd.cache.impl.CliPluginRealmCache;
import org.mvndaemon.mvnd.cache.impl.CliProjectArtifactsCache;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
Expand Down Expand Up @@ -493,6 +501,10 @@ DefaultPlexusContainer container()
protected void configure() {
bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory);
bind(CoreExports.class).toInstance(exports);
bind(ExtensionRealmCache.class).to(CliExtensionRealmCache.class);
bind(PluginArtifactsCache.class).to(CliPluginArtifactsCache.class);
bind(PluginRealmCache.class).to(CliPluginRealmCache.class);
bind(ProjectArtifactsCache.class).to(CliProjectArtifactsCache.class);
}
});

Expand Down
56 changes: 56 additions & 0 deletions daemon/src/main/java/org/mvndaemon/mvnd/cache/factory/Cache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2021 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
*
* 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.mvndaemon.mvnd.cache.factory;

import java.util.function.BiPredicate;

/**
* Cache containing records that can be invalidated.
*
* Whenever the paths associated to a given {@link CacheRecord} have been modified,
* the record will be invalidated using {@link CacheRecord#invalidate()}.
*
* @param <K>
* @param <V>
*/
public interface Cache<K, V extends CacheRecord> {

/**
* Check if the cache contains the given key
*/
boolean contains(K key);

/**
* Get the cached record for the key
*/
V get(K key);

/**
* Put a record in the cache
*/
void put(K key, V value);

/**
* Remove all cached records
*/
void clear();

/**
* Remove cached records according to the predicate
*/
void removeIf(BiPredicate<K, V> predicate);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2021 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
*
* 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.mvndaemon.mvnd.cache.factory;

/**
* A factory for cache objects
*/
public interface CacheFactory {

<K, V extends CacheRecord> Cache<K, V> newCache();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2021 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
*
* 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.mvndaemon.mvnd.cache.factory;

import java.nio.file.Path;
import java.util.stream.Stream;

/**
* Data stored in a {@link Cache} which depends on the state
* of a few {@link Path}s.
*/
public interface CacheRecord {

/**
* A list of Path that will invalidate this record if modified.
*/
Stream<Path> getDependentPaths();

/**
* Callback called by the cache when this record
* is removed from the cache.
*/
void invalidate();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2021 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
*
* 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.mvndaemon.mvnd.cache.factory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.sisu.Priority;
import org.mvndaemon.mvnd.common.Os;

@Named
@Singleton
@Priority(10)
public class DefaultCacheFactory implements CacheFactory {

@Inject
WatchServiceCacheFactory watchServiceCacheFactory;
@Inject
TimestampCacheFactory timestampCacheFactory;

@Override
public <K, V extends CacheRecord> Cache<K, V> newCache() {
CacheFactory factory = Os.current() == Os.WINDOWS ? watchServiceCacheFactory : timestampCacheFactory;
return factory.newCache();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2021 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
*
* 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.mvndaemon.mvnd.cache.factory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import javax.inject.Named;
import javax.inject.Singleton;
import org.codehaus.plexus.logging.AbstractLogEnabled;

/**
* A factory for {@link Cache} objects.
*/
@Named
@Singleton
public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFactory {

public TimestampCacheFactory() {
}

@Override
public <K, V extends CacheRecord> Cache<K, V> newCache() {
return new DefaultCache<>();
}

static class ArtifactTimestamp {
final Path path;
final FileTime lastModifiedTime;
final Object fileKey;

ArtifactTimestamp(Path path) {
this.path = path;
try {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
this.lastModifiedTime = attrs.lastModifiedTime();
this.fileKey = attrs.fileKey();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ArtifactTimestamp that = (ArtifactTimestamp) o;
return path.equals(that.path) &&
Objects.equals(lastModifiedTime, that.lastModifiedTime) &&
Objects.equals(fileKey, that.fileKey);
}

@Override
public int hashCode() {
return Objects.hash(path, lastModifiedTime, fileKey);
}
}

static class Record<V extends CacheRecord> {
final V record;
final Set<ArtifactTimestamp> timestamp;

public Record(V record) {
this.record = record;
this.timestamp = current();
}

private Set<ArtifactTimestamp> current() {
return record.getDependentPaths()
.map(ArtifactTimestamp::new)
.collect(Collectors.toSet());
}
}

static class DefaultCache<K, V extends CacheRecord> implements Cache<K, V> {

private final ConcurrentHashMap<K, Record<V>> map = new ConcurrentHashMap<>();

@Override
public boolean contains(K key) {
return map.containsKey(key);
}

@Override
public V get(K key) {
Record<V> record = map.compute(key, (k, v) -> {
if (v != null) {
try {
if (Objects.equals(v.timestamp, v.current())) {
return v;
}
} catch (RuntimeException e) {
// ignore and invalidate the record
}
v.record.invalidate();
v = null;
}
return v;
});
return record != null ? record.record : null;
}

@Override
public void put(K key, V value) {
map.put(key, new Record<>(value));
}

@Override
public void clear() {
removeIf((k, v) -> true);
}

@Override
public void removeIf(BiPredicate<K, V> predicate) {
for (Iterator<Map.Entry<K, Record<V>>> iterator = map.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<K, Record<V>> entry = iterator.next();
if (predicate.test(entry.getKey(), entry.getValue().record)) {
entry.getValue().record.invalidate();
iterator.remove();
}
}
}

}
}
Loading

0 comments on commit cee0ba6

Please sign in to comment.