New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve live reload experience of external artifacts on dev mode #27372
Improve live reload experience of external artifacts on dev mode #27372
Conversation
Thanks for your pull request! The title of your pull request does not follow our editorial rules. Could you have a look?
|
This comment has been minimized.
This comment has been minimized.
00578d1
to
bcb13ac
Compare
This comment has been minimized.
This comment has been minimized.
@fercomunello there is a problem with the test. [1] https://github.com/aloubyansky/quarkus/tree/external-reloadable-modules |
@fercomunello according to my simple tests my branch appears to be compiling ~50ms faster. It'd be great if you could test it as well. |
Testing it more, the difference appears to be a bit less than ~50ms when there are reloadable external artifacts, however, it's ~35ms slower when there are no reloadable external artifacts. I'll look a bit more into this. |
bcb13ac
to
4bc5258
Compare
4bc5258
to
ecb79ef
Compare
There seem to be relevant test failures @fercomunello |
This comment has been minimized.
This comment has been minimized.
@aloubyansky I will have a closer look to know why The performance is a bit better now for traditional and non-traditional use-cases, take a look of the results before and after the changes. This is a custom |
@fercomunello if you make it a draft, the full CI won't run. You'd need to run the full CI in your fork to know whether it's good. |
It could be that |
Working on changes like this one, running tests in the |
34490f6
to
df8e04e
Compare
This comment has been minimized.
This comment has been minimized.
Add new extensible file manager implementations for the Java compilation provider, the main goal is to separate the static classpath from the reloadable one, this approach is more efficient than reopen the file manager before every compilation task, as only the reloadable part will be reset. Add integration tests of external reloadable artifacts feature for: Java, Kotlin & Scala. Fix: quarkusio#27242
df8e04e
to
00aa3b1
Compare
This test failed because of a small typing mistake in QuarkusCompiler.java class: // Before
if (application.getQuarkusBootstrap().getMode() != QuarkusBootstrap.Mode.TEST && module.getTest().isPresent()) { // ... }
// After
if (application.getQuarkusBootstrap().getMode() == QuarkusBootstrap.Mode.TEST && module.getTest().isPresent()) { // ... } The other test fails helped me to think more about the file manager implementation, so I did some refactoring and I think it's even better now :) Now, all the relevant tests passed 👍 |
public class ReloadableFileManager extends QuarkusFileManager { | ||
|
||
private final StandardJavaFileManager reloadableFileManager; | ||
|
||
public ReloadableFileManager(Supplier<StandardJavaFileManager> supplier, Context context) { | ||
this(supplier.get(), supplier.get(), context); | ||
} | ||
|
||
protected ReloadableFileManager(StandardJavaFileManager fileManager, | ||
StandardJavaFileManager reloadableFileManager, Context context) { | ||
super(fileManager, context); | ||
this.reloadableFileManager = reloadableFileManager; | ||
try { | ||
this.reloadableFileManager.setLocation(StandardLocation.CLASS_PATH, context.getReloadableClassPath()); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Cannot initialize reloadable file manager", e); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it more efficient to extend from ForwardingJavaFileManager than to implement the StandardJavaFileManager interface manually, the previous code of this class is on @aloubyansky branch
import io.quarkus.deployment.dev.filewatch.FileChangeCallback; | ||
import io.quarkus.deployment.dev.filewatch.FileChangeEvent; | ||
import io.quarkus.deployment.dev.filewatch.WatchServiceFileSystemWatcher; | ||
import io.quarkus.deployment.dev.filesystem.watch.FileChangeCallback; | ||
import io.quarkus.deployment.dev.filesystem.watch.FileChangeEvent; | ||
import io.quarkus.deployment.dev.filesystem.watch.WatchServiceFileSystemWatcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Classes moved due to the addition of the io.quarkus.deployment.dev.filesystem
package
public void reset(Context context) { | ||
try { | ||
this.fileManager.setLocation(StandardLocation.CLASS_PATH, context.getClassPath()); | ||
this.fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(context.getOutputDirectory())); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Cannot reset file manager", e); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A file manager reset means that if any internal state of the manager needs to be cleared it will be done
public void reset(Context context) { | ||
try { | ||
this.reloadableFileManager.close(); | ||
this.reloadableFileManager.setLocation(StandardLocation.CLASS_PATH, context.getReloadableClassPath()); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Cannot reset reloadable file manager", e); | ||
} | ||
super.reset(context); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, close and reset the reloadable file manager, then forward the static file manager reset to super class
@Override | ||
public void compile(Set<File> files, Context context) { | ||
Settings settings = new Settings(); | ||
context.getClasspath().stream() | ||
.map(File::getAbsolutePath) | ||
.forEach(f -> settings.classpath().append(f)); | ||
context.getClasspath().forEach(file -> settings.classpath().append(file.getAbsolutePath())); | ||
context.getReloadableClasspath().forEach(file -> settings.classpath().append(file.getAbsolutePath())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scala compiler settings does not expose the file manager implementation as Java, the solution is to add the reloadable paths to the classpath setting.
TODO: JavaConverters is deprecated, the recommendation is to use scala.jdk.CollectionConverters
. I ask for your opinion, as it may or may not be necessary to change.
//run.compile(JavaConverters.asScalaSet(fileSet).toList());
run.compile(CollectionConverters.SetHasAsScala(fileSet).asScala().toList());
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<scope>test</scope> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>org.checkerframework</groupId> | ||
<artifactId>checker-qual</artifactId> | ||
</exclusion> | ||
</exclusions> | ||
</dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed Guava dependency of this module because ImmutableMap.of()
can be easily replaced by Map.of()
if (LOG.isEnabled(Logger.Level.ERROR) || LOG.isEnabled(Logger.Level.WARN)) { | ||
collectDiagnostics(diagnosticsCollector, (level, diagnostic) -> LOG.logf(level, "%s, line %d in %s", | ||
diagnostic.getMessage(null), diagnostic.getLineNumber(), | ||
diagnostic.getSource() == null ? "[unknown source]" : diagnostic.getSource().getName())); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collect logs only if the log level is enabled in the application
Looks good @fercomunello, thanks for a great contribution! |
Fixes issue #27242
This PR:
Adds new extensible file manager implementations for the Java compilation provider, the main goal is to separate the static classpath from the reloadable one, this approach is more efficient than reopen the file manager before every compilation task, as only the reloadable part will be reset.
Adds integration tests of external reloadable artifacts feature for: Java, Kotlin & Scala.
How to reproduce the issue?
Verify that the test is working as expected:
$ mvn clean install -f integration-tests/maven/ -Dit.test=DevMojoIT#testExternalReloadableArtifacts
Then, revert all the changes, run the same tests again and see that it will fails, throwing a compilation error.
The issue initially was caused by the following commit: 175665d
This PR needs a review. For knowledge: @aloubyansky