4444 * BatchContext stores the state of an active batch process
4545 * and controls its lifecycle.
4646 *
47- * <h2>State</h2>
48- *
4947 * <h2>Lifecycle</h2>
5048 *
49+ * The SSB implementation is based on gRPC bidi-streams, which are modeled as a
50+ * pair of "observers" that define callbacks for inbound and outbound messages.
51+ * We'll refer to them as "sender" (sending messages to the server) and "recv"
52+ * (receiving and handing server-side events).
53+ *
54+ * <p>
55+ * When the context is started, the client exchanges a "recv" for a "sender",
56+ * then stores in {@link #messages}. A {@link Send} process is started in the
57+ * {@link #sendService} -- it will continue to run until the context is closed
58+ * either gracefully via {@link #close} or abruptly via {@link #shutdownNow}.
59+ *
60+ * <p>
61+ * A "recv" always runs on some internal gRPC thread. The "recv" process is
62+ * expected to exit whenever server closes its half of the stream, and will
63+ * be re-created if the stream is re-opened. {@link Recv} delegates most
64+ * of the operations to its parent BatchContext.
65+ *
66+ * <p>
67+ * Collectively, "sender" and "recv" are referred to as "workers".
68+ * After both "workers" exit the {@link #workers} count is expected to be 0.
69+ *
70+ * <h2>State</h2>
71+ *
5172 * <h2>Cancellation policy</h2>
5273 *
5374 * @param <PropertiesT> the shape of properties for inserted objects.
75+ *
76+ * @see StreamObserver
5477 */
5578public final class BatchContext <PropertiesT > implements Closeable {
5679 private final int maxReconnectRetries ;
@@ -69,15 +92,15 @@ public final class BatchContext<PropertiesT> implements Closeable {
6992 * thread; the latter may be blocked on {@link Send#awaitCanSend} or
7093 * {@link Send#awaitCanPrepareNext}.
7194 */
72- private final ExecutorService sendExec = Executors .newSingleThreadExecutor ();
95+ private final ExecutorService sendService = Executors .newSingleThreadExecutor ();
7396
7497 /**
7598 * Scheduled thread pool for delayed tasks.
7699 *
77100 * @see Oom
78101 * @see Reconnecting
79102 */
80- private final ScheduledExecutorService scheduledExec = Executors .newScheduledThreadPool (1 );
103+ private final ScheduledExecutorService scheduledService = Executors .newScheduledThreadPool (1 );
81104
82105 /** The thread that created the context. */
83106 private final Thread parent = Thread .currentThread ();
@@ -99,7 +122,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
99122 /**
100123 * Work-in-progress items.
101124 *
102- * An item is added to the wip map after the Sender successfully
125+ * An item is added to the wip map after the "sender" successfully
103126 * adds it to the {@link #batch} and is removed once the server reports
104127 * back the result (whether success of failure).
105128 */
@@ -109,7 +132,7 @@ public final class BatchContext<PropertiesT> implements Closeable {
109132 * Current batch.
110133 *
111134 * <p>
112- * An item is added to the batch after the Sender pulls it
135+ * An item is added to the batch after the "sender" pulls it
113136 * from the queue and remains there until it's Ack'ed.
114137 */
115138 private final Batch batch ;
@@ -144,8 +167,8 @@ public final class BatchContext<PropertiesT> implements Closeable {
144167 /** closing completes the stream. */
145168 private final CompletableFuture <Void > closing = new CompletableFuture <>();
146169
147- /** Executor for performing the shutdown sequence. */
148- private final ExecutorService shutdownExec = Executors .newSingleThreadExecutor ();
170+ /** Executor for performing graceful shutdown sequence. */
171+ private final ExecutorService shutdownService = Executors .newSingleThreadExecutor ();
149172
150173 /** Lightway check to ensure users cannot send on a closed context. */
151174 private volatile boolean closed ;
@@ -202,7 +225,7 @@ void start() {
202225
203226 // "send" routine must start after the nextState has been set.
204227 setState (AWAIT_STARTED );
205- send = sendExec .submit (new Send ());
228+ send = sendService .submit (new Send ());
206229 }
207230
208231 /**
@@ -261,27 +284,27 @@ public void close() throws IOException {
261284 }
262285 throw new IOException (e .getCause ());
263286 } finally {
264- shutdownExecutors ();
287+ shutdownExecutionServices ();
265288 }
266289 }
267290
291+ /** Start a graceful context shutdown. */
268292 private void shutdown () {
269- CompletableFuture . runAsync (() -> {
293+ shutdownService . execute (() -> {
270294 try {
271295 // Poison the queue -- this will signal "send" to drain the remaining
272296 // items in the batch and in the backlog and exit.
273297 //
274298 // If shutdownNow has been called previously and the "send" routine
275299 // has been interrupted, this would block indefinitely.
276300 // Luckily, shutdownNow resolves the `closing` future as well.
277- System .out .println ("POISON THE QUEUE" );
278301 queue .put (TaskHandle .POISON );
279302
280303 // Wait for the send to exit before closing our end of the stream.
281304 try {
282305 send .get ();
283306 } catch (CancellationException ignored ) {
284- // Send task can be cancelled due to a reconnect or an internal error.
307+ // "sender" can be cancelled due to a reconnect or an internal error.
285308 }
286309
287310 // Wait for both "send" and "recv" to exit.
@@ -290,9 +313,10 @@ private void shutdown() {
290313 } catch (Exception e ) {
291314 closing .completeExceptionally (e );
292315 }
293- }, shutdownExec );
316+ });
294317 }
295318
319+ /** Terminate context abruptly. */
296320 private void shutdownNow (Exception ex ) {
297321 // Now report this error to the server and terminate the stream.
298322 closing .completeExceptionally (ex );
@@ -315,21 +339,21 @@ private void shutdownNow(Exception ex) {
315339 }
316340 }
317341
318- private void shutdownExecutors () {
342+ private void shutdownExecutionServices () {
319343 BiConsumer <String , List <Runnable >> assertEmpty = (name , pending ) -> {
320344 assert pending .isEmpty () : "'%s' service had %d tasks awaiting execution"
321345 .formatted (pending .size (), name );
322346 };
323347
324348 List <Runnable > pending ;
325349
326- pending = sendExec .shutdownNow ();
350+ pending = sendService .shutdownNow ();
327351 assertEmpty .accept ("send" , pending );
328352
329- pending = scheduledExec .shutdownNow ();
353+ pending = scheduledService .shutdownNow ();
330354 assertEmpty .accept ("oom" , pending );
331355
332- pending = shutdownExec .shutdownNow ();
356+ pending = shutdownService .shutdownNow ();
333357 assertEmpty .accept ("shutdown" , pending );
334358 }
335359
@@ -377,7 +401,7 @@ boolean canPrepareNext() {
377401 * on a gRPC thread. {@link State} implementations SHOULD offload any
378402 * blocking operations to one of the provided executors.
379403 *
380- * @see #scheduledExec
404+ * @see #scheduledService
381405 */
382406 private void onEvent (Event event ) {
383407 lock .lock ();
@@ -719,7 +743,7 @@ private Oom(long delaySeconds) {
719743
720744 @ Override
721745 public void onEnter (State prev ) {
722- shutdown = scheduledExec .schedule (this ::initiateShutdown , delaySeconds , TimeUnit .SECONDS );
746+ shutdown = scheduledService .schedule (this ::initiateShutdown , delaySeconds , TimeUnit .SECONDS );
723747 }
724748
725749 /** Imitate server shutdown sequence. */
@@ -748,7 +772,7 @@ public void onEvent(Event event) {
748772 shutdown .get ();
749773 } catch (CancellationException ignored ) {
750774 } catch (InterruptedException ignored ) {
751- // Recv is running on a thread from gRPC's internal thread pool,
775+ // "recv" is running on a thread from gRPC's internal thread pool,
752776 // so, while onEvent allows InterruptedException to stay responsive,
753777 // in practice this thread will only be interrupted by the thread pool,
754778 // which already knows it's being shut down.
@@ -763,7 +787,7 @@ public void onEvent(Event event) {
763787 /**
764788 * ServerShuttingDown allows preparing the next batch
765789 * unless the server's OOM'ed on the previous one.
766- * Once set, the state will shutdown {@link BatchContext#sendExec }
790+ * Once set, the state will shutdown {@link BatchContext#sendService }
767791 * to instruct the "send" thread to close our part of the stream.
768792 */
769793 private final class ServerShuttingDown extends BaseState {
@@ -863,7 +887,7 @@ private void reconnectNow() {
863887 * Schedule a task to {@link #reconnect} after a delay.
864888 *
865889 * <p>
866- * The task is scheduled on {@link #scheduledExec } even if
890+ * The task is scheduled on {@link #scheduledService } even if
867891 * {@code delaySeconds == 0} to avoid blocking gRPC worker
868892 * thread,
869893 * where the {@link BatchContext#onEvent} callback runs.
@@ -873,7 +897,7 @@ private void reconnectNow() {
873897 private void reconnectAfter (long delaySeconds ) {
874898 retries ++;
875899
876- scheduledExec .schedule (() -> {
900+ scheduledService .schedule (() -> {
877901 try {
878902 reconnect ();
879903 } catch (InterruptedException e ) {
0 commit comments