From 5aed6d77589c2f7c72efe027a590529cfceafec6 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 21 Jul 2021 10:18:55 +1000 Subject: [PATCH 1/6] Disable Vert.x TCCL management This breaks dev mode, and in general is not needed as Quarkus can perform it's own TCCL management when required. It also provides a slight performance boost. Fixes #18299 --- .../runtime/logging/LoggingSetupRecorder.java | 41 +++--- .../hibernate-reactive/deployment/pom.xml | 2 +- .../quarkus/hibernate/reactive/dev/Fruit.java | 51 +++++++ .../reactive/dev/FruitMutinyResource.java | 139 ++++++++++++++++++ .../dev/HibernateReactiveDevModeTest.java | 59 ++++++++ .../core/deployment/VertxCoreProcessor.java | 5 + .../vertx/core/runtime/VertxCoreRecorder.java | 4 + .../io/quarkus/test/QuarkusDevModeTest.java | 1 + .../java/io/quarkus/test/QuarkusUnitTest.java | 1 + 9 files changed, 285 insertions(+), 18 deletions(-) create mode 100644 extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/Fruit.java create mode 100644 extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/FruitMutinyResource.java create mode 100644 extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/HibernateReactiveDevModeTest.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index 25393756fe4f1..25a685b6ff228 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -20,6 +20,7 @@ import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import org.graalvm.nativeimage.ImageInfo; @@ -102,22 +103,26 @@ public void accept(String loggerName, CleanupFilterConfig config) { } }); } + LogCleanupFilter cleanupFiler = new LogCleanupFilter(filterElements); + for (Handler handler : LogManager.getLogManager().getLogger("").getHandlers()) { + handler.setFilter(cleanupFiler); + } final ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); if (config.console.enable) { - final Handler consoleHandler = configureConsoleHandler(config.console, consoleConfig, errorManager, filterElements, + final Handler consoleHandler = configureConsoleHandler(config.console, consoleConfig, errorManager, cleanupFiler, possibleFormatters, possibleBannerSupplier, launchMode); errorManager = consoleHandler.getErrorManager(); handlers.add(consoleHandler); } if (config.file.enable) { - handlers.add(configureFileHandler(config.file, errorManager, filterElements)); + handlers.add(configureFileHandler(config.file, errorManager, cleanupFiler)); } if (config.syslog.enable) { - final Handler syslogHandler = configureSyslogHandler(config.syslog, errorManager, filterElements); + final Handler syslogHandler = configureSyslogHandler(config.syslog, errorManager, cleanupFiler); if (syslogHandler != null) { handlers.add(syslogHandler); } @@ -125,7 +130,7 @@ public void accept(String loggerName, CleanupFilterConfig config) { if (!categories.isEmpty()) { Map namedHandlers = createNamedHandlers(config, consoleConfig, possibleFormatters, errorManager, - filterElements, launchMode); + cleanupFiler, launchMode); Map additionalNamedHandlersMap; if (additionalNamedHandlers.isEmpty()) { @@ -166,7 +171,7 @@ public void accept(String categoryName, CategoryConfig config) { if (optional.isPresent()) { final Handler handler = optional.get(); handler.setErrorManager(errorManager); - handler.setFilter(new LogCleanupFilter(filterElements)); + handler.setFilter(cleanupFiler); handlers.add(handler); } } @@ -191,18 +196,20 @@ public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConf filterElements.add( new LogCleanupFilterElement(entry.getKey(), entry.getValue().targetLevel, entry.getValue().ifStartsWith)); } + LogCleanupFilter logCleanupFilter = new LogCleanupFilter(filterElements); final ArrayList handlers = new ArrayList<>(3); if (config.console.enable) { - final Handler consoleHandler = configureConsoleHandler(config.console, consoleConfig, errorManager, filterElements, + final Handler consoleHandler = configureConsoleHandler(config.console, consoleConfig, errorManager, + logCleanupFilter, Collections.emptyList(), new RuntimeValue<>(Optional.empty()), launchMode); errorManager = consoleHandler.getErrorManager(); handlers.add(consoleHandler); } Map namedHandlers = createNamedHandlers(config, consoleConfig, Collections.emptyList(), errorManager, - filterElements, launchMode); + logCleanupFilter, launchMode); for (Map.Entry entry : categories.entrySet()) { final CategoryBuildTimeConfig buildCategory = isSubsetOf(entry.getKey(), buildConfig.categories); @@ -262,7 +269,7 @@ private static CategoryBuildTimeConfig isSubsetOf(String categoryName, Map createNamedHandlers(LogConfig config, ConsoleRuntimeConfig consoleRuntimeConfig, List>> possibleFormatters, ErrorManager errorManager, - List filterElements, LaunchMode launchMode) { + LogCleanupFilter cleanupFilter, LaunchMode launchMode) { Map namedHandlers = new HashMap<>(); for (Entry consoleConfigEntry : config.consoleHandlers.entrySet()) { ConsoleConfig namedConsoleConfig = consoleConfigEntry.getValue(); @@ -270,7 +277,7 @@ private static Map createNamedHandlers(LogConfig config, Consol continue; } final Handler consoleHandler = configureConsoleHandler(namedConsoleConfig, consoleRuntimeConfig, errorManager, - filterElements, + cleanupFilter, possibleFormatters, null, launchMode); addToNamedHandlers(namedHandlers, consoleHandler, consoleConfigEntry.getKey()); } @@ -279,7 +286,7 @@ private static Map createNamedHandlers(LogConfig config, Consol if (!namedFileConfig.enable) { continue; } - final Handler fileHandler = configureFileHandler(namedFileConfig, errorManager, filterElements); + final Handler fileHandler = configureFileHandler(namedFileConfig, errorManager, cleanupFilter); addToNamedHandlers(namedHandlers, fileHandler, fileConfigEntry.getKey()); } for (Entry sysLogConfigEntry : config.syslogHandlers.entrySet()) { @@ -287,7 +294,7 @@ private static Map createNamedHandlers(LogConfig config, Consol if (!namedSyslogConfig.enable) { continue; } - final Handler syslogHandler = configureSyslogHandler(namedSyslogConfig, errorManager, filterElements); + final Handler syslogHandler = configureSyslogHandler(namedSyslogConfig, errorManager, cleanupFilter); if (syslogHandler != null) { addToNamedHandlers(namedHandlers, syslogHandler, sysLogConfigEntry.getKey()); } @@ -341,7 +348,7 @@ public void initializeLoggingForImageBuild() { private static Handler configureConsoleHandler(final ConsoleConfig config, ConsoleRuntimeConfig consoleRuntimeConfig, final ErrorManager defaultErrorManager, - final List filterElements, + final LogCleanupFilter cleanupFilter, final List>> possibleFormatters, final RuntimeValue>> possibleBannerSupplier, LaunchMode launchMode) { Formatter formatter = null; @@ -384,7 +391,7 @@ private static Handler configureConsoleHandler(final ConsoleConfig config, Conso config.stderr ? ConsoleHandler.Target.SYSTEM_ERR : ConsoleHandler.Target.SYSTEM_OUT, formatter); consoleHandler.setLevel(config.level); consoleHandler.setErrorManager(defaultErrorManager); - consoleHandler.setFilter(new LogCleanupFilter(filterElements)); + consoleHandler.setFilter(cleanupFilter); Handler handler = config.async.enable ? createAsyncHandler(config.async, config.level, consoleHandler) : consoleHandler; @@ -421,7 +428,7 @@ public void close() throws SecurityException { } private static Handler configureFileHandler(final FileConfig config, final ErrorManager errorManager, - final List filterElements) { + final LogCleanupFilter cleanupFilter) { FileHandler handler = new FileHandler(); FileConfig.RotationConfig rotationConfig = config.rotation; if ((rotationConfig.maxFileSize.isPresent() || rotationConfig.rotateOnBoot) @@ -454,7 +461,7 @@ private static Handler configureFileHandler(final FileConfig config, final Error } handler.setErrorManager(errorManager); handler.setLevel(config.level); - handler.setFilter(new LogCleanupFilter(filterElements)); + handler.setFilter(cleanupFilter); if (config.async.enable) { return createAsyncHandler(config.async, config.level, handler); } @@ -463,7 +470,7 @@ private static Handler configureFileHandler(final FileConfig config, final Error private static Handler configureSyslogHandler(final SyslogConfig config, final ErrorManager errorManager, - final List filterElements) { + final LogCleanupFilter logCleanupFilter) { try { final SyslogHandler handler = new SyslogHandler(config.endpoint.getHostString(), config.endpoint.getPort()); handler.setAppName(config.appName.orElse(getProcessName())); @@ -478,7 +485,7 @@ private static Handler configureSyslogHandler(final SyslogConfig config, final PatternFormatter formatter = new PatternFormatter(config.format); handler.setFormatter(formatter); handler.setErrorManager(errorManager); - handler.setFilter(new LogCleanupFilter(filterElements)); + handler.setFilter(logCleanupFilter); if (config.async.enable) { return createAsyncHandler(config.async, config.level, handler); } diff --git a/extensions/hibernate-reactive/deployment/pom.xml b/extensions/hibernate-reactive/deployment/pom.xml index 20f3610589054..283a0b43c7c4a 100644 --- a/extensions/hibernate-reactive/deployment/pom.xml +++ b/extensions/hibernate-reactive/deployment/pom.xml @@ -73,7 +73,7 @@ io.quarkus - quarkus-resteasy-deployment + quarkus-resteasy-reactive-jsonb-deployment test diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/Fruit.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/Fruit.java new file mode 100644 index 0000000000000..f97af4fa431fa --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/Fruit.java @@ -0,0 +1,51 @@ +package io.quarkus.hibernate.reactive.dev; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.NamedQuery; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +@Entity +@Table(name = "known_fruits") +@NamedQuery(name = "Fruits.findAll", query = "SELECT f FROM Fruit f ORDER BY f.name") +public class Fruit { + + @Id + @SequenceGenerator(name = "fruitsSequence", sequenceName = "known_fruits_id_seq", allocationSize = 1, initialValue = 10) + @GeneratedValue(generator = "fruitsSequence") + private Integer id; + + @Column(length = 40, unique = true) + private String name; + + public Fruit() { + } + + public Fruit(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Fruit{" + id + "," + name + '}'; + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/FruitMutinyResource.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/FruitMutinyResource.java new file mode 100644 index 0000000000000..e05664860e2f1 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/FruitMutinyResource.java @@ -0,0 +1,139 @@ +package io.quarkus.hibernate.reactive.dev; + +import static javax.ws.rs.core.Response.Status.CREATED; +import static javax.ws.rs.core.Response.Status.NOT_FOUND; +import static javax.ws.rs.core.Response.Status.NO_CONTENT; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.hibernate.reactive.mutiny.Mutiny; +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.RestPath; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.smallrye.mutiny.Uni; + +@Path("fruits") +@ApplicationScoped +@Produces("application/json") +@Consumes("application/json") +public class FruitMutinyResource { + private static final Logger LOGGER = Logger.getLogger(FruitMutinyResource.class); + + @Inject + Mutiny.SessionFactory sf; + + @GET + public Uni> get() { + return sf.withTransaction((s, t) -> s + .createNamedQuery("Fruits.findAll", Fruit.class) + .getResultList()); + } + + @GET + @Path("{id}") + public Uni getSingle(@RestPath Integer id) { + return sf.withTransaction((s, t) -> s.find(Fruit.class, id)); + } + + @POST + public Uni create(Fruit fruit) { + if (fruit == null || fruit.getId() != null) { + throw new WebApplicationException("Id was invalidly set on request.", 422); + } + + return sf.withTransaction((s, t) -> s.persist(fruit)) + .replaceWith(() -> Response.ok(fruit).status(CREATED).build()); + } + + @PUT + @Path("{id}") + public Uni update(@RestPath Integer id, Fruit fruit) { + if (fruit == null || fruit.getName() == null) { + throw new WebApplicationException("Fruit name was not set on request.", 422); + } + + return sf.withTransaction((s, t) -> s.find(Fruit.class, id) + // If entity exists then update it + .onItem().ifNotNull().invoke(entity -> entity.setName(fruit.getName())) + .onItem().ifNotNull().transform(entity -> Response.ok(entity).build()) + // If entity not found return the appropriate response + .onItem().ifNull() + .continueWith(() -> Response.ok().status(NOT_FOUND).build())); + } + + @DELETE + @Path("{id}") + public Uni delete(@RestPath Integer id) { + return sf.withTransaction((s, t) -> s.find(Fruit.class, id) + // If entity exists then delete it + .onItem().ifNotNull() + .transformToUni(entity -> s.remove(entity) + .replaceWith(() -> Response.ok().status(NO_CONTENT).build())) + // If entity not found return the appropriate response + .onItem().ifNull().continueWith(() -> Response.ok().status(NOT_FOUND).build())); + } + + /** + * Create a HTTP response from an exception. + * + * Response Example: + * + *
+     * HTTP/1.1 422 Unprocessable Entity
+     * Content-Length: 111
+     * Content-Type: application/json
+     *
+     * {
+     *     "code": 422,
+     *     "error": "Fruit name was not set on request.",
+     *     "exceptionType": "javax.ws.rs.WebApplicationException"
+     * }
+     * 
+ */ + @Provider + public static class ErrorMapper implements ExceptionMapper { + + @Inject + ObjectMapper objectMapper; + + @Override + public Response toResponse(Exception exception) { + LOGGER.error("Failed to handle request", exception); + + int code = 500; + if (exception instanceof WebApplicationException) { + code = ((WebApplicationException) exception).getResponse().getStatus(); + } + + ObjectNode exceptionJson = objectMapper.createObjectNode(); + exceptionJson.put("exceptionType", exception.getClass().getName()); + exceptionJson.put("code", code); + + if (exception.getMessage() != null) { + exceptionJson.put("error", exception.getMessage()); + } + + return Response.status(code) + .entity(exceptionJson) + .build(); + } + + } +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/HibernateReactiveDevModeTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/HibernateReactiveDevModeTest.java new file mode 100644 index 0000000000000..c6b405936eddc --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/dev/HibernateReactiveDevModeTest.java @@ -0,0 +1,59 @@ +package io.quarkus.hibernate.reactive.dev; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.response.Response; + +/** + * Checks that public field access is correctly replaced with getter/setter calls, + * regardless of the field type. + */ +public class HibernateReactiveDevModeTest { + + @RegisterExtension + static QuarkusDevModeTest runner = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Fruit.class, FruitMutinyResource.class).addAsResource("application.properties") + .addAsResource(new StringAsset("INSERT INTO known_fruits(id, name) VALUES (1, 'Cherry');\n" + + "INSERT INTO known_fruits(id, name) VALUES (2, 'Apple');\n" + + "INSERT INTO known_fruits(id, name) VALUES (3, 'Banana');\n"), "import.sql")); + + @Test + public void testListAllFruits() { + Response response = given() + .when() + .get("/fruits") + .then() + .statusCode(200) + .contentType("application/json") + .extract().response(); + assertThat(response.jsonPath().getList("name")).isEqualTo(Arrays.asList("Apple", "Banana", "Cherry")); + + runner.modifySourceFile(Fruit.class, s -> s.replace("ORDER BY f.name", "ORDER BY f.name desk")); + given() + .when() + .get("/fruits") + .then() + .statusCode(500); + + runner.modifySourceFile(Fruit.class, s -> s.replace("desk", "desc")); + response = given() + .when() + .get("/fruits") + .then() + .statusCode(200) + .contentType("application/json") + .extract().response(); + assertThat(response.jsonPath().getList("name")).isEqualTo(Arrays.asList("Cherry", "Banana", "Apple")); + } +} diff --git a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java index b932f00cfaf68..e6f6e1fef3c0f 100644 --- a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java +++ b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java @@ -72,6 +72,11 @@ EventLoopCountBuildItem eventLoopCount(VertxCoreRecorder recorder, VertxConfigur return new EventLoopCountBuildItem(recorder.calculateEventLoopThreads(vertxConfiguration)); } + @BuildStep + LogCleanupFilterBuildItem cleanupVertxWarnings() { + return new LogCleanupFilterBuildItem("io.vertx.core.impl.ContextImpl", "You have disabled TCCL checks"); + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) IOThreadDetectorBuildItem ioThreadDetector(VertxCoreRecorder recorder) { diff --git a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index a74e994fde389..41d3a861a798e 100644 --- a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -64,6 +64,10 @@ @Recorder public class VertxCoreRecorder { + static { + System.setProperty("vertx.disableTCCL", "true"); + } + private static final Logger LOGGER = Logger.getLogger(VertxCoreRecorder.class.getName()); public static final String VERTX_CACHE = "vertx-cache"; diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java index 2a71daa94ac94..7f98907120311 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java @@ -262,6 +262,7 @@ public void afterAll(ExtensionContext context) throws Exception { } rootLogger.setHandlers(originalRootLoggerHandlers); inMemoryLogHandler.clearRecords(); + inMemoryLogHandler.setFilter(null); ClearCache.clearAnnotationCache(); GroovyCacheCleaner.clearGroovyCache(); } diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java index a0f32b1ec076f..693eb2957b69b 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java @@ -574,6 +574,7 @@ public void afterAll(ExtensionContext extensionContext) throws Exception { } rootLogger.setHandlers(originalHandlers); inMemoryLogHandler.clearRecords(); + inMemoryLogHandler.setFilter(null); try { if (runningQuarkusApplication != null) { From 7edaafa4921e157bbfacf89d13388d511046510c Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 26 Jul 2021 17:46:54 +1000 Subject: [PATCH 2/6] Fix some ClassLoader leaks running tests --- .../console/ConsoleStateManager.java | 6 ++- .../dev/testing/JunitTestRunner.java | 28 +++++++++-- .../deployment/dev/testing/TestSupport.java | 6 ++- .../dev/testing/TestTracingProcessor.java | 8 ---- .../logging/LoggingResourceProcessor.java | 6 +++ .../io/quarkus/dev/console/BasicConsole.java | 5 ++ .../quarkus/dev/console/QuarkusConsole.java | 3 ++ .../ContinuousTestingWebsocketListener.java | 2 +- .../devmode/tests/TestsProcessor.java | 4 +- .../ContinuousTestWebSocketHandler.java | 39 +++++++-------- .../runtime/devmode/DevConsoleRecorder.java | 15 +++++- .../test/junit/QuarkusTestExtension.java | 48 +++++++++++++++---- 12 files changed, 123 insertions(+), 47 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java index 4be52a8848ed9..af7bdc993fbdc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/console/ConsoleStateManager.java @@ -85,8 +85,10 @@ public static void init(QuarkusConsole console, DevModeType devModeType) { return; } initialized = true; - console.setInputHandler(INSTANCE.consumer); - INSTANCE.installBuiltins(devModeType); + if (console.isInputSupported()) { + console.setInputHandler(INSTANCE.consumer); + INSTANCE.installBuiltins(devModeType); + } } void installBuiltins(DevModeType devModeType) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java index 0f8592dda983e..55ce179b20a81 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java @@ -133,6 +133,12 @@ public void runTests() { long start = System.currentTimeMillis(); ClassLoader old = Thread.currentThread().getContextClassLoader(); try (QuarkusClassLoader tcl = testApplication.createDeploymentClassLoader()) { + synchronized (this) { + if (aborted) { + return; + } + testsRunning = true; + } Thread.currentThread().setContextClassLoader(tcl); Consumer currentTestAppConsumer = (Consumer) tcl.loadClass(CurrentTestApplication.class.getName()).newInstance(); currentTestAppConsumer.accept(testApplication); @@ -408,9 +414,18 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e } catch (Exception e) { throw new RuntimeException(e); } finally { - TracingHandler.setTracingHandler(null); - QuarkusConsole.INSTANCE.setOutputFilter(null); - Thread.currentThread().setContextClassLoader(old); + try { + TracingHandler.setTracingHandler(null); + QuarkusConsole.INSTANCE.setOutputFilter(null); + Thread.currentThread().setContextClassLoader(old); + } finally { + synchronized (this) { + testsRunning = false; + if (aborted) { + notifyAll(); + } + } + } } } @@ -424,6 +439,13 @@ public synchronized void abort() { } aborted = true; notifyAll(); + while (testsRunning) { + try { + wait(); + } catch (InterruptedException e) { + //ignore + } + } } public synchronized void pause() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index 8f26c6291c520..8230a37aa381f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -142,7 +142,11 @@ public void init() { cl.addCloseTask(new Runnable() { @Override public void run() { - testCuratedApplication.close(); + try { + stop(); + } finally { + testCuratedApplication.close(); + } } }); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java index b239778103fca..9da423bb4f1bd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestTracingProcessor.java @@ -77,14 +77,6 @@ void startTesting(TestConfig config, LiveReloadBuildItem liveReloadBuildItem, testSupport.stop(); } } - - QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); - ((QuarkusClassLoader) cl.parent()).addCloseTask(new Runnable() { - @Override - public void run() { - testSupport.stop(); - } - }); } @BuildStep(onlyIf = IsTest.class) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index e23109b52e296..b59083346d89e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -236,6 +236,12 @@ public void accept(LogRecord logRecord, Consumer logRecordConsumer) { } } }; + ((QuarkusClassLoader) getClass().getClassLoader()).addCloseTask(new Runnable() { + @Override + public void run() { + CurrentAppExceptionHighlighter.THROWABLE_FORMATTER = null; + } + }); } @BuildStep diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java index 77320954f199f..eb7fb0d8bfb51 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java @@ -156,4 +156,9 @@ public void write(byte[] buf, int off, int len) { write(new String(buf, off, len, StandardCharsets.UTF_8)); } + @Override + public boolean isInputSupported() { + return inputSupport; + } + } diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java index cca4e770bf00e..13bcb196e1e3e 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java @@ -100,4 +100,7 @@ public void setOutputFilter(Predicate logHandler) { this.outputFilter = logHandler; } + public boolean isInputSupported() { + return true; + } } diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/ContinuousTestingWebsocketListener.java b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/ContinuousTestingWebsocketListener.java index 288743df732a6..45a3b21d0d3eb 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/testing/ContinuousTestingWebsocketListener.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/testing/ContinuousTestingWebsocketListener.java @@ -14,7 +14,7 @@ public static Consumer getStateListener() { public static void setStateListener(Consumer stateListener) { ContinuousTestingWebsocketListener.stateListener = stateListener; - if (lastState != null) { + if (lastState != null && stateListener != null) { stateListener.accept(lastState); } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java index 68b8eb17d414c..417e00f80f2b2 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java @@ -12,6 +12,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.dev.testing.TestClassResult; import io.quarkus.deployment.dev.testing.TestListenerBuildItem; import io.quarkus.deployment.dev.testing.TestRunResults; @@ -45,6 +46,7 @@ public void setupTestRoutes( DevConsoleRecorder recorder, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, LaunchModeBuildItem launchModeBuildItem, + ShutdownContextBuildItem shutdownContextBuildItem, BuildProducer routeBuildItemBuildProducer, BuildProducer testListenerBuildItemBuildProducer) throws IOException { DevModeType devModeType = launchModeBuildItem.getDevModeType().orElse(null); @@ -56,7 +58,7 @@ public void setupTestRoutes( // Add continuous testing routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .route("dev/test") - .handler(recorder.continousTestHandler()) + .handler(recorder.continousTestHandler(shutdownContextBuildItem)) .build()); testListenerBuildItemBuildProducer.produce(new TestListenerBuildItem(new ContinuousTestingWebSocketListener())); } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ContinuousTestWebSocketHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ContinuousTestWebSocketHandler.java index 3eebc59f3d924..e7b9ba8301457 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ContinuousTestWebSocketHandler.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ContinuousTestWebSocketHandler.java @@ -14,33 +14,30 @@ import io.vertx.core.http.ServerWebSocket; import io.vertx.ext.web.RoutingContext; -public class ContinuousTestWebSocketHandler implements Handler { +public class ContinuousTestWebSocketHandler + implements Handler, Consumer { private static final Logger log = Logger.getLogger(ContinuousTestingWebsocketListener.class); private static final Set sockets = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static volatile String lastMessage; - static { - ContinuousTestingWebsocketListener.setStateListener(new Consumer() { - @Override - public void accept(ContinuousTestingWebsocketListener.State state) { - Json.JsonObjectBuilder response = Json.object(); - response.put("running", state.running); - response.put("inProgress", state.inProgress); - response.put("run", state.run); - response.put("passed", state.passed); - response.put("failed", state.failed); - response.put("skipped", state.skipped); - response.put("isBrokenOnly", state.isBrokenOnly); - response.put("isTestOutput", state.isTestOutput); - response.put("isInstrumentationBasedReload", state.isInstrumentationBasedReload); + @Override + public void accept(ContinuousTestingWebsocketListener.State state) { + Json.JsonObjectBuilder response = Json.object(); + response.put("running", state.running); + response.put("inProgress", state.inProgress); + response.put("run", state.run); + response.put("passed", state.passed); + response.put("failed", state.failed); + response.put("skipped", state.skipped); + response.put("isBrokenOnly", state.isBrokenOnly); + response.put("isTestOutput", state.isTestOutput); + response.put("isInstrumentationBasedReload", state.isInstrumentationBasedReload); - lastMessage = response.build(); - for (ServerWebSocket i : sockets) { - i.writeTextMessage(lastMessage); - } - } - }); + lastMessage = response.build(); + for (ServerWebSocket i : sockets) { + i.writeTextMessage(lastMessage); + } } @Override diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleRecorder.java index 12e7de8d767a7..ec5126f6b5909 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleRecorder.java @@ -15,6 +15,7 @@ import org.jboss.logging.Logger; import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.dev.testing.ContinuousTestingWebsocketListener; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; @@ -44,8 +45,18 @@ public Handler devConsoleHandler(String devConsoleFinalDestinati return new DevConsoleStaticHandler(devConsoleFinalDestination); } - public Handler continousTestHandler() { - return new ContinuousTestWebSocketHandler(); + public Handler continousTestHandler(ShutdownContext context) { + + ContinuousTestWebSocketHandler handler = new ContinuousTestWebSocketHandler(); + ContinuousTestingWebsocketListener.setStateListener(handler); + context.addShutdownTask(new Runnable() { + @Override + public void run() { + ContinuousTestingWebsocketListener.setStateListener(null); + + } + }); + return handler; } private static final class CleanupDevConsoleTempDirectory implements Runnable { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index bbf94b1be1800..8674b8b29478e 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -31,8 +31,10 @@ import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -148,9 +150,9 @@ public class QuarkusTestExtension private static DeepClone deepClone; //needed for @Nested private static final Deque> currentTestClassStack = new ArrayDeque<>(); - private static ScheduledExecutorService hangDetectionExecutor; - private static Duration hangTimeout; - private static ScheduledFuture hangTaskKey; + private static volatile ScheduledExecutorService hangDetectionExecutor; + private static volatile Duration hangTimeout; + private static volatile ScheduledFuture hangTaskKey; private static final Runnable hangDetectionTask = new Runnable() { final AtomicBoolean runOnce = new AtomicBoolean(); @@ -188,9 +190,30 @@ public void run() { } }; + static { + ClassLoader classLoader = QuarkusTestExtension.class.getClassLoader(); + if (classLoader instanceof QuarkusClassLoader) { + ((QuarkusClassLoader) classLoader).addCloseTask(new Runnable() { + @Override + public void run() { + ScheduledExecutorService h = QuarkusTestExtension.hangDetectionExecutor; + if (h != null) { + h.shutdownNow(); + QuarkusTestExtension.hangDetectionExecutor = null; + } + } + }); + } + } + private ExtensionState doJavaStart(ExtensionContext context, Class profile) throws Throwable { TracingHandler.quarkusStarting(); - hangDetectionExecutor = Executors.newSingleThreadScheduledExecutor(); + hangDetectionExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "Quarkus hang detection timer thread"); + } + }); String time = "10m"; //config is not established yet //we can only read from system properties @@ -404,8 +427,11 @@ public void close() throws IOException { hangTaskKey.cancel(true); hangTaskKey = null; } - hangDetectionExecutor.shutdownNow(); - hangDetectionExecutor = null; + var h = hangDetectionExecutor; + if (h != null) { + h.shutdownNow(); + hangDetectionExecutor = null; + } } } try { @@ -1185,7 +1211,6 @@ public void run() { @Override public void close() { - resetHangTimeout(); if (closed.compareAndSet(false, true)) { ClassLoader old = Thread.currentThread().getContextClassLoader(); if (runningQuarkusApplication != null) { @@ -1336,7 +1361,14 @@ public void execute(BuildContext context) { private static void resetHangTimeout() { if (hangTaskKey != null) { hangTaskKey.cancel(false); - hangTaskKey = hangDetectionExecutor.schedule(hangDetectionTask, hangTimeout.toMillis(), TimeUnit.MILLISECONDS); + ScheduledExecutorService h = QuarkusTestExtension.hangDetectionExecutor; + if (h != null) { + try { + hangTaskKey = h.schedule(hangDetectionTask, hangTimeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (RejectedExecutionException ignore) { + + } + } } } From b8cdfeb991f260665056f03437addc21f6c875d4 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 27 Jul 2021 11:38:29 +0100 Subject: [PATCH 3/6] Switch to vertx 4.2 preview --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 796774b48b21a..96e03286de853 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -109,7 +109,7 @@ 1.16.1.Final 1.8.7.Final 3.4.0.Final - 4.1.2 + 4.2.0-SNAPSHOT 4.5.13 4.4.14 4.1.4 From fa8ac9281a91ec38122db59a8b1b56a092b48633 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 27 Jul 2021 11:37:34 +0100 Subject: [PATCH 4/6] Configure Vert.x to disable TCCL management --- .../io/quarkus/vertx/core/runtime/VertxCoreRecorder.java | 5 +---- .../http/deployment/devmode/console/DevConsoleProcessor.java | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index 41d3a861a798e..e7505a7e71675 100644 --- a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -64,10 +64,6 @@ @Recorder public class VertxCoreRecorder { - static { - System.setProperty("vertx.disableTCCL", "true"); - } - private static final Logger LOGGER = Logger.getLogger(VertxCoreRecorder.class.getName()); public static final String VERTX_CACHE = "vertx-cache"; @@ -241,6 +237,7 @@ public static Vertx initialize(VertxConfiguration conf, VertxOptionsCustomizer c LaunchMode launchMode) { VertxOptions options = new VertxOptions(); + options.setDisableTCCL(true); if (conf != null) { convertToVertxOptions(conf, options, true, shutdown); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index 8a84e13f6da5f..e07d239d88d17 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -229,6 +229,7 @@ private static Vertx initializeDevConsoleVertx() { final VertxOptions vertxOptions = new VertxOptions(); //Smaller than default, but larger than 1 to be on the safe side. int POOL_SIZE = 2; + vertxOptions.setDisableTCCL(true); vertxOptions.setEventLoopPoolSize(POOL_SIZE); vertxOptions.setWorkerPoolSize(POOL_SIZE); vertxOptions.getMetricsOptions().setEnabled(false); From 072ffc04bfcbb54055352ab4b0a590e7076cf4f9 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 27 Jul 2021 11:39:05 +0100 Subject: [PATCH 5/6] No longer need reflection to retrieve the worker pool references --- .../vertx/core/runtime/VertxCoreRecorder.java | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java index e7505a7e71675..d44f076218fe7 100644 --- a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java +++ b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java @@ -8,8 +8,6 @@ import java.io.File; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -179,24 +177,9 @@ private WorkerPool extractWorkerPool(Vertx devModeVertx) { /** * Extract the JBoss Threads EnhancedQueueExecutor from the Vertx instance * and reset all threads to use the given ClassLoader. - * This is messy as it needs to use reflection until Vertx can expose it: - * - https://github.com/eclipse-vertx/vert.x/pull/4029 */ private void resetExecutorsClassloaderContext(WorkerPool workerPool, ClassLoader cl) { - final Method executorMethod; - try { - executorMethod = WorkerPool.class.getDeclaredMethod("executor"); - executorMethod.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - final Object result; - try { - result = executorMethod.invoke(workerPool); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - EnhancedQueueExecutor executor = (EnhancedQueueExecutor) result; + EnhancedQueueExecutor executor = (EnhancedQueueExecutor) workerPool.executor(); final Thread[] runningThreads = executor.getRunningThreads(); for (Thread t : runningThreads) { t.setContextClassLoader(cl); From 68d2191d5655d6548982566673daa411e68c87f3 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 27 Jul 2021 11:40:16 +0100 Subject: [PATCH 6/6] No longer need to filter out the TCCL checks warning --- .../io/quarkus/vertx/core/deployment/VertxCoreProcessor.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java index e6f6e1fef3c0f..b932f00cfaf68 100644 --- a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java +++ b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java @@ -72,11 +72,6 @@ EventLoopCountBuildItem eventLoopCount(VertxCoreRecorder recorder, VertxConfigur return new EventLoopCountBuildItem(recorder.calculateEventLoopThreads(vertxConfiguration)); } - @BuildStep - LogCleanupFilterBuildItem cleanupVertxWarnings() { - return new LogCleanupFilterBuildItem("io.vertx.core.impl.ContextImpl", "You have disabled TCCL checks"); - } - @BuildStep @Record(ExecutionTime.STATIC_INIT) IOThreadDetectorBuildItem ioThreadDetector(VertxCoreRecorder recorder) {