Skip to content

Commit

Permalink
fix(dev-mode): live reload failing with Zip archives generated by cod…
Browse files Browse the repository at this point in the history
…e.quarkus.io due to time zone issue

this fixes the issue by implementing a solution proposed by this comment
quarkusio#4064 (comment) and just check if last
recorded time and current modification time are different.
  • Loading branch information
machi1990 committed Sep 22, 2019
1 parent 22cb53a commit 778b5a6
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,33 @@

public class RuntimeUpdatesProcessor implements HotReplacementContext {
private static final String CLASS_EXTENSION = ".class";
private static final Logger log = Logger.getLogger(RuntimeUpdatesProcessor.class.getPackage().getName());

private final DevModeContext context;
private final ClassLoaderCompiler compiler;
private volatile long lastChange = System.currentTimeMillis();

// file path -> isRestartNeeded
private volatile Map<String, Boolean> watchedFilePaths = Collections.emptyMap();

/**
* A first scan is considered done when we have visited all modules at least once.
* This is useful in two ways.
* - To make sure that source time stamps have been recorded at least once
* - To avoid re-compiling on first run by ignoring all first time changes detected by
* {@link RuntimeUpdatesProcessor#checkIfFileModified(Path, Map)} during the first scan.
*/
private volatile boolean firstScanDone = false;

private final Map<Path, Long> sourceFileTimestamps = new ConcurrentHashMap<>();
private final Map<Path, Long> watchedFileTimestamps = new ConcurrentHashMap<>();
private final Map<Path, Long> classFileChangeTimestamps = new ConcurrentHashMap<>();
private final Map<Path, Path> classFilePathToSourceFilePath = new ConcurrentHashMap<>();

/**
* resources that appear in both src and target, these will be removed if the src resource subsequently disappears
*
* Resources that appear in both src and target, these will be removed if the src resource subsequently disappears.
* This set contains the paths in the target dir
*/
private final Set<Path> correspondingResources = Collections.newSetFromMap(new ConcurrentHashMap<>());

private static final Logger log = Logger.getLogger(RuntimeUpdatesProcessor.class.getPackage().getName());
private final List<Runnable> preScanSteps = new CopyOnWriteArrayList<>();
private final List<Consumer<Set<String>>> noRestartChangesConsumers = new CopyOnWriteArrayList<>();
private final List<HotReplacementSetup> hotReplacementSetup = new ArrayList<>();
Expand Down Expand Up @@ -148,14 +159,17 @@ public void consumeNoRestartChanges(Consumer<Set<String>> consumer) {

boolean checkForChangedClasses() throws IOException {
boolean hasChanges = false;

for (DevModeContext.ModuleInfo module : context.getModules()) {
final List<Path> moduleChangedSourceFilePaths = new ArrayList<>();

for (String sourcePath : module.getSourcePaths()) {
final Set<File> changedSourceFiles;
try (final Stream<Path> sourcesStream = Files.walk(Paths.get(sourcePath))) {
changedSourceFiles = sourcesStream
.parallel()
.filter(p -> matchingHandledExtension(p).isPresent() && wasRecentlyModified(p))
.filter(path -> matchingHandledExtension(path).isPresent()
&& sourceFileWasRecentModified(path))
.map(Path::toFile)
//Needing a concurrent Set, not many standard options:
.collect(Collectors.toCollection(ConcurrentSkipListSet::new));
Expand Down Expand Up @@ -183,10 +197,7 @@ boolean checkForChangedClasses() throws IOException {
}
}

if (hasChanges) {
lastChange = System.currentTimeMillis();
}

this.firstScanDone = true;
return hasChanges;
}

Expand All @@ -209,27 +220,26 @@ private boolean checkForClassFilesChangesInModule(DevModeContext.ModuleInfo modu
for (Path classFilePath : classFilePaths) {
final Path sourceFilePath = retrieveSourceFilePathForClassFile(classFilePath, moduleChangedSourceFiles,
module);
final long classFileModificationTime = Files.getLastModifiedTime(classFilePath).toMillis();

if (sourceFilePath != null) {
if (!sourceFilePath.toFile().exists()) {
// Source file has been deleted. Delete class and restart
Files.deleteIfExists(classFilePath);
classFilePathToSourceFilePath.remove(classFilePath);
cleanUpClassFile(classFilePath);
sourceFileTimestamps.remove(sourceFilePath);
hasChanges = true;
} else {
classFilePathToSourceFilePath.put(classFilePath, sourceFilePath);
if (classFileModificationTime > lastChange) {
if (classFileWasRecentModified(classFilePath)) {
// At least one class was recently modified. Restart.
hasChanges = true;
} else if (moduleChangedSourceFiles.contains(sourceFilePath)) {
// Source file has been modified, we delete the .class files as they are going to
//be recompiled anyway, this allows for simple cleanup of inner classes
Files.deleteIfExists(classFilePath);
classFilePathToSourceFilePath.remove(classFilePath);
// be recompiled anyway, this allows for simple cleanup of inner classes
cleanUpClassFile(classFilePath);
hasChanges = true;
}
}
} else if (classFileModificationTime > lastChange) {
} else if (classFileWasRecentModified(classFilePath)) {
hasChanges = true;
}
}
Expand All @@ -251,6 +261,12 @@ private Path retrieveSourceFilePathForClassFile(Path classFilePath, List<Path> m
return sourceFilePath;
}

private void cleanUpClassFile(Path classFilePath) throws IOException {
Files.deleteIfExists(classFilePath);
classFileChangeTimestamps.remove(classFilePath);
classFilePathToSourceFilePath.remove(classFilePath);
}

private Optional<String> matchingHandledExtension(Path p) {
return compiler.allHandledExtensions().stream().filter(e -> p.toString().endsWith(e)).findFirst();
}
Expand Down Expand Up @@ -356,10 +372,34 @@ public void accept(Path path) {
return ret;
}

private boolean wasRecentlyModified(final Path p) {
private boolean sourceFileWasRecentModified(final Path sourcePath) {
return checkIfFileModified(sourcePath, sourceFileTimestamps);
}

private boolean classFileWasRecentModified(final Path classFilePath) {
return checkIfFileModified(classFilePath, classFileChangeTimestamps);
}

private boolean checkIfFileModified(Path path, Map<Path, Long> pathModificationTimes) {
try {
long sourceMod = Files.getLastModifiedTime(p).toMillis();
return sourceMod > lastChange;
final long lastModificationTime = Files.getLastModifiedTime(path).toMillis();
final Long lastRecordedChange = pathModificationTimes.get(path);

if (lastRecordedChange == null) {
pathModificationTimes.put(path, lastModificationTime);
if (firstScanDone) {
return true;
}

return false; // ignore changes to avoid recompilation and re-start
}

if (lastRecordedChange != lastModificationTime) {
pathModificationTimes.put(path, lastModificationTime);
return true;
}

return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ public void testThatNewBeanAreDiscovered() throws IOException, MavenInvocationEx
"}";
FileUtils.write(source, content, "UTF-8");

// Update the resource ot use the bean
// Update the resource to use the bean
File resource = new File(testDir, "src/main/java/org/acme/HelloResource.java");
filter(resource, Collections.singletonMap("String greeting;", "String greeting;\n @Inject MyBean bean;"));
filter(resource, Collections.singletonMap("\"hello\"", "bean.get()"));
Expand Down

0 comments on commit 778b5a6

Please sign in to comment.