From e88a5717da5e00a238f340714fa1ee95fb594040 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Tue, 3 Sep 2024 13:13:24 +0200 Subject: [PATCH] Make sure AsyncFile gets closed --- .../advanced/reactive/DownloadResource.java | 59 +++++++++++++++++++ .../advanced/reactive/DownloadResourceIT.java | 58 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 http/http-advanced-reactive/src/main/java/io/quarkus/ts/http/advanced/reactive/DownloadResource.java create mode 100644 http/http-advanced-reactive/src/test/java/io/quarkus/ts/http/advanced/reactive/DownloadResourceIT.java diff --git a/http/http-advanced-reactive/src/main/java/io/quarkus/ts/http/advanced/reactive/DownloadResource.java b/http/http-advanced-reactive/src/main/java/io/quarkus/ts/http/advanced/reactive/DownloadResource.java new file mode 100644 index 000000000..833d6d25c --- /dev/null +++ b/http/http-advanced-reactive/src/main/java/io/quarkus/ts/http/advanced/reactive/DownloadResource.java @@ -0,0 +1,59 @@ +package io.quarkus.ts.http.advanced.reactive; + +import java.io.File; +import java.util.UUID; + +import jakarta.inject.Inject; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.smallrye.mutiny.Uni; +import io.vertx.core.file.OpenOptions; +import io.vertx.mutiny.core.Vertx; + +@Path("/download") +public class DownloadResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(DownloadResource.class); + private static final String TEST_FILE = System.getProperty("java.io.tmpdir") + + File.separator + "DownloadResource-" + UUID.randomUUID().toString() + "-test.txt"; + private static final OpenOptions READ_ONLY = new OpenOptions().setWrite(false).setCreate(false); + + @Inject + Vertx vertx; + + @POST + @Path("/create") + public Uni createFile() { + LOGGER.info("Creating test file: {}", TEST_FILE); + return vertx.fileSystem() + .createFile(TEST_FILE) + .onItem().transform(it -> Response.ok(TEST_FILE).build()); + } + + @DELETE + @Path("/delete") + public Uni deleteFile() { + LOGGER.info("Deleting test file: {}", TEST_FILE); + return vertx.fileSystem() + .delete(TEST_FILE) + .onItem().transform(it -> Response.noContent().build()); + } + + @GET + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Uni downloadFile() { + LOGGER.info("Downloading test file: {}", TEST_FILE); + return vertx.fileSystem() + .open(TEST_FILE, READ_ONLY) + .onItem().transform(it -> Response.ok(it).build()); + } +} diff --git a/http/http-advanced-reactive/src/test/java/io/quarkus/ts/http/advanced/reactive/DownloadResourceIT.java b/http/http-advanced-reactive/src/test/java/io/quarkus/ts/http/advanced/reactive/DownloadResourceIT.java new file mode 100644 index 000000000..1d190f89e --- /dev/null +++ b/http/http-advanced-reactive/src/test/java/io/quarkus/ts/http/advanced/reactive/DownloadResourceIT.java @@ -0,0 +1,58 @@ +package io.quarkus.ts.http.advanced.reactive; + +import static io.restassured.RestAssured.given; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.scenarios.annotations.DisabledOnNative; +import io.quarkus.test.services.QuarkusApplication; +import io.restassured.response.Response; + +/** + * Test makes sure AsyncFile gets closed, coverage triggered by https://github.com/quarkusio/quarkus/issues/41811 + */ +@QuarkusScenario +@DisabledOnNative(reason = "To save resources on CI") +@DisabledOnOs(value = OS.WINDOWS, disabledReason = "No lsof command on Windows") +class DownloadResourceIT { + @QuarkusApplication(classes = { DownloadResource.class }, properties = "oidcdisable.properties") + static RestService app = new RestService(); + + @Test + void ensureAsyncFileGetsClosed() throws IOException { + Response response = app.given() + .when().post("/download/create") + .then() + .statusCode(200) + .extract().response(); + String file = response.getBody().asString(); + + app.given() + .when().get("/download") + .then() + .statusCode(200); + + ProcessBuilder lsofBuilder = new ProcessBuilder("lsof", file); + Process lsofProcess = lsofBuilder.start(); + String lsofOutput = new BufferedReader(new InputStreamReader(lsofProcess.getInputStream())).lines() + .collect(Collectors.joining("\n")); + + app.given() + .when().delete("/download/delete") + .then() + .statusCode(204); + + Assert.assertEquals("AsyncFile is not closed, details:\n" + lsofOutput + "\n", 0, lsofOutput.length()); + } + +}