From 4013c99156a46ef28deed4acf4e120084eb5f8ff Mon Sep 17 00:00:00 2001 From: e550448 Date: Thu, 29 Aug 2024 19:10:01 +0200 Subject: [PATCH 1/2] feat: Cleanup successfully finished jobs by timeout like failed jobs Refs: #175 --- .../converter/PdfConverterJobsService.java | 4 +-- .../PdfConverterJobsServiceTest.java | 36 ++++++++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsService.java b/src/main/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsService.java index a890ea2..5afe7eb 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsService.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsService.java @@ -100,7 +100,7 @@ public Optional getJobResult(String jobId) { return Optional.empty(); } if (future.isCancelled() || future.isCompletedExceptionally()) { - throw new IllegalStateException("Job was cancelled or failed: " + jobId); + throw new IllegalStateException("Job was cancelled or failed: " + failedJobsReasons.get(jobId)); } try { return Optional.of(future.get()); @@ -109,8 +109,6 @@ public Optional getJobResult(String jobId) { throw new IllegalStateException("Cannot extract result for job " + jobId + " :" + e.getMessage(), e); } catch (Exception e) { throw new IllegalStateException("Cannot extract result for job " + jobId + " :" + e.getMessage(), e); - } finally { - jobs.remove(jobId); } } diff --git a/src/test/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsServiceTest.java b/src/test/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsServiceTest.java index c6da96c..23c4143 100644 --- a/src/test/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsServiceTest.java +++ b/src/test/java/ch/sbb/polarion/extension/pdf/exporter/converter/PdfConverterJobsServiceTest.java @@ -21,7 +21,6 @@ import javax.security.auth.Subject; import java.security.PrivilegedAction; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -77,9 +76,14 @@ void shouldStartJobAndGetStatus() { assertThat(jobResult).isNotEmpty(); assertThat(new String(jobResult.get())).isEqualTo("test pdf"); - assertThatThrownBy(() -> pdfConverterJobsService.getJobState(jobId)) - .isInstanceOf(NoSuchElementException.class) - .hasMessageContaining(jobId); + // Second attempt to ensure that job is not removed + jobState = pdfConverterJobsService.getJobState(jobId); + assertThat(jobState.isDone()).isTrue(); + assertThat(jobState.isCompletedExceptionally()).isFalse(); + assertThat(jobState.isCancelled()).isFalse(); + jobResult = pdfConverterJobsService.getJobResult(jobId); + assertThat(jobResult).isNotEmpty(); + verify(securityService).logout(subject); } @@ -98,6 +102,10 @@ void shouldReturnFailInExceptionalCase() { assertThat(jobState.isCompletedExceptionally()).isTrue(); assertThat(jobState.isCancelled()).isFalse(); assertThat(jobState.errorMessage()).contains("test error"); + + assertThatThrownBy(() -> pdfConverterJobsService.getJobResult(jobId)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("test error"); verify(securityService).logout(subject); } @@ -175,7 +183,7 @@ void shouldRespectInProgressTimeout(int timeout, boolean isTimeoutExpected) { @ParameterizedTest @CsvSource({"0,0", "1,1"}) - void shouldCleanupTimedOutFinishedJobs(int timeout, int expectedJobsCount) { + void shouldCleanupSuccessfullyFinishedJobs(int timeout, int expectedJobsCount) { ExportParams exportParams = ExportParams.builder().build(); String finishedJobId = pdfConverterJobsService.startJob(exportParams, 1); waitToFinishJob(finishedJobId); @@ -185,6 +193,24 @@ void shouldCleanupTimedOutFinishedJobs(int timeout, int expectedJobsCount) { assertThat(pdfConverterJobsService.getAllJobsStates()).hasSize(expectedJobsCount); } + @ParameterizedTest + @CsvSource({"0,0", "1,1"}) + void shouldCleanupFailedJobs(int timeout, int expectedJobsCount) { + lenient().when(securityService.doAsUser(any(), any(PrivilegedAction.class))).thenThrow(new RuntimeException("test error")); + ExportParams exportParams = ExportParams.builder().build(); + String failedJobId = pdfConverterJobsService.startJob(exportParams, 1); + waitToFinishJob(failedJobId); + + JobState jobState = pdfConverterJobsService.getJobState(failedJobId); + assertThat(jobState.isDone()).isTrue(); + assertThat(jobState.isCompletedExceptionally()).isTrue(); + assertThat(jobState.errorMessage()).contains("test error"); + + PdfConverterJobsService.cleanupExpiredJobs(timeout); + + assertThat(pdfConverterJobsService.getAllJobsStates()).hasSize(expectedJobsCount); + } + @Test @SuppressWarnings({"unchecked", "java:S2925"}) void shouldCleanupTimedOutInProgressJobs() { From 3694b78cff76e036bafa7f1d54a48bd07b5403d3 Mon Sep 17 00:00:00 2001 From: e550448 Date: Mon, 2 Sep 2024 09:09:09 +0200 Subject: [PATCH 2/2] chore: Updated readme with timeout jobs properties description Refs: #175 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ccd869e..db9576f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,16 @@ To change WeasyPrint Service URL, adjust the following property in the `polarion ch.sbb.polarion.extension.pdf-exporter.weasyprint.service=http://localhost:9080 ``` +### Asynchronous PDF Export: export jobs timeout +This extension provides REST API to export PDF asynchronously. Using this API, it is possible to start export job, observe their status and get result. +Finished (succeed or failed) and in-progress export jobs will be preserved in memory until configured timeout. To change this timeout, adjust the following property in the local `pdf-converter-jobs.properties` file: +```properties +# Timeout in minutes to keep finished async conversion jobs results in memory +jobs.timeout.finished.minutes=30 +# Timeout in minutes to wait until async conversion jobs is finished +jobs.timeout.in-progress.minutes=60 +``` + ### PDF exporter extension to appear on a Document's properties pane 1. Open a project where you wish PDF Exporter to be available