|
4 | 4 | import org.icij.datashare.asynctasks.CancelException; |
5 | 5 | import org.icij.datashare.asynctasks.Task; |
6 | 6 | import org.icij.datashare.batch.BatchSearch; |
| 7 | +import org.icij.datashare.batch.BatchSearchRecord; |
7 | 8 | import org.icij.datashare.batch.BatchSearchRepository; |
8 | 9 | import org.icij.datashare.batch.SearchException; |
9 | 10 | import org.icij.datashare.test.DatashareTimeRule; |
|
36 | 37 | import static org.mockito.ArgumentMatchers.anyBoolean; |
37 | 38 | import static org.mockito.ArgumentMatchers.anyList; |
38 | 39 | import static org.mockito.ArgumentMatchers.anyString; |
39 | | -import static org.mockito.Mockito.verify; |
40 | | -import static org.mockito.Mockito.when; |
| 40 | +import static org.mockito.Mockito.*; |
41 | 41 | import static org.mockito.MockitoAnnotations.initMocks; |
42 | 42 |
|
43 | 43 |
|
@@ -123,20 +123,46 @@ public void test_run_batch_search_with_throttle_should_not_last_more_than_max_ti |
123 | 123 | assertThat(timeRule.now().getTime() - beforeBatch.getTime()).isEqualTo(1000); |
124 | 124 | } |
125 | 125 |
|
| 126 | + // To avoid race conditions, this test relies heavily on synchronization. |
| 127 | + // The goal is to avoid having the BatchSearchRunner execute itself before receiving the cancel request, |
| 128 | + // which happened sometimes on the CI. |
126 | 129 | @Test |
127 | 130 | public void test_cancel_current_batch_search() throws Exception { |
128 | | - CountDownLatch countDownLatch = new CountDownLatch(1); |
| 131 | + // Given |
129 | 132 | Document[] documents = {createDoc("doc1").build(), createDoc("doc2").build()}; |
130 | 133 | mockSearch.willReturn(1, documents); |
| 134 | + CountDownLatch runnerStarted = new CountDownLatch(1); |
131 | 135 | BatchSearch search = new BatchSearch("uuid1", singletonList(project("test-datashare")), "name1", "desc1", asSet("query1", "query2"), new Date(), BatchSearch.State.QUEUED, User.local()); |
132 | 136 | when(repository.get(local(), search.uuid)).thenReturn(search); |
133 | | - BatchSearchRunner batchSearchRunner = new BatchSearchRunner(indexer, new PropertiesProvider(), repository, taskView(search), progressCb, countDownLatch); |
134 | 137 |
|
| 138 | + // The following block will halt the execution of the BatchSearchRunner until it receives the cancel request |
| 139 | + CountDownLatch cancelRequestedInRunner = new CountDownLatch(1); |
| 140 | + doAnswer(inv -> { |
| 141 | + //This will block the execution of the runner |
| 142 | + cancelRequestedInRunner.await(); |
| 143 | + return null; |
| 144 | + }).when(repository).setState(any(), eq(BatchSearchRecord.State.RUNNING)); |
| 145 | + |
| 146 | + // WHEN |
| 147 | + BatchSearchRunner batchSearchRunner = new BatchSearchRunner(indexer, new PropertiesProvider(), repository, taskView(search), progressCb, runnerStarted); |
135 | 148 | Future<BatchSearchRunnerResult> result = executor.submit(batchSearchRunner); |
| 149 | + runnerStarted.await(); |
136 | 150 | executor.shutdown(); |
137 | | - countDownLatch.await(); |
138 | | - batchSearchRunner.cancel(false); |
139 | 151 |
|
| 152 | + // BatchSearchRunner.cancel is blocking until the thread returns, |
| 153 | + // so it must be started in a new thread to avoid interlocking |
| 154 | + Thread cancelThread = new Thread(() -> batchSearchRunner.cancel(false)); |
| 155 | + cancelThread.start(); |
| 156 | + |
| 157 | + //Wait for the batchSearchRunner to receive the instruction that it must be canceled |
| 158 | + while (!batchSearchRunner.cancelAsked) { Thread.yield(); } |
| 159 | + |
| 160 | + //BatchSearchRunner received the instruction that it must be canceled : its execution can resume |
| 161 | + cancelRequestedInRunner.countDown(); |
| 162 | + |
| 163 | + cancelThread.join(); |
| 164 | + |
| 165 | + // THEN |
140 | 166 | assertThat(executor.awaitTermination(2, TimeUnit.SECONDS)).isTrue(); |
141 | 167 | assertThat(assertThrows(ExecutionException.class, result::get).getCause()).isInstanceOf(CancelException.class); |
142 | 168 | } |
|
0 commit comments