Skip to content
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

Merged

Conversation

fercomunello
Copy link
Contributor

@fercomunello fercomunello commented Aug 19, 2022

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?

  1. Verify that the test is working as expected:
    $ mvn clean install -f integration-tests/maven/ -Dit.test=DevMojoIT#testExternalReloadableArtifacts

  2. 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

@quarkus-bot
Copy link

quarkus-bot bot commented Aug 19, 2022

Thanks for your pull request!

The title of your pull request does not follow our editorial rules. Could you have a look?

  • title should not end up with dot

This message is automatically generated by a bot.

@quarkus-bot quarkus-bot bot added area/core area/devtools Issues/PR related to maven, gradle, platform and cli tooling/plugins area/maven area/testing labels Aug 19, 2022
@fercomunello fercomunello changed the title Fix dev mode compilation issue when change external JARs. Fix dev mode compilation issue when change external JARs Aug 19, 2022
@quarkus-bot

This comment has been minimized.

@fercomunello fercomunello changed the title Fix dev mode compilation issue when change external JARs [WIP] Fix dev mode compilation issue when change external JARs Aug 23, 2022
@fercomunello fercomunello changed the title [WIP] Fix dev mode compilation issue when change external JARs Fix dev mode compilation issue when change external JARs Aug 23, 2022
@gsmet gsmet requested a review from aloubyansky August 23, 2022 21:52
@quarkus-bot

This comment has been minimized.

@aloubyansky
Copy link
Member

@fercomunello there is a problem with the test.
I was looking for a more optimal way to do this. Would you mind giving my branch [1] a try and see whether the compilation time difference looks better in your app? Basically, I was trying to separate the static classpath from the reloadable one. And I think there is more we could do to make it better. Thanks.

[1] https://github.com/aloubyansky/quarkus/tree/external-reloadable-modules

@aloubyansky
Copy link
Member

@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.

@aloubyansky
Copy link
Member

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.

@fercomunello fercomunello changed the title Fix dev mode compilation issue when change external JARs Fix dev mode compilation issue for sources that depends on reloadable artifacts Aug 31, 2022
@fercomunello fercomunello changed the title Fix dev mode compilation issue for sources that depends on reloadable artifacts Improve dev mode live reload experience Aug 31, 2022
@aloubyansky
Copy link
Member

There seem to be relevant test failures @fercomunello

@quarkus-bot

This comment has been minimized.

@quarkus-bot quarkus-bot bot added area/arc Issue related to ARC (dependency injection) area/cache area/cli Related to quarkus cli (not maven/gradle/etc.) area/dependencies Pull requests that update a dependency file area/documentation area/flyway area/google-cloud-functions area/gradle Gradle area/graphql labels Aug 31, 2022
@phillip-kruger phillip-kruger removed area/websockets area/jackson Issues related to Jackson (JSON library) area/jakarta labels Sep 1, 2022
@fercomunello
Copy link
Contributor Author

@aloubyansky I will have a closer look to know why LambdaDevServicesContinuousTestingTestCase.java test failed. Also, I will add a integration test for Kotlin and Scala too.

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 testThatTheApplicationIsReloadedOnJavaChange test that reload 4 times, I not commited this 2 additional reload changes because it's only useful to see the performance perspective. In the first run, the live reload is 12% better and in the next run the live reload time is also a bit faster :)

@aloubyansky
Copy link
Member

@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.

@aloubyansky
Copy link
Member

It could be that LambdaDevServicesContinuousTestingTestCase isn't related to your changes, although I haven't looked into that. I saw there were some dev mode test failures in the maven module, those looked related and should be probably be looked into first.

@aloubyansky
Copy link
Member

Working on changes like this one, running tests in the integration-tests/maven module locally before pushing to the remote branch may save some CI time.

@fercomunello fercomunello changed the title [WIP] Improve dev mode live reload experience Improve live reload experience of external modules on dev mode Sep 3, 2022
@fercomunello fercomunello changed the title Improve live reload experience of external modules on dev mode Improve live reload experience of external artifacts on dev mode Sep 3, 2022
@fercomunello fercomunello marked this pull request as ready for review September 3, 2022 07:24
@quarkus-bot

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
@fercomunello
Copy link
Contributor Author

It could be that LambdaDevServicesContinuousTestingTestCase isn't related to your changes, although I haven't looked into that. I saw there were some dev mode test failures in the maven module, those looked related and should be probably be looked into first.

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 👍

Comment on lines +31 to +48
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);
}
}
Copy link
Contributor Author

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

Comment on lines -21 to +23
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;
Copy link
Contributor Author

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

Comment on lines +28 to +35
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);
}
}
Copy link
Contributor Author

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

Comment on lines +264 to +272
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);
}
Copy link
Contributor Author

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

Comment on lines 27 to +31
@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()));
Copy link
Contributor Author

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());

Comment on lines -17 to -27
<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>
Copy link
Contributor Author

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()

Comment on lines +102 to +106
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()));
}
Copy link
Contributor Author

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

Quarkus Documentation automation moved this from To do to Reviewer approved Sep 4, 2022
@aloubyansky
Copy link
Member

Looks good @fercomunello, thanks for a great contribution!

@aloubyansky aloubyansky merged commit c3edb14 into quarkusio:main Sep 4, 2022
Quarkus Documentation automation moved this from Reviewer approved to Done Sep 4, 2022
@quarkus-bot quarkus-bot bot added this to the 2.13 - main milestone Sep 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/core area/devtools Issues/PR related to maven, gradle, platform and cli tooling/plugins area/kotlin area/maven area/scala area/testing
Development

Successfully merging this pull request may close these issues.

None yet

3 participants