diff --git a/src/main/java/com/meilisearch/sdk/Client.java b/src/main/java/com/meilisearch/sdk/Client.java index d8d4cc68..17ce792e 100644 --- a/src/main/java/com/meilisearch/sdk/Client.java +++ b/src/main/java/com/meilisearch/sdk/Client.java @@ -356,10 +356,29 @@ public TaskInfo deleteTasks(DeleteTasksQuery param) throws MeilisearchException * Waits for a task to be processed * * @param uid Identifier of the requested Task + * @return Task in its final state (succeeded, failed or canceled) * @throws MeilisearchException if an error occurs or if timeout is reached + * @see API + * specification */ - public void waitForTask(int uid) throws MeilisearchException { - this.tasksHandler.waitForTask(uid); + public Task waitForTask(int uid) throws MeilisearchException { + return this.tasksHandler.waitForTask(uid); + } + + /** + * Waits for a task to be processed + * + * @param uid Identifier of the requested Task + * @param timeoutInMs number of milliseconds before throwing an Exception + * @param intervalInMs number of milliseconds before requesting the status again + * @return Task in its final state (succeeded, failed or canceled) + * @throws MeilisearchException if an error occurs or if timeout is reached + * @see API + * specification + */ + public Task waitForTask(int uid, int timeoutInMs, int intervalInMs) + throws MeilisearchException { + return this.tasksHandler.waitForTask(uid, timeoutInMs, intervalInMs); } /** diff --git a/src/main/java/com/meilisearch/sdk/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index 97d397b7..1cac80d7 100644 --- a/src/main/java/com/meilisearch/sdk/Index.java +++ b/src/main/java/com/meilisearch/sdk/Index.java @@ -1307,12 +1307,13 @@ public TasksResults getTasks(TasksQuery param) throws MeilisearchException { * Waits for a task to be processed * * @param taskId Identifier of the requested Task + * @return Task in its final state (succeeded, failed or canceled) * @throws MeilisearchException if an error occurs or if timeout is reached * @see API * specification */ - public void waitForTask(int taskId) throws MeilisearchException { - this.tasksHandler.waitForTask(taskId, 5000, 50); + public Task waitForTask(int taskId) throws MeilisearchException { + return this.tasksHandler.waitForTask(taskId); } /** @@ -1321,13 +1322,14 @@ public void waitForTask(int taskId) throws MeilisearchException { * @param taskId ID of the index update * @param timeoutInMs number of milliseconds before throwing an Exception * @param intervalInMs number of milliseconds before requesting the status again + * @return Task in its final state (succeeded, failed or canceled) * @throws MeilisearchException if an error occurs or if timeout is reached * @see API * specification */ - public void waitForTask(int taskId, int timeoutInMs, int intervalInMs) + public Task waitForTask(int taskId, int timeoutInMs, int intervalInMs) throws MeilisearchException { - this.tasksHandler.waitForTask(taskId, timeoutInMs, intervalInMs); + return this.tasksHandler.waitForTask(taskId, timeoutInMs, intervalInMs); } /** diff --git a/src/main/java/com/meilisearch/sdk/TasksHandler.java b/src/main/java/com/meilisearch/sdk/TasksHandler.java index 3692d919..47756c25 100644 --- a/src/main/java/com/meilisearch/sdk/TasksHandler.java +++ b/src/main/java/com/meilisearch/sdk/TasksHandler.java @@ -6,7 +6,6 @@ import com.meilisearch.sdk.model.*; import com.meilisearch.sdk.model.batch.req.BatchesQuery; import com.meilisearch.sdk.model.batch.res.Batch; -import java.util.Date; /** * Class covering the Meilisearch Task API @@ -14,6 +13,9 @@ * @see API specification */ public class TasksHandler { + static final int DEFAULT_WAIT_TIMEOUT_MS = 5000; + static final int DEFAULT_WAIT_INTERVAL_MS = 50; + private final HttpClient httpClient; /** @@ -124,10 +126,11 @@ TaskInfo deleteTasks(DeleteTasksQuery param) throws MeilisearchException { * Waits for a task to be processed * * @param taskUid Identifier of the Task + * @return Task in its final state (succeeded, failed or canceled) * @throws MeilisearchException if timeout is reached */ - void waitForTask(int taskUid) throws MeilisearchException { - this.waitForTask(taskUid, 5000, 50); + Task waitForTask(int taskUid) throws MeilisearchException { + return this.waitForTask(taskUid, DEFAULT_WAIT_TIMEOUT_MS, DEFAULT_WAIT_INTERVAL_MS); } /** @@ -136,28 +139,35 @@ void waitForTask(int taskUid) throws MeilisearchException { * @param taskUid Identifier of the Task * @param timeoutInMs number of milliseconds before throwing an Exception * @param intervalInMs number of milliseconds before requesting the status again + * @return Task in its final state (succeeded, failed or canceled) * @throws MeilisearchException if timeout is reached */ - void waitForTask(int taskUid, int timeoutInMs, int intervalInMs) throws MeilisearchException { - Task task; - TaskStatus status = null; - long startTime = new Date().getTime(); - long elapsedTime = 0; - - while (status == null - || (status.equals(TaskStatus.ENQUEUED) || status.equals(TaskStatus.PROCESSING))) { - if (elapsedTime >= timeoutInMs) { - throw new MeilisearchTimeoutException(); + Task waitForTask(int taskUid, int timeoutInMs, int intervalInMs) throws MeilisearchException { + long deadline = System.currentTimeMillis() + timeoutInMs; + while (true) { + Task task = this.getTask(taskUid); + TaskStatus status = task.getStatus(); + if (status != TaskStatus.ENQUEUED && status != TaskStatus.PROCESSING) { + return task; + } + long remainingMs = deadline - System.currentTimeMillis(); + if (remainingMs <= 0) { + throw new MeilisearchTimeoutException( + "Task " + + taskUid + + " not finished after " + + timeoutInMs + + "ms (last status: " + + status + + ")"); } - task = this.getTask(taskUid); - status = task.getStatus(); try { - Thread.sleep(intervalInMs); + // never sleep past the deadline, even when intervalInMs exceeds timeoutInMs + Thread.sleep(Math.min(intervalInMs, remainingMs)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new MeilisearchTimeoutException(); + throw new MeilisearchTimeoutException(e); } - elapsedTime = new Date().getTime() - startTime; } } diff --git a/src/test/java/com/meilisearch/integration/TasksTest.java b/src/test/java/com/meilisearch/integration/TasksTest.java index 391b57ca..8c86627b 100644 --- a/src/test/java/com/meilisearch/integration/TasksTest.java +++ b/src/test/java/com/meilisearch/integration/TasksTest.java @@ -2,10 +2,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -14,6 +16,7 @@ import com.meilisearch.integration.classes.AbstractIT; import com.meilisearch.integration.classes.TestData; import com.meilisearch.sdk.Index; +import com.meilisearch.sdk.exceptions.MeilisearchTimeoutException; import com.meilisearch.sdk.model.*; import com.meilisearch.sdk.utils.Movie; import java.time.Instant; @@ -340,9 +343,77 @@ public void testWaitForTaskTimoutInMs() throws Exception { Index index = client.index(indexUid); TaskInfo task = index.addDocuments(this.testData.getRaw()); + + MeilisearchTimeoutException e = + assertThrows( + MeilisearchTimeoutException.class, + () -> index.waitForTask(task.getTaskUid(), 0, 50)); + assertThat(e.getMessage(), containsString("Task " + task.getTaskUid())); + assertThat(e.getMessage(), containsString("0ms")); + + index.waitForTask(task.getTaskUid()); + } + + /** Test waitForTask does not sleep past the timeout when intervalInMs exceeds it */ + @Test + public void testWaitForTaskIntervalLongerThanTimeout() throws Exception { + String indexUid = "WaitForTaskIntervalLongerThanTimeout"; + Index index = client.index(indexUid); + TaskInfo task = index.addDocuments(this.testData.getRaw()); + + long start = System.currentTimeMillis(); + try { + index.waitForTask(task.getTaskUid(), 100, 10000); + } catch (MeilisearchTimeoutException ignored) { + // either outcome is fine, only the elapsed time matters + } + + assertThat(System.currentTimeMillis() - start, is(lessThan(5000L))); + index.waitForTask(task.getTaskUid()); + } + + /** Test waitForTask returns the finished task */ + @Test + public void testWaitForTaskReturnsTask() throws Exception { + String indexUid = "WaitForTaskReturnsTask"; + TaskInfo response = client.createIndex(indexUid); + + Task task = client.waitForTask(response.getTaskUid()); + + assertThat(task.getUid(), is(equalTo(response.getTaskUid()))); + assertThat(task.getStatus(), is(equalTo(TaskStatus.SUCCEEDED))); + assertThat(task.getFinishedAt(), is(notNullValue())); + + client.deleteIndex(indexUid); + } + + /** Test waitForTask returns a failed task instead of throwing */ + @Test + public void testWaitForTaskReturnsFailedTask() throws Exception { + String indexUid = "WaitForTaskReturnsFailedTask"; + client.waitForTask(client.createIndex(indexUid).getTaskUid()); + + TaskInfo response = client.createIndex(indexUid); + Task task = client.waitForTask(response.getTaskUid()); + + assertThat(task.getStatus(), is(equalTo(TaskStatus.FAILED))); + assertThat(task.getError().getCode(), is(equalTo("index_already_exists"))); + + client.deleteIndex(indexUid); + } + + /** Test Client.waitForTask with timeoutInMs and intervalInMs */ + @Test + public void testClientWaitForTaskTimeoutInMs() throws Exception { + String indexUid = "ClientWaitForTaskTimeoutInMs"; + TaskInfo response = client.createIndex(indexUid); + + Task task = client.waitForTask(response.getTaskUid(), 10000, 50); + + assertThat(task.getStatus(), is(equalTo(TaskStatus.SUCCEEDED))); - assertThrows(Exception.class, () -> index.waitForTask(task.getTaskUid(), 0, 50)); + client.deleteIndex(indexUid); } /** Test Tasks with Jackson Json Handler */