2020import org .assertj .core .api .Assertions ;
2121import org .junit .After ;
2222import org .junit .Before ;
23+ import org .junit .Rule ;
2324import org .junit .Test ;
25+ import org .junit .rules .TestName ;
26+ import org .junit .rules .TestWatcher ;
27+ import org .junit .runner .Description ;
2428import org .slf4j .Logger ;
2529import org .slf4j .LoggerFactory ;
2630
@@ -95,13 +99,27 @@ private StreamObserver<Message> createStream(StreamObserver<Event> recv) {
9599 return in ;
96100 }
97101
102+ @ Rule
103+ public TestName currentTest = new TestName ();
104+
105+ private boolean testFailed ;
106+
107+ @ Rule
108+ public TestWatcher __ = new TestWatcher () {
109+ @ Override
110+ protected void failed (Throwable e , Description description ) {
111+ testFailed = true ;
112+ }
113+ };
114+
98115 /**
99116 * Create new unstarted context with default maxSizeBytes, collection
100117 * descriptor, and collection handle defaults.
101118 */
102119 @ Before
103120 public void startContext () throws InterruptedException {
104121 log .debug ("===================startContext==================" );
122+ log .debug (currentTest .getMethodName ());
105123
106124 assert !Thread .currentThread ().isInterrupted () : "main thread interrupted" ;
107125 assert REQUEST_QUEUE .isEmpty () : "stream contains incoming message " + REQUEST_QUEUE .peek ();
@@ -117,12 +135,21 @@ public void startContext() throws InterruptedException {
117135 context .start ();
118136
119137 in .expectMessage (START );
120- out .emitEvent (Event .STARTED );
138+ out .emitEventAsync (Event .STARTED );
121139 }
122140
123141 @ After
124142 public void reset () throws Exception {
125- if (!contextClosed ) {
143+ log .atDebug ()
144+ .addKeyValue ("contextClosed" , contextClosed )
145+ .addKeyValue ("testFailed" , testFailed )
146+ .log ("Begin test cleanup" );
147+
148+ // Do not attempt to close the context if it has been previously closed
149+ // by the test or the test has failed. In the latter case closing the
150+ // context may lead to a deadlock if the case hasn't scheduled Results
151+ // for all submitted messages.
152+ if (!contextClosed && !testFailed ) {
126153 closeContext ();
127154 }
128155
@@ -159,7 +186,7 @@ private void closeContext() throws Exception {
159186
160187 try {
161188 context .close ();
162- eof .get ();
189+ eof .get (5 , TimeUnit . SECONDS );
163190 } finally {
164191 contextClosed = true ;
165192 }
@@ -175,17 +202,21 @@ public void test_sendOneBatch() throws Exception {
175202 // BatchContext should flush the current batch once it hits its limit.
176203 // We will ack all items in the batch and send successful result for each one.
177204 List <String > received = recvDataAndAck ();
205+ out .beforeEof (new Event .Results (received , Collections .emptyMap ()));
206+
178207 Assertions .assertThat (tasks )
179208 .extracting (TaskHandle ::id ).containsExactlyInAnyOrderElementsOf (received );
180- Assertions .assertThat (tasks )
181- .extracting (TaskHandle ::isAcked ).allMatch (CompletableFuture ::isDone );
182209
183- out .beforeEof (new Event .Results (received , Collections .emptyMap ()));
210+ CompletableFuture <?>[] tasksAcked = tasks .stream ()
211+ .map (TaskHandle ::isAcked ).toArray (CompletableFuture []::new );
212+ Assertions .assertThat (CompletableFuture .allOf (tasksAcked ))
213+ .succeedsWithin (5 , TimeUnit .SECONDS );
184214
185215 // Since MockServer runs in the same thread as this test,
186216 // the context will be updated before the last emitEvent returns.
187217 closeContext ();
188218
219+ // By the time context.close() returns all tasks MUST have results set.
189220 Assertions .assertThat (tasks ).extracting (TaskHandle ::result )
190221 .allMatch (CompletableFuture ::isDone )
191222 .extracting (CompletableFuture ::get ).extracting (TaskHandle .Result ::error )
@@ -207,8 +238,11 @@ public void test_drainOnClose() throws Exception {
207238 List <String > received = recvDataAndAck ();
208239 Assertions .assertThat (tasks ).extracting (TaskHandle ::id )
209240 .containsExactlyInAnyOrderElementsOf (received );
210- Assertions .assertThat (tasks ).extracting (TaskHandle ::isAcked )
211- .allMatch (CompletableFuture ::isDone );
241+
242+ CompletableFuture <?>[] tasksAcked = tasks .stream ()
243+ .map (TaskHandle ::isAcked ).toArray (CompletableFuture []::new );
244+ Assertions .assertThat (CompletableFuture .allOf (tasksAcked ))
245+ .succeedsWithin (5 , TimeUnit .SECONDS );
212246 } catch (Exception e ) {
213247 throw new RuntimeException (e );
214248 }
@@ -273,6 +307,7 @@ public void test_backoffBacklog() throws Exception {
273307 int batchSizeNew = BATCH_SIZE / 2 ;
274308
275309 // Force the last BATCH_SIZE / 2 - 1 items to be transferred to the backlog.
310+ // Await for this event to be processed before moving forward.
276311 out .emitEvent (new Event .Backoff (batchSizeNew ));
277312
278313 // The next item will go on the backlog and the trigger a flush,
@@ -298,14 +333,14 @@ public void test_backoffBacklog() throws Exception {
298333
299334 @ Test
300335 public void test_reconnect_onShutdown () throws Exception {
301- out .emitEvent (Event .SHUTTING_DOWN );
336+ out .emitEventAsync (Event .SHUTTING_DOWN );
302337 in .expectMessage (STOP );
303338 out .eof (true );
304339 in .expectMessage (START );
305340
306341 // Not strictly necessary -- we can close the context
307342 // before a new connection is established.
308- out .emitEvent (Event .STARTED );
343+ out .emitEventAsync (Event .STARTED );
309344 }
310345
311346 @ Test
@@ -319,18 +354,18 @@ public void test_reconnect_onOom() throws Exception {
319354
320355 // Respond with OOM and wait for the client to close its end of the stream.
321356 in .expectMessage (DATA );
322- out .emitEvent (new Event .Oom (0 ));
357+ out .emitEventAsync (new Event .Oom (0 ));
323358
324359 // Close the server's end of the stream.
325360 in .expectMessage (STOP );
326361
327362 // Allow the client to reconnect to another "instance" and Ack the batch.
328363 in .expectMessage (START );
329- out .emitEvent (Event .STARTED );
364+ out .emitEventAsync (Event .STARTED );
330365 recvDataAndAck ();
331366
332367 List <String > submitted = tasks .stream ().map (TaskHandle ::id ).toList ();
333- out .emitEvent (new Event .Results (submitted , Collections .emptyMap ()));
368+ out .emitEventAsync (new Event .Results (submitted , Collections .emptyMap ()));
334369 }
335370
336371 @ Test
@@ -347,7 +382,7 @@ public void test_reconnect_onStreamHangup() throws Exception {
347382
348383 // The client should try to reconnect, because the context is still open.
349384 in .expectMessage (START );
350- out .emitEvent (Event .STARTED );
385+ out .emitEventAsync (Event .STARTED );
351386
352387 // The previous batch hasn't been acked, so we should expect to receive it
353388 // again.
@@ -358,7 +393,7 @@ public void test_reconnect_onStreamHangup() throws Exception {
358393 // in the queue to wake the sender up.
359394 out .hangup ();
360395 in .expectMessage (START );
361- out .emitEvent (Event .STARTED );
396+ out .emitEventAsync (Event .STARTED );
362397 tasks .add (context .add (WeaviateObject .of ()));
363398 recvDataAndAck ();
364399
@@ -399,7 +434,7 @@ public void test_reconnect_DrainAfterStreamHangup() throws Exception {
399434 // When the server starts accepting connections again, the client should
400435 // drain the remaining BATCH_SIZE+1 objects as we close the context.
401436 in .expectMessage (START );
402- out .emitEvent (Event .STARTED );
437+ out .emitEventAsync (Event .STARTED );
403438 Future <?> backgroundAcks = backgroundThread .submit (() -> {
404439 try {
405440 recvDataAndAck ();
@@ -426,7 +461,7 @@ public void test_reconnect_DrainAfterStreamHangup() throws Exception {
426461 public void test_closeAfterStreamHangup () throws Exception {
427462 out .hangup ();
428463 in .expectMessage (START );
429- out .emitEvent (Event .STARTED );
464+ out .emitEventAsync (Event .STARTED );
430465 }
431466
432467 @ Test
@@ -464,7 +499,7 @@ public void test_startAfterClose() throws Exception {
464499 */
465500 private List <String > recvDataAndAck () throws InterruptedException {
466501 List <String > received = recvData ();
467- out .emitEvent (new Event .Acks (received ));
502+ out .emitEventAsync (new Event .Acks (received ));
468503 return received ;
469504 }
470505
@@ -492,7 +527,16 @@ private static final class OutboundStream {
492527 this .eventThread = eventThread ;
493528 }
494529
495- CompletableFuture <Void > emitEvent (Event event ) {
530+ /** Emit event on the current thread. */
531+ void emitEvent (Event event ) {
532+ assert event != Event .EOF : "must not use synthetic EOF event" ;
533+ assert !(event instanceof Event .StreamHangup ) : "must not use synthetic StreamHangup event" ;
534+
535+ stream .onNext (event );
536+ }
537+
538+ /** Emit event on the {@link #eventThread}. */
539+ CompletableFuture <Void > emitEventAsync (Event event ) {
496540 assert event != Event .EOF : "must not use synthetic EOF event" ;
497541 assert !(event instanceof Event .StreamHangup ) : "must not use synthetic StreamHangup event" ;
498542
@@ -522,7 +566,7 @@ CompletableFuture<Void> eof(boolean ok) {
522566 if (ok ) {
523567 // These are guaranteed to finish before onCompleted,
524568 // as eventThread is just 1 thread.
525- pendingEvents .forEach (this ::emitEvent );
569+ pendingEvents .forEach (this ::emitEventAsync );
526570 }
527571 return CompletableFuture .runAsync (stream ::onCompleted , eventThread );
528572 }
0 commit comments