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

Support running the same application multiple times in dev mode #22743

Merged
merged 1 commit into from
Jan 18, 2022

Conversation

Postremus
Copy link
Member

@Postremus Postremus commented Jan 7, 2022

Adds support for running the same application multiple times in dev mode on the same machine.
Problem was, that resources of webjars (like devui) where unpacked to %TEMP%/quarkus/applicationName/libraryName/libraryVersion.
On windows, this worked fine. %TEMP% points to a path inside the user directory (e.g. c:/users/martin/appdata/local/temp/).
On linux however, this caused AccessDeniedException, since %TEMP% points to a global temp directory, e.g. /temp.

This branch changes the location where webjars are unpacked to.
Now, every quarkus dev mode isntace creates a new temp directory, e.g. C:\Users\Martin\AppData\Local\Temp\quarkus-webjar10464518099488497426.
These temp directories are removed on shutdown.

The directory layout inside the temp directory is changed. It is now flatter, and the same as in the generated resource. e.g. quarkus-webjar10464518099488497426\META-INF\swagger-ui-files\index.html

I also deprecated io.quarkus.deployment.util.WebJarUtil (forRemoval) in favor of two new BuildItems - WebJarBuildItem and WebJarResultBuildItem.
WebJarBuildItem contains the instructions on which artifact should be unpacked and to which final destination (e.g. META-INF/swagger-ui-files). The webjar is either unpacked to disk (dev mode), or resources are generated (prod).
WebJarResultBuildItem contains the path where the webjar was unpacked to.

dev-ui, swagger-ui, smallrye-health and graphql already use these new builditems - in quarkus, no place uses the deprecated WebJarUtil anymore.

fixes #22515

Related zulip discussion: https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Refactoring.20WebJarUtil

Handler<RoutingContext> handler = recorder.handler(finalDestinationBuildItem.getSwaggerUiFinalDestination(),
finalDestinationBuildItem.getSwaggerUiPath(),
String swaggerUiPath = nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path);
swaggerUiBuildProducer.produce(new SwaggerUiBuildItem(result.getFinalDestination(), swaggerUiPath));
Copy link
Member Author

@Postremus Postremus Jan 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwaggerUiBuildItem is produced just to be on the safe side, no idea if anyone consumes it.

Maybe we can deprecate the build item as well?

smallRyeHealthBuildItem.getHealthUiPath(), runtimeConfig);
String healthUiPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath);
smallryeHealthBuildProducer
.produce(new SmallRyeHealthBuildItem(result.getFinalDestination(), healthUiPath));
Copy link
Member Author

@Postremus Postremus Jan 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SmallRyeHealthBuildItem is produced just to be on the safe side, no idea if anyone consumes it.

Maybe we can deprecate the build item as well?

@Postremus Postremus force-pushed the bugs/22515 branch 2 times, most recently from 8c17e36 to f5ed857 Compare January 8, 2022 21:18
@quarkus-bot
Copy link

quarkus-bot bot commented Jan 9, 2022

This workflow status is outdated as a new workflow run has been triggered.

Failing Jobs - Building f5ed857

Status Name Step Failures Logs Raw logs
Gradle Tests - JDK 11 Windows Build Failures Logs Raw logs

Full information is available in the Build summary check run.

Failures

⚙️ Gradle Tests - JDK 11 Windows #

- Failing: integration-tests/gradle 

📦 integration-tests/gradle

io.quarkus.gradle.devmode.ModuleWithParentDependencyDevModeTest.main line 14 - More details - Source on GitHub

org.awaitility.core.ConditionTimeoutException: Condition with lambda expression in io.quarkus.test.devmode.util.DevModeTestUtils that uses java.util.function.Supplier, java.util.function.Supplierjava.util.concurrent.atomic.AtomicReference, java.util.concurrent.atomic.AtomicReferencejava.lang.String, java.lang.Stringboolean was not fulfilled within 1 minutes.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:164)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)

@Postremus
Copy link
Member Author

This PR is ready for review - it contains everything I wanted to include for now.

Dev mode startup time worsens by about 30ms. According to profiling, this is because the temp directory is now deleted on shutdown, and webjar resources need to be copied again.

I plan to open a follow up PR once this one is merged which will implement #22525 for swagger-ui, smallrye-health, graphql.
Should be enough to counteract these 30ms.

Handler<RoutingContext> handler = recorder.uiHandler(smallRyeGraphQLBuildItem.getGraphqlUiFinalDestination(),
smallRyeGraphQLBuildItem.getGraphqlUiPath(), runtimeConfig);
String graphQLUiPath = nonApplicationRootPathBuildItem.resolvePath(graphQLConfig.ui.rootPath);
smallRyeGraphQLBuildProducer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SmallRyeGraphQLBuildItem is produced just to be on the safe side, no idea if anyone consumes it.

Maybe we can deprecate the build item as well?


ClassLoader classLoader = WebJarUtil.class.getClassLoader();

for (Path p : resourcesArtifact.getResolvedPaths()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be moving away from using resolved path to explore the content of a resolved dependency switching to PathTree userApplication.getContentTree(). Then you walk the tree or process a specific path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

String artifactId) {
for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) {
if (dep.getArtifactId().equals(artifactId)
&& dep.getGroupId().equals(groupId)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind, it may happen there could be multiple artifacts on the classpath with the same G:A but different classifiers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now also compare the classifier in this place.
I saw no other way to compare a GACT to the GACTV of the ResolvedDependency.

@gsmet gsmet self-assigned this Jan 12, 2022

resourcesArtifact.getContentTree().accept(webJar.getRoot(), new Consumer<PathVisit>() {
@Override
public void accept(PathVisit pathVisit) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically, the passed in pathVisit could be null if the content tree does not contain the path (webJar.getRoot), not sure if that could happen in this specific case though. I thought it would be up to the impl of the consumer whether to throw an error or simply return in such a case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) {
if (dep.getArtifactId().equals(artifactKey.getArtifactId())
&& dep.getGroupId().equals(artifactKey.getGroupId())
&& dep.getClassifier().equals(artifactKey.getClassifier())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That or dep.getKey().equals(artifactKey)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, too, but I don't see how .equals can return true.
ResolvedDependency.getKey() returns an instance of GACTV.

We only pass a GACT in here, since the version of the webjar is not static.
E.g. io.quarkus: quarkus-vertx-http-deployment is one of it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, too, but I don't see how .equals can return true. ResolvedDependency.getKey() returns an instance of GACTV.

No, it returns an ArtifactKey, which is GACT.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, you are obviously right. I guess I got confused, because ResolvedArtifactDependency extends GACTV. :D

@Postremus
Copy link
Member Author

I cleaned this up a bit further, and implemented following additonal changes / fixes:

  • onlyCopyNonArtifactFiles now has no effect in prod builds, since all files are needed anyway.
  • insertVariables is now also a simple WebJarResourcesFilter
  • CombinedWebJarResoucesFilter allows to combine multiple filters into one. This allows to propagate the changed flag through all filters. For example, SwaggerUIProcessor generated a changed index.html file. With onlyCopyNonArtifactFiles=true, this file was not copied to disk however, since no change was detected.

@Postremus Postremus marked this pull request as ready for review January 15, 2022 17:58
@quarkus-bot
Copy link

quarkus-bot bot commented Jan 15, 2022

This workflow status is outdated as a new workflow run has been triggered.

Failing Jobs - Building 2455ecd

Status Name Step Failures Logs Raw logs
Gradle Tests - JDK 11 Windows Build Failures Logs Raw logs

Full information is available in the Build summary check run.

Failures

⚙️ Gradle Tests - JDK 11 Windows #

- Failing: integration-tests/gradle 

📦 integration-tests/gradle

io.quarkus.gradle.devmode.QuarkusDevDependencyDevModeTest.main line 14 - More details - Source on GitHub

org.awaitility.core.ConditionTimeoutException: Condition with lambda expression in io.quarkus.test.devmode.util.DevModeTestUtils that uses java.util.function.Supplier, java.util.function.Supplierjava.util.concurrent.atomic.AtomicReference, java.util.concurrent.atomic.AtomicReferencejava.lang.String, java.lang.Stringboolean was not fulfilled within 1 minutes.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:164)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)

Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally had the time to look at this one. I also migrated the Quarkus GitHub App to it to make sure things would be smooth.

Things are indeed far better with this setup and I was able to remove quite some boilerplate that was very ad hoc.

So it's a big +1 from me but... I added a few suggestions. They are open to discussion so feel free to push back :).

import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.ResolvedDependency;

public final class WebJarResultBuildItem extends SimpleBuildItem {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have some class-level javadoc here?

private static final GACT DEVCONSOLE_WEBJAR_ARTIFACT_KEY = new GACT("io.quarkus", "quarkus-vertx-http-deployment", null,
"jar");
private static final String DEVCONSOLE_WEBJAR_STATIC_RESOURCES_PATH = "dev-static/";
private static final String DEVCONSOLE_FINAL_DESTINATION = "META-INF/dev-ui-files";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this now? Couldn't it be generated from the artifact id/version + resource path? That would simplify things a bit.
At least I don't think we should make it mandatory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this option completly. Automaticly generating the final destination also prevents clashes between different webjars, which is quite nice.

import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.ResolvedDependency;

public final class WebJarResultBuildItem extends SimpleBuildItem {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would call it WebJarResultsBuildItem plural. You have several and it would be less confusing.

this(artifactKey, webjarRoot, true, false, finalDestination, filter);
}

public WebJarBuildItem(GACT webJarKey, String webjarRoot, boolean useDefaultQuarkusBranding,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be handy to have versions of both constructors without the filter as it's purely optional.

@@ -44,7 +44,10 @@

/**
* Utility for Web resource related operations
*
* @deprecated Use WebJarBuildItem and WebJarResultBuildItem instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you rename WebJarResultBuildItem, make sure this is done everywhere including here.

private final boolean onlyCopyNonArtifactFiles;

/**
* Defines whether quarkus can override resources of the webjar with quarkus internal files.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Defines whether quarkus can override resources of the webjar with quarkus internal files.
* Defines whether Quarkus can override resources of the webjar with Quarkus internal files.

this(artifactKey, webjarRoot, true, false, finalDestination, filter);
}

public WebJarBuildItem(GACT webJarKey, String webjarRoot, boolean useDefaultQuarkusBranding,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said, I wonder if we should use a Builder approach for this one?

this(artifactKey, webjarRoot, true, false, finalDestination, filter);
}

public WebJarBuildItem(GACT webJarKey, String webjarRoot, boolean useDefaultQuarkusBranding,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename webJarKey to artifactKey for consistency?

@Postremus
Copy link
Member Author

Thank you for the review @gsmet.
I implemented every change.

@quarkus-bot
Copy link

quarkus-bot bot commented Jan 18, 2022

This workflow status is outdated as a new workflow run has been triggered.

Failing Jobs - Building f9f1334

Status Name Step Failures Logs Raw logs
JVM Tests - JDK 11 Build Failures Logs Raw logs
JVM Tests - JDK 11 Windows Build Failures Logs Raw logs
JVM Tests - JDK 17 Build Failures Logs Raw logs
Native Tests - Amazon Build Failures Logs Raw logs
Native Tests - Main Build Failures Logs Raw logs

Full information is available in the Build summary check run.

Failures

⚙️ JVM Tests - JDK 11 #

- Failing: integration-tests/spring-web 

📦 integration-tests/spring-web

io.quarkus.it.spring.web.openapi.SwaggerAndOpenAPIWithCommonPrefixPMT.shouldWorkEvenWithCommonPrefix line 28 - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <404>.

⚙️ JVM Tests - JDK 11 Windows #

- Failing: integration-tests/spring-web 

📦 integration-tests/spring-web

io.quarkus.it.spring.web.openapi.SwaggerAndOpenAPIWithCommonPrefixPMT.shouldWorkEvenWithCommonPrefix line 28 - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <404>.

⚙️ JVM Tests - JDK 17 #

- Failing: integration-tests/spring-web 

📦 integration-tests/spring-web

io.quarkus.it.spring.web.openapi.SwaggerAndOpenAPIWithCommonPrefixPMT.shouldWorkEvenWithCommonPrefix line 28 - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <404>.

⚙️ Native Tests - Amazon #

- Failing: integration-tests/amazon-lambda-http 

📦 integration-tests/amazon-lambda-http

io.quarkus.it.amazon.lambda.AmazonLambdaSimpleIT.testSwaggerUi - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <404>.

⚙️ Native Tests - Main #

- Failing: integration-tests/main 

📦 integration-tests/main

io.quarkus.it.main.SwaggerUIITCase.testSwaggerUi - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Response body doesn't match expectation.

@Postremus
Copy link
Member Author

Postremus commented Jan 18, 2022

test failures look related, will have to take a closer look.

@gsmet
Copy link
Member

gsmet commented Jan 18, 2022

@Postremus just a note that the cutoff for 2.7.0.CR1 is tonight and I would prefer having this one in CR1 given how structural it is.

@Postremus
Copy link
Member Author

Postremus commented Jan 18, 2022

Should be fixed now, lets see what CI has to say.
The generated finalDestination ended with a slash, which wasn't expected by the SwaggerUIHandler / StaticFileHandlerImpl.

@gsmet gsmet added this to the 2.7 - main milestone Jan 18, 2022
@gsmet gsmet added the triage/waiting-for-ci Ready to merge when CI successfully finishes label Jan 18, 2022
@gsmet gsmet merged commit a039ce4 into quarkusio:main Jan 18, 2022
@quarkus-bot quarkus-bot bot removed the triage/waiting-for-ci Ready to merge when CI successfully finishes label Jan 18, 2022
@gsmet
Copy link
Member

gsmet commented Jan 18, 2022

And all green! Thanks for your efforts on this @Postremus, it's much cleaner!

@Postremus Postremus deleted the bugs/22515 branch January 19, 2022 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide support to build Quarkus applications with multiple Quarkus developers on the same machine
3 participants