FilesDelta data synchronization made more explicit;

some files could have been incorrectly marked as up-to-date: now comparing file timestamp against build_target compilation start, instead of global adjustable compilation start stamp.
This commit is contained in:
Eugene Zhuravlev
2015-07-06 13:29:14 +02:00
parent 820ce7170a
commit aa2ad1db58
9 changed files with 289 additions and 216 deletions
@@ -210,7 +210,7 @@ public class BuildRunner {
}
fileSet.add(file);
if (targetTypesToForceBuild.contains(descriptor.getTarget().getTargetType())) {
pd.fsState.markDirty(null, file, descriptor, timestamps, false);
pd.fsState.markDirty(null, file, descriptor, timestamps);
}
}
}
@@ -253,7 +253,7 @@ final class BuildSession implements Runnable, CanceledStatus {
else {
// apply events to already loaded state
try {
applyFSEvent(pd, myInitialFSDelta, false);
applyFSEvent(pd, myInitialFSDelta);
}
catch (Throwable e) {
LOG.error(e);
@@ -269,7 +269,7 @@ final class BuildSession implements Runnable, CanceledStatus {
try {
try {
fsState.load(fsStateStream, pd.getModel(), pd.getBuildRootIndex());
applyFSEvent(pd, myInitialFSDelta, false);
applyFSEvent(pd, myInitialFSDelta);
TimingLog.LOG.debug("FS Delta loaded");
}
finally {
@@ -349,7 +349,7 @@ final class BuildSession implements Runnable, CanceledStatus {
@Override
public void run() {
try {
applyFSEvent(myProjectDescriptor, event, true);
applyFSEvent(myProjectDescriptor, event);
myLastEventOrdinal += 1;
}
catch (IOException e) {
@@ -378,8 +378,7 @@ final class BuildSession implements Runnable, CanceledStatus {
}
}
private static void applyFSEvent(ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event,
final boolean saveEventStamp) throws IOException {
private static void applyFSEvent(ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event) throws IOException {
if (event == null) {
return;
}
@@ -426,7 +425,7 @@ final class BuildSession implements Runnable, CanceledStatus {
pd.getFSCache().clear();
cacheCleared = true;
}
pd.fsState.markDirty(null, file, descriptor, timestamps, saveEventStamp);
pd.fsState.markDirty(null, file, descriptor, timestamps);
}
else {
if (LOG.isDebugEnabled()) {
@@ -47,7 +47,7 @@ class ChainedTargetsBuildListener implements BuildListener {
Collection<BuildRootDescriptor> descriptors = rootsIndex.findAllParentDescriptors(file, null, myContext);
for (BuildRootDescriptor descriptor : descriptors) {
try {
fsState.markDirty(myContext, file, descriptor, myContext.getProjectDescriptor().timestamps.getStorage(), false);
fsState.markDirty(myContext, file, descriptor, myContext.getProjectDescriptor().timestamps.getStorage());
}
catch (IOException ignored) {
}
@@ -19,9 +19,12 @@ import com.intellij.openapi.util.UserDataHolder;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.api.CanceledStatus;
import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.logging.BuildLoggingManager;
import org.jetbrains.jps.cmdline.ProjectDescriptor;
import java.util.Collection;
/**
* @author Eugene Zhuravlev
* Date: 7/8/12
@@ -58,9 +61,9 @@ public interface CompileContext extends UserDataHolder, MessageHandler {
void setDone(float done);
long getCompilationStartStamp();
long getCompilationStartStamp(BuildTarget<?> target);
void updateCompilationStartStamp();
void setCompilationStartStamp(Collection<BuildTarget<?>> target, long stamp);
void markNonIncremental(ModuleBuildTarget target);
@@ -18,9 +18,11 @@ package org.jetbrains.jps.incremental;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.util.EventDispatcher;
import gnu.trove.TObjectLongHashMap;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.api.CanceledStatus;
import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.java.JavaBuilderUtil;
import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
import org.jetbrains.jps.builders.logging.BuildLoggingManager;
@@ -42,7 +44,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
private final MessageHandler myDelegateMessageHandler;
private final Set<ModuleBuildTarget> myNonIncrementalModules = new HashSet<ModuleBuildTarget>();
private volatile long myCompilationStartStamp;
private final TObjectLongHashMap<BuildTarget<?>> myCompilationStartStamp = new TObjectLongHashMap<BuildTarget<?>>();
private final ProjectDescriptor myProjectDescriptor;
private final Map<String, String> myBuilderParams;
private final CanceledStatus myCancelStatus;
@@ -57,19 +59,23 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
myProjectDescriptor = pd;
myBuilderParams = Collections.unmodifiableMap(builderParams);
myCancelStatus = cancelStatus;
myCompilationStartStamp = System.currentTimeMillis();
myScope = scope;
myDelegateMessageHandler = delegateMessageHandler;
}
// todo: add timestamp-setting code
@Override
public long getCompilationStartStamp() {
return myCompilationStartStamp;
public long getCompilationStartStamp(BuildTarget<?> target) {
synchronized (myCompilationStartStamp) {
return myCompilationStartStamp.get(target);
}
}
@Override
public void updateCompilationStartStamp() {
myCompilationStartStamp = System.currentTimeMillis();
public void setCompilationStartStamp(Collection<BuildTarget<?>> targets, long stamp) {
synchronized (myCompilationStartStamp) {
for (BuildTarget<?> target : targets) {
myCompilationStartStamp.put(target, stamp);
}
}
}
@Override
@@ -82,7 +82,7 @@ public class FSOperations {
final JavaSourceRootDescriptor rd = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context, file);
if (rd != null) {
final ProjectDescriptor pd = context.getProjectDescriptor();
pd.fsState.markDirty(context, round, file, rd, pd.timestamps.getStorage(), false);
pd.fsState.markDirty(context, round, file, rd, pd.timestamps.getStorage());
}
}
@@ -245,7 +245,7 @@ public class FSOperations {
// if it is full project rebuild, all storages are already completely cleared;
// so passing null because there is no need to access the storage to clear non-existing data
final Timestamps marker = context.isProjectRebuild() ? null : tsStorage;
context.getProjectDescriptor().fsState.markDirty(context, round, file, rd, marker, false);
context.getProjectDescriptor().fsState.markDirty(context, round, file, rd, marker);
}
if (currentFiles != null) {
currentFiles.add(file);
@@ -47,6 +47,7 @@ import org.jetbrains.jps.cmdline.BuildRunner;
import org.jetbrains.jps.cmdline.ProjectDescriptor;
import org.jetbrains.jps.incremental.fs.BuildFSState;
import org.jetbrains.jps.incremental.fs.CompilationRound;
import org.jetbrains.jps.incremental.fs.FilesDelta;
import org.jetbrains.jps.incremental.messages.*;
import org.jetbrains.jps.incremental.storage.BuildTargetConfiguration;
import org.jetbrains.jps.incremental.storage.OneToManyPathsMapping;
@@ -60,9 +61,7 @@ import org.jetbrains.jps.model.module.JpsModule;
import org.jetbrains.jps.service.SharedThreadPool;
import org.jetbrains.jps.util.JpsPathUtil;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@@ -82,7 +81,7 @@ public class IncProjectBuilder {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.IncProjectBuilder");
private static final String CLASSPATH_INDEX_FILE_NAME = "classpath.index";
private static final boolean GENERATE_CLASSPATH_INDEX = Boolean.parseBoolean(System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION, "false"));
//private static final boolean GENERATE_CLASSPATH_INDEX = Boolean.parseBoolean(System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION, "false"));
private static final boolean SYNC_DELETE = Boolean.parseBoolean(System.getProperty("jps.sync.delete", SystemInfo.isWindows ? "true" : "false"));
private static final GlobalContextKey<Set<BuildTarget<?>>> TARGET_WITH_CLEARED_OUTPUT = GlobalContextKey.create("_targets_with_cleared_output_");
public static final int MAX_BUILDER_THREADS;
@@ -143,10 +142,10 @@ public class IncProjectBuilder {
for (BuildTarget<?> target : myProjectDescriptor.getBuildTargetIndex().getAllTargets()) {
if (scope.isAffected(target)) {
BuildOperations.ensureFSStateInitialized(context, target);
final Map<BuildRootDescriptor, Set<File>> toRecompile = fsState.getSourcesToRecompile(context, target);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (toRecompile) {
for (Set<File> files : toRecompile.values()) {
final FilesDelta delta = fsState.getEffectiveFilesDelta(context, target);
delta.lockData();
try {
for (Set<File> files : delta.getSourcesToRecompile().values()) {
for (File file : files) {
if (scope.isAffected(target, file)) {
// this will serve as a marker that compiler has work to do
@@ -156,6 +155,9 @@ public class IncProjectBuilder {
}
}
}
finally {
delta.unlockData();
}
}
}
}
@@ -663,7 +665,6 @@ public class IncProjectBuilder {
buildChunkIfAffected(context, scope, chunk);
}
finally {
context.updateCompilationStartStamp();
pd.dataManager.closeSourceToOutputStorages(Collections.singleton(chunk));
pd.dataManager.flush(true);
}
@@ -810,7 +811,6 @@ public class IncProjectBuilder {
}
}
finally {
myContext.updateCompilationStartStamp();
myProjectDescriptor.dataManager.closeSourceToOutputStorages(Collections.singletonList(task.getChunk()));
myProjectDescriptor.dataManager.flush(true);
}
@@ -929,8 +929,11 @@ public class IncProjectBuilder {
}
private void buildTargetsChunk(CompileContext context, final BuildTargetChunk chunk) throws ProjectBuildException {
final BuildFSState fsState = myProjectDescriptor.fsState;
boolean doneSomething;
try {
context.setCompilationStartStamp(chunk.getTargets(), System.currentTimeMillis());
sendBuildingTargetMessages(chunk.getTargets(), BuildingTargetProgressMessage.Event.STARTED);
Utils.ERRORS_DETECTED_KEY.set(context, Boolean.FALSE);
@@ -940,11 +943,14 @@ public class IncProjectBuilder {
doneSomething = processDeletedPaths(context, chunk.getTargets());
myProjectDescriptor.fsState.beforeChunkBuildStart(context, chunk);
fsState.beforeChunkBuildStart(context, chunk);
doneSomething |= runBuildersForChunk(context, chunk);
onChunkBuildComplete(context, chunk);
fsState.clearContextRoundData(context);
fsState.clearContextChunk(context);
BuildOperations.markTargetsUpToDate(context, chunk);
//if (doneSomething && GENERATE_CLASSPATH_INDEX) {
// myAsyncTasks.add(SharedThreadPool.getInstance().executeOnPooledThread(new Runnable() {
@@ -983,7 +989,7 @@ public class IncProjectBuilder {
final Collection<String> paths = entry.getValue();
if (paths != null) {
for (String path : paths) {
myProjectDescriptor.fsState.registerDeleted(target, new File(path), null);
fsState.registerDeleted(target, new File(path), null);
}
}
}
@@ -1004,40 +1010,40 @@ public class IncProjectBuilder {
myMessageDispatcher.processMessage(new BuildingTargetProgressMessage(targets, event));
}
private static void createClasspathIndex(final BuildTargetChunk chunk) {
final Set<File> outputDirs = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
for (BuildTarget<?> target : chunk.getTargets()) {
if (target instanceof ModuleBuildTarget) {
File outputDir = ((ModuleBuildTarget)target).getOutputDir();
if (outputDir != null && outputDirs.add(outputDir)) {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(outputDir, CLASSPATH_INDEX_FILE_NAME)));
try {
writeIndex(writer, outputDir, "");
}
finally {
writer.close();
}
}
catch (IOException e) {
// Ignore. Failed to create optional classpath index
}
}
}
}
}
//private static void createClasspathIndex(final BuildTargetChunk chunk) {
// final Set<File> outputDirs = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
// for (BuildTarget<?> target : chunk.getTargets()) {
// if (target instanceof ModuleBuildTarget) {
// File outputDir = ((ModuleBuildTarget)target).getOutputDir();
// if (outputDir != null && outputDirs.add(outputDir)) {
// try {
// BufferedWriter writer = new BufferedWriter(new FileWriter(new File(outputDir, CLASSPATH_INDEX_FILE_NAME)));
// try {
// writeIndex(writer, outputDir, "");
// }
// finally {
// writer.close();
// }
// }
// catch (IOException e) {
// // Ignore. Failed to create optional classpath index
// }
// }
// }
// }
//}
private static void writeIndex(final BufferedWriter writer, final File file, final String path) throws IOException {
writer.write(path);
writer.write('\n');
final File[] files = file.listFiles();
if (files != null) {
for (File child : files) {
final String _path = path.isEmpty() ? child.getName() : path + "/" + child.getName();
writeIndex(writer, child, _path);
}
}
}
//private static void writeIndex(final BufferedWriter writer, final File file, final String path) throws IOException {
// writer.write(path);
// writer.write('\n');
// final File[] files = file.listFiles();
// if (files != null) {
// for (File child : files) {
// final String _path = path.isEmpty() ? child.getName() : path + "/" + child.getName();
// writeIndex(writer, child, _path);
// }
// }
//}
private boolean processDeletedPaths(CompileContext context, final Set<? extends BuildTarget<?>> targets) throws ProjectBuildException {
@@ -1286,15 +1292,6 @@ public class IncProjectBuilder {
}
}
private static void onChunkBuildComplete(CompileContext context, @NotNull BuildTargetChunk chunk) throws IOException {
final ProjectDescriptor pd = context.getProjectDescriptor();
final BuildFSState fsState = pd.fsState;
fsState.clearContextRoundData(context);
fsState.clearContextChunk(context);
BuildOperations.markTargetsUpToDate(context, chunk);
}
private static CompileContext createContextWrapper(final CompileContext delegate) {
final ClassLoader loader = delegate.getClass().getClassLoader();
final UserDataHolderBase localDataHolder = new UserDataHolderBase();
@@ -18,10 +18,8 @@ package org.jetbrains.jps.incremental.fs;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.io.IOUtil;
import gnu.trove.TObjectLongHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.ModuleChunk;
@@ -52,7 +50,6 @@ public class BuildFSState {
// alternatively, when false, after first scan will rely on external notifications about changes
private final boolean myAlwaysScanFS;
private final Set<BuildTarget<?>> myInitialScanPerformed = Collections.synchronizedSet(new HashSet<BuildTarget<?>>());
private final TObjectLongHashMap<File> myRegistrationStamps = new TObjectLongHashMap<File>(FileUtil.FILE_HASHING_STRATEGY);
private final Map<BuildTarget<?>, FilesDelta> myDeltas = Collections.synchronizedMap(new HashMap<BuildTarget<?>, FilesDelta>());
public BuildFSState(boolean alwaysScanFS) {
@@ -107,10 +104,6 @@ public class BuildFSState {
getDelta(rd.getTarget()).clearRecompile(rd);
}
public long getEventRegistrationStamp(File file) {
return myRegistrationStamps.get(file);
}
public boolean hasWorkToDo(BuildTarget<?> target) {
if (!myInitialScanPerformed.contains(target)) {
return true;
@@ -166,15 +159,16 @@ public class BuildFSState {
return !myAlwaysScanFS && myInitialScanPerformed.contains(target);
}
public Map<BuildRootDescriptor, Set<File>> getSourcesToRecompile(@NotNull CompileContext context, BuildTarget<?> target) {
@NotNull
public FilesDelta getEffectiveFilesDelta(@NotNull CompileContext context, BuildTarget<?> target) {
if (target instanceof ModuleBuildTarget) {
// multiple compilation rounds are applicable to ModuleBuildTarget only
final FilesDelta lastRoundDelta = getRoundDelta(CURRENT_ROUND_DELTA_KEY, context);
if (lastRoundDelta != null) {
return lastRoundDelta.getSourcesToRecompile();
return lastRoundDelta;
}
}
return getDelta(target).getSourcesToRecompile();
return getDelta(target);
}
public boolean isMarkedForRecompilation(@Nullable CompileContext context, CompilationRound round, BuildRootDescriptor rd, File file) {
@@ -183,46 +177,45 @@ public class BuildFSState {
delta = getDelta(rd.getTarget());
}
final Map<BuildRootDescriptor, Set<File>> recompile = delta.getSourcesToRecompile();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (recompile) {
final Set<File> files = recompile.get(rd);
return files != null && files.contains(file);
}
return delta.isMarkedRecompile(rd, file);
}
/**
* Note: marked file will well be visible as "dirty" only on the next compilation round!
* @throws IOException
*/
public final boolean markDirty(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage, boolean saveEventStamp) throws IOException {
return markDirty(context, CompilationRound.NEXT, file, rd, tsStorage, saveEventStamp);
public final boolean markDirty(@Nullable CompileContext context, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage) throws IOException {
return markDirty(context, CompilationRound.NEXT, file, rd, tsStorage);
}
public boolean markDirty(@Nullable CompileContext context, CompilationRound round, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage, boolean saveEventStamp) throws IOException {
public boolean markDirty(@Nullable CompileContext context, CompilationRound round, File file, final BuildRootDescriptor rd, @Nullable Timestamps tsStorage) throws IOException {
final FilesDelta roundDelta = getRoundDelta(round == CompilationRound.NEXT? NEXT_ROUND_DELTA_KEY : CURRENT_ROUND_DELTA_KEY, context);
if (roundDelta != null && isInCurrentContextTargets(context, rd)) {
roundDelta.markRecompile(rd, file);
}
final boolean marked = getDelta(rd.getTarget()).markRecompile(rd, file);
if (marked) {
if (LOG.isDebugEnabled()) {
LOG.debug(rd.getTarget() + ": MARKED DIRTY: " + file.getPath());
final FilesDelta filesDelta = getDelta(rd.getTarget());
filesDelta.lockData();
try {
final boolean marked = filesDelta.markRecompile(rd, file);
if (marked) {
if (LOG.isDebugEnabled()) {
LOG.debug(rd.getTarget() + ": MARKED DIRTY: " + file.getPath());
}
if (tsStorage != null) {
tsStorage.removeStamp(file, rd.getTarget());
}
}
if (saveEventStamp) {
myRegistrationStamps.put(file, System.currentTimeMillis());
}
if (tsStorage != null) {
tsStorage.removeStamp(file, rd.getTarget());
else {
if (LOG.isDebugEnabled()) {
LOG.debug(rd.getTarget() + ": NOT MARKED DIRTY: " + file.getPath());
}
}
return marked;
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug(rd.getTarget() + ": NOT MARKED DIRTY: " + file.getPath());
}
finally {
filesDelta.unlockData();
}
return marked;
}
private static boolean isInCurrentContextTargets(CompileContext context, BuildRootDescriptor rd) {
@@ -254,7 +247,6 @@ public class BuildFSState {
clearContextChunk(null);
myInitialScanPerformed.clear();
myDeltas.clear();
myRegistrationStamps.clear();
}
public void clearContextRoundData(@Nullable CompileContext context) {
@@ -286,10 +278,11 @@ public class BuildFSState {
}
public <R extends BuildRootDescriptor, T extends BuildTarget<R>> boolean processFilesToRecompile(CompileContext context, final @NotNull T target, final FileProcessor<R, T> processor) throws IOException {
final Map<BuildRootDescriptor, Set<File>> data = getSourcesToRecompile(context, target);
final CompileScope scope = context.getScope();
synchronized (data) {
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : data.entrySet()) {
final FilesDelta delta = getEffectiveFilesDelta(context, target);
delta.lockData();
try {
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : delta.getSourcesToRecompile().entrySet()) {
//noinspection unchecked
R root = (R)entry.getKey();
if (!target.equals(root.getTarget())) {
@@ -305,8 +298,11 @@ public class BuildFSState {
}
}
}
return true;
}
finally {
delta.unlockData();
}
return true;
}
/**
@@ -314,36 +310,44 @@ public class BuildFSState {
*/
public boolean markAllUpToDate(CompileContext context, final BuildRootDescriptor rd, final Timestamps stamps) throws IOException {
boolean marked = false;
final FilesDelta delta = getDelta(rd.getTarget());
final Set<File> files = delta.clearRecompile(rd);
if (files != null) {
CompileScope scope = context.getScope();
final long compilationStartStamp = context.getCompilationStartStamp();
for (File file : files) {
if (scope.isAffected(rd.getTarget(), file)) {
final long currentFileStamp = FileSystemUtil.lastModified(file);
if (!rd.isGenerated() && (currentFileStamp > compilationStartStamp || getEventRegistrationStamp(file) > compilationStartStamp)) {
// if the file was modified after the compilation had started,
// do not save the stamp considering file dirty
final BuildTarget<?> target = rd.getTarget();
final FilesDelta delta = getDelta(target);
final long targetBuildStartStamp = context.getCompilationStartStamp(target);
// prevent modifications to the data structure from external FS events
delta.lockData();
try {
final Set<File> files = delta.clearRecompile(rd);
if (files != null) {
CompileScope scope = context.getScope();
for (File file : files) {
if (scope.isAffected(target, file)) {
final long currentFileStamp = FileSystemUtil.lastModified(file);
if (!rd.isGenerated() && currentFileStamp > targetBuildStartStamp) {
// if the file was modified after the compilation had started,
// do not save the stamp considering file dirty
if (Utils.IS_TEST_MODE) {
LOG.info("Timestamp after compilation started; marking dirty again: " + file.getPath());
}
delta.markRecompile(rd, file);
}
else {
marked = true;
stamps.saveStamp(file, target, currentFileStamp);
}
}
else {
if (Utils.IS_TEST_MODE) {
LOG.info("Timestamp after compilation started; marking dirty again: " + file.getPath());
LOG.info("Not affected by compile scope; marking dirty again: " + file.getPath());
}
delta.markRecompile(rd, file);
}
else {
marked = true;
stamps.saveStamp(file, rd.getTarget(), currentFileStamp);
}
}
else {
if (Utils.IS_TEST_MODE) {
LOG.info("Not affected by compile scope; marking dirty again: " + file.getPath());
}
delta.markRecompile(rd, file);
}
}
return marked;
}
finally {
delta.unlockData();
}
return marked;
}
private static void setContextTargets(@Nullable CompileContext context, @Nullable Set<? extends BuildTarget<?>> targets) {
@@ -31,32 +31,50 @@ import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/** @noinspection SynchronizationOnLocalVariableOrMethodParameter*/
final class FilesDelta {
public final class FilesDelta {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.fs.FilesDelta");
private final ReentrantLock myDataLock = new ReentrantLock();
private final Set<String> myDeletedPaths = Collections.synchronizedSet(new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY));
private final Map<BuildRootDescriptor, Set<File>> myFilesToRecompile = Collections.synchronizedMap(new HashMap<BuildRootDescriptor, Set<File>>());
private final Set<String> myDeletedPaths = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
private final Map<BuildRootDescriptor, Set<File>> myFilesToRecompile = new HashMap<BuildRootDescriptor, Set<File>>();
public void lockData(){
myDataLock.lock();
}
public void unlockData(){
myDataLock.unlock();
}
protected void addAll(FilesDelta other) {
myDeletedPaths.addAll(other.myDeletedPaths);
final Map<BuildRootDescriptor, Set<File>> dataToAdd = other.myFilesToRecompile;
synchronized (dataToAdd) {
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : dataToAdd.entrySet()) {
_addToRecompiled(entry.getKey(), entry.getValue());
lockData();
try {
other.lockData();
try {
myDeletedPaths.addAll(other.myDeletedPaths);
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : other.myFilesToRecompile.entrySet()) {
_addToRecompiled(entry.getKey(), entry.getValue());
}
}
finally {
other.unlockData();
}
}
finally {
unlockData();
}
}
public void save(DataOutput out) throws IOException {
out.writeInt(myDeletedPaths.size());
synchronized (myDeletedPaths) {
lockData();
try {
out.writeInt(myDeletedPaths.size());
for (String path : myDeletedPaths) {
IOUtil.writeString(path, out);
}
}
synchronized (myFilesToRecompile) {
out.writeInt(myFilesToRecompile.size());
for (Map.Entry<BuildRootDescriptor, Set<File>> entry : myFilesToRecompile.entrySet()) {
IOUtil.writeString(entry.getKey().getRootId(), out);
@@ -67,39 +85,48 @@ final class FilesDelta {
}
}
}
finally {
unlockData();
}
}
public void load(DataInput in, @NotNull BuildTarget<?> target, BuildRootIndex buildRootIndex) throws IOException {
myDeletedPaths.clear();
int deletedCount = in.readInt();
while (deletedCount-- > 0) {
myDeletedPaths.add(IOUtil.readString(in));
}
myFilesToRecompile.clear();
int recompileCount = in.readInt();
while (recompileCount-- > 0) {
String rootId = IOUtil.readString(in);
BuildRootDescriptor descriptor = target.findRootDescriptor(rootId, buildRootIndex);
Set<File> files;
if (descriptor != null) {
files = myFilesToRecompile.get(descriptor);
if (files == null) {
lockData();
try {
myDeletedPaths.clear();
int deletedCount = in.readInt();
while (deletedCount-- > 0) {
myDeletedPaths.add(IOUtil.readString(in));
}
myFilesToRecompile.clear();
int recompileCount = in.readInt();
while (recompileCount-- > 0) {
String rootId = IOUtil.readString(in);
BuildRootDescriptor descriptor = target.findRootDescriptor(rootId, buildRootIndex);
Set<File> files;
if (descriptor != null) {
files = myFilesToRecompile.get(descriptor);
if (files == null) {
files = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
myFilesToRecompile.put(descriptor, files);
}
}
else {
LOG.debug("Cannot find root by " + rootId + ", delta will be skipped");
files = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
myFilesToRecompile.put(descriptor, files);
}
int filesCount = in.readInt();
while (filesCount-- > 0) {
final File file = new File(IOUtil.readString(in));
if (Utils.IS_TEST_MODE) {
LOG.info("Loaded " + file.getPath());
}
files.add(file);
}
}
else {
LOG.debug("Cannot find root by " + rootId + ", delta will be skipped");
files = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
}
int filesCount = in.readInt();
while (filesCount-- > 0) {
final File file = new File(IOUtil.readString(in));
if (Utils.IS_TEST_MODE) {
LOG.info("Loaded " + file.getPath());
}
files.add(file);
}
}
finally {
unlockData();
}
}
@@ -119,32 +146,55 @@ final class FilesDelta {
}
public boolean hasChanges() {
return hasPathsToDelete() || hasSourcesToRecompile();
lockData();
try {
if (!myDeletedPaths.isEmpty()) {
return true;
}
if(!myFilesToRecompile.isEmpty()) {
for (Set<File> files : myFilesToRecompile.values()) {
if (!files.isEmpty()) {
return true;
}
}
}
return false;
}
finally {
unlockData();
}
}
public boolean markRecompile(BuildRootDescriptor root, File file) {
final boolean added = _addToRecompiled(root, file);
if (added) {
synchronized (myDeletedPaths) {
lockData();
try {
final boolean added = _addToRecompiled(root, file);
if (added) {
if (!myDeletedPaths.isEmpty()) { // optimization
myDeletedPaths.remove(FileUtil.toCanonicalPath(file.getPath()));
}
}
return added;
}
finally {
unlockData();
}
return added;
}
public boolean markRecompileIfNotDeleted(BuildRootDescriptor root, File file) {
final boolean isMarkedDeleted;
synchronized (myDeletedPaths) {
isMarkedDeleted = !myDeletedPaths.isEmpty() && myDeletedPaths.contains(FileUtil.toCanonicalPath(file.getPath()));
lockData();
try {
final boolean isMarkedDeleted = !myDeletedPaths.isEmpty() && myDeletedPaths.contains(FileUtil.toCanonicalPath(file.getPath()));
if (!isMarkedDeleted) {
_addToRecompiled(root, file);
return true;
}
return false;
}
if (!isMarkedDeleted) {
_addToRecompiled(root, file);
return true;
finally {
unlockData();
}
return false;
}
private boolean _addToRecompiled(BuildRootDescriptor root, File file) {
@@ -155,36 +205,45 @@ final class FilesDelta {
}
private boolean _addToRecompiled(BuildRootDescriptor root, Collection<File> filesToAdd) {
synchronized (myFilesToRecompile) {
Set<File> files = myFilesToRecompile.get(root);
if (files == null) {
files = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
myFilesToRecompile.put(root, files);
}
return files.addAll(filesToAdd);
Set<File> files = myFilesToRecompile.get(root);
if (files == null) {
files = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
myFilesToRecompile.put(root, files);
}
return files.addAll(filesToAdd);
}
public void addDeleted(File file) {
// ensure the file is no more marked to recompilation
synchronized (myFilesToRecompile) {
final String path = FileUtil.toCanonicalPath(file.getPath());
lockData();
try {
// ensure the file is not marked to recompilation anymore
for (Set<File> files : myFilesToRecompile.values()) {
files.remove(file);
}
myDeletedPaths.add(path);
if (Utils.IS_TEST_MODE) {
LOG.info("Marking deleted: " + path);
}
}
final String path = FileUtil.toCanonicalPath(file.getPath());
myDeletedPaths.add(path);
if (Utils.IS_TEST_MODE) {
LOG.info("Marking deleted: " + path);
finally {
unlockData();
}
}
public void clearDeletedPaths() {
myDeletedPaths.clear();
lockData();
try {
myDeletedPaths.clear();
}
finally {
unlockData();
}
}
public Set<String> getAndClearDeletedPaths() {
synchronized (myDeletedPaths) {
lockData();
try {
try {
final THashSet<String> _paths = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
_paths.addAll(myDeletedPaths);
@@ -194,31 +253,36 @@ final class FilesDelta {
myDeletedPaths.clear();
}
}
finally {
unlockData();
}
}
@NotNull
public Map<BuildRootDescriptor, Set<File>> getSourcesToRecompile() {
LOG.assertTrue(myDataLock.isHeldByCurrentThread(), "FilesDelta data must be locked by querying thread");
return myFilesToRecompile;
}
private boolean hasSourcesToRecompile() {
synchronized (myFilesToRecompile) {
if(!myFilesToRecompile.isEmpty()) {
for (Set<File> files : myFilesToRecompile.values()) {
if (!files.isEmpty()) {
return true;
}
}
}
public boolean isMarkedRecompile(BuildRootDescriptor rd, File file) {
lockData();
try {
final Set<File> files = myFilesToRecompile.get(rd);
return files != null && files.contains(file);
}
finally {
unlockData();
}
return false;
}
private boolean hasPathsToDelete() {
return !myDeletedPaths.isEmpty();
}
@Nullable
public Set<File> clearRecompile(BuildRootDescriptor root) {
return myFilesToRecompile.remove(root);
lockData();
try {
return myFilesToRecompile.remove(root);
}
finally {
unlockData();
}
}
}