1717import java .util .concurrent .ExecutionException ;
1818import java .util .concurrent .ExecutorService ;
1919import java .util .concurrent .Executors ;
20+ import java .util .concurrent .Future ;
2021import java .util .concurrent .ScheduledExecutorService ;
2122import java .util .concurrent .ScheduledFuture ;
2223import java .util .concurrent .TimeUnit ;
2324import java .util .concurrent .locks .Condition ;
2425import java .util .concurrent .locks .Lock ;
2526import java .util .concurrent .locks .ReentrantLock ;
27+ import java .util .function .Predicate ;
2628
2729import javax .annotation .concurrent .GuardedBy ;
2830
@@ -159,6 +161,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
159161 /**
160162 * Queue publishes insert tasks from the main thread to the "sender".
161163 *
164+ * <p>
162165 * Send {@link TaskHandle#POISON} to gracefully shut down the "sender"
163166 * thread. The same queue may be re-used with a different "sender",
164167 * e.g. after {@link #reconnect}, but only when the new thread is known
@@ -170,6 +173,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
170173 /**
171174 * Work-in-progress items.
172175 *
176+ * <p>
173177 * An item is added to the wip map after the "sender" successfully
174178 * adds it to the {@link #batch} and is removed once the server reports
175179 * back the result (whether success of failure).
@@ -205,16 +209,14 @@ public final class BatchContext<PropertiesT> implements Closeable {
205209 /**
206210 * Handle for the "sender" routine.
207211 * Cancel this future to interrupt the "sender".
208- *
209- * @see Send#cancel
210212 */
211- private final Send send = new Send () ;
213+ private volatile Future <?> send ;
212214
213215 /**
214216 * Indicates completion of the "recv" routine.
215217 * Canceling this future will have no effect.
216218 */
217- private volatile Recv recv ;
219+ private volatile CompletableFuture <?> recv ;
218220
219221 /**
220222 * Maximum number of times the client will attempt to re-open the stream
@@ -246,6 +248,8 @@ public final class BatchContext<PropertiesT> implements Closeable {
246248 this .queue = new ArrayBlockingQueue <>(queueSize );
247249 this .batch = new Batch (batchSize , maxSizeBytes );
248250 this .maxReconnectRetries = maxReconnectRetries ;
251+
252+ setState (AWAIT_STARTED );
249253 }
250254
251255 private BatchContext (Builder <PropertiesT > builder ) {
@@ -279,13 +283,8 @@ void start() {
279283 if (closed ) {
280284 throw new IllegalStateException ("context is closed" );
281285 }
282-
283- messages = streamFactory .createStream (recv = new Recv (this ));
284- messages .onNext (Message .start (collectionHandleDefaults .consistencyLevel ()));
285-
286- // "send" routine must start after the nextState has been set.
287- setState (AWAIT_STARTED );
288- sendService .execute ((Runnable ) send );
286+ openStream ();
287+ send = sendService .submit (new Send ());
289288 }
290289
291290 /**
@@ -304,9 +303,7 @@ void reconnect() throws InterruptedException, ExecutionException {
304303 // the context can only transition into the Reconnecting state
305304 // after the server half of the stream is closed (EOF or hangup).
306305 recv .get ();
307-
308- messages = streamFactory .createStream (recv = new Recv (this ));
309- messages .onNext (Message .start (collectionHandleDefaults .consistencyLevel ()));
306+ openStream ();
310307 }
311308
312309 /**
@@ -365,8 +362,8 @@ private void shutdown() {
365362 // Luckily, shutdownNow resolves the `closing` future as well.
366363 queue .put (TaskHandle .POISON );
367364
368- // Wait for both "send" and "recv" to exit.
369- CompletableFuture . allOf ( send , recv ) .get ();
365+ // Wait for both "send" to exit; "send" will not exit until "recv" completes .
366+ send .get ();
370367 closing .complete (null );
371368 } catch (Exception e ) {
372369 closing .completeExceptionally (e );
@@ -380,14 +377,8 @@ private void shutdownNow(Exception ex) {
380377 closing .completeExceptionally (ex );
381378 messages .onError (Status .INTERNAL .withCause (ex ).asRuntimeException ());
382379
383- // Terminate the "send" routine and wait for it to exit.
384- // Since we're already in the error state we do not care
385- // much if it throws or not.
380+ // Terminate the "send" routine.
386381 send .cancel (true );
387- try {
388- send .get ();
389- } catch (Exception e ) {
390- }
391382
392383 if (!closed ) {
393384 // Since shutdownNow is never triggered by the "main" thread,
@@ -419,24 +410,32 @@ void setState(State nextState) {
419410 }
420411 }
421412
422- /** Returns true if the next batch can be sent. */
423- boolean canSend () {
413+ /** Blocks until a change in {@link #state} causes the predicate to be true. */
414+ void awaitState (Predicate <State > predicate ) throws InterruptedException {
415+ requireNonNull (predicate , "predicate is null" );
416+
424417 lock .lock ();
425418 try {
426- return state .canSend ();
419+ while (!predicate .test (state )) {
420+ stateChanged .await ();
421+ }
427422 } finally {
428423 lock .unlock ();
429424 }
430425 }
431426
432- /** Returns true if the next batch can be assembled from the queued items. */
433- boolean canPrepareNext () {
434- lock .lock ();
435- try {
436- return state .canPrepareNext ();
437- } finally {
438- lock .unlock ();
439- }
427+ /** Open a new batching stream. */
428+ void openStream () {
429+ Recv events = new Recv (this );
430+ recv = events ;
431+ messages = streamFactory .createStream (events );
432+ messages .onNext (Message .start (collectionHandleDefaults .consistencyLevel ()));
433+ }
434+
435+ /** Close the client half of the stream. */
436+ void closeStream () {
437+ messages .onNext (Message .stop ());
438+ messages .onCompleted ();
440439 }
441440
442441 /**
@@ -450,6 +449,8 @@ boolean canPrepareNext() {
450449 * @see #scheduledService
451450 */
452451 private void onEvent (Event event ) {
452+ requireNonNull (event , "event is null" );
453+
453454 lock .lock ();
454455 try {
455456 System .out .println ("onEvent " + event );
@@ -463,6 +464,7 @@ private TaskHandle add(final TaskHandle taskHandle) throws InterruptedException
463464 if (closed ) {
464465 throw new IllegalStateException ("context is closed" );
465466 }
467+ requireNonNull (taskHandle , "taskHandle is null" );
466468
467469 TaskHandle existing = wip .get (taskHandle .id ());
468470 if (existing != null ) {
@@ -473,7 +475,7 @@ private TaskHandle add(final TaskHandle taskHandle) throws InterruptedException
473475 return taskHandle ;
474476 }
475477
476- private final class Send extends CompletableFuture < Void > implements Runnable {
478+ private final class Send implements Runnable {
477479
478480 @ Override
479481 public void run () {
@@ -483,25 +485,16 @@ public void run() {
483485 trySend ();
484486 } finally {
485487 Thread .currentThread ().setName (threadName );
486- complete (null );
487488 }
488489 }
489490
490- @ Override
491- public boolean cancel (boolean mayInterruptIfRunning ) {
492- if (mayInterruptIfRunning ) {
493- Thread .currentThread ().interrupt ();
494- }
495- return mayInterruptIfRunning ;
496- }
497-
498491 /**
499492 * trySend consumes {@link #queue} tasks and sends them in batches until it
500493 * encounters a {@link TaskHandle#POISON} or is otherwise interrupted.
501494 */
502495 private void trySend () {
503496 try {
504- awaitCanPrepareNext ( );
497+ awaitState ( State :: canPrepareNext );
505498
506499 while (!Thread .currentThread ().isInterrupted ()) {
507500 if (batch .isFull ()) {
@@ -515,9 +508,7 @@ private void trySend() {
515508 System .out .println ("took POISON" );
516509 drain ();
517510
518- // Close the client half of the stream.
519- messages .onNext (Message .stop ());
520- messages .onCompleted ();
511+ closeStream ();
521512
522513 // The SSB protocol requires the client to continue reading the stream
523514 // until EOF. In the happy case, the server will close its half having
@@ -582,49 +573,27 @@ private void drain() throws InterruptedException {
582573 assert batch .isEmpty () : "batch not empty after drain" ;
583574 }
584575
585- private void flush () throws InterruptedException {
586- awaitCanSend ();
587- messages .onNext (batch .prepare ());
588- setState (IN_FLIGHT );
589-
590- // When we get into OOM / ServerShuttingDown state, then we can be certain that
591- // there isn't any reason to keep waiting for the ACKs. However, we should not
592- // exit without either taking a poison pill from the queue,
593- // or being interrupted, as this risks blocking the producer (main) thread.
594- awaitCanPrepareNext ();
595- }
596-
597- /** Block until the current state allows {@link State#canSend}. */
598- private void awaitCanSend () throws InterruptedException {
599- lock .lock ();
600- try {
601- while (!canSend ()) {
602- stateChanged .await ();
603- }
604- } finally {
605- lock .unlock ();
606- }
607- }
608-
609576 /**
577+ * Block until the current state allows {@link State#canSend},
578+ * then prepare the batch, send it, and set InFlight state.
610579 * Block until the current state allows {@link State#canPrepareNext}.
611580 *
612- * <p >
581+ * <br >
613582 * Depending on the BatchContext lifecycle, the semantics of
614583 * "await can prepare next" can be one of "message is ACK'ed"
615584 * "the stream has started", or, more generally,
616585 * "it is safe to take a next item from the queue and add it to the batch".
586+ *
587+ * @see Batch#prepare
588+ * @see #IN_FLIGHT
617589 */
618- private void awaitCanPrepareNext () throws InterruptedException {
619- lock .lock ();
620- try {
621- while (!canPrepareNext ()) {
622- stateChanged .await ();
623- }
624- } finally {
625- lock .unlock ();
626- }
590+ private void flush () throws InterruptedException {
591+ awaitState (State ::canSend );
592+ messages .onNext (batch .prepare ());
593+ setState (IN_FLIGHT );
594+ awaitState (State ::canPrepareNext );
627595 }
596+
628597 }
629598
630599 private static final class Recv extends CompletableFuture <Void > implements StreamObserver <Event > {
@@ -639,7 +608,7 @@ public void onNext(Event event) {
639608 try {
640609 context .onEvent (event );
641610 } catch (Exception e ) {
642- context .onEvent (Event .StreamHangup . fromThrowable (e ));
611+ context .onEvent (new Event .ClientError (e ));
643612 }
644613 }
645614
@@ -668,22 +637,20 @@ public void onError(Throwable t) {
668637 }
669638 }
670639
671- final State AWAIT_STARTED = new BaseState ("AWAIT_STARTED" , BaseState .Action .PREPARE_NEXT ) {
640+ private final State AWAIT_STARTED = new BaseState ("AWAIT_STARTED" , BaseState .Action .PREPARE_NEXT ) {
672641 @ Override
673642 public void onEvent (Event event ) {
674- if (requireNonNull ( event , "event is null" ) == Event .STARTED ) {
643+ if (event == Event .STARTED ) {
675644 setState (ACTIVE );
676645 } else {
677646 super .onEvent (event );
678647 }
679648 }
680649 };
681- final State ACTIVE = new BaseState ("ACTIVE" , BaseState .Action .PREPARE_NEXT , BaseState .Action .SEND );
682- final State IN_FLIGHT = new BaseState ("IN_FLIGHT" ) {
650+ private final State ACTIVE = new BaseState ("ACTIVE" , BaseState .Action .PREPARE_NEXT , BaseState .Action .SEND );
651+ private final State IN_FLIGHT = new BaseState ("IN_FLIGHT" ) {
683652 @ Override
684653 public void onEvent (Event event ) {
685- requireNonNull (event , "event is null" );
686-
687654 if (event instanceof Event .Acks acks ) {
688655 Collection <String > removed = batch .clear ();
689656 if (!acks .acked ().containsAll (removed )) {
@@ -763,8 +730,6 @@ public boolean canPrepareNext() {
763730 */
764731 @ Override
765732 public void onEvent (Event event ) {
766- requireNonNull (event , "event is null" );
767-
768733 if (event instanceof Event .Results results ) {
769734 onResults (results );
770735 } else if (event instanceof Event .Backoff backoff ) {
@@ -847,7 +812,6 @@ private void initiateShutdown() {
847812
848813 @ Override
849814 public void onEvent (Event event ) {
850- requireNonNull (event , "event" );
851815 if (event == Event .SHUTTING_DOWN ||
852816 event instanceof StreamHangup ||
853817 event instanceof ClientError ) {
@@ -894,8 +858,7 @@ public boolean canSend() {
894858
895859 @ Override
896860 public void onEnter (State prev ) {
897- messages .onNext (Message .stop ());
898- messages .onCompleted ();
861+ closeStream ();
899862 }
900863 }
901864
0 commit comments