1717 */
1818package org .apache .beam .sdk .fn .data ;
1919
20+ import static org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .base .Preconditions .checkNotNull ;
21+
2022import java .io .IOException ;
2123import java .util .Collections ;
2224import java .util .HashMap ;
2830import java .util .concurrent .Executors ;
2931import java .util .concurrent .ScheduledFuture ;
3032import java .util .concurrent .TimeUnit ;
31- import java .util .function .Supplier ;
3233import javax .annotation .Nullable ;
3334import javax .annotation .concurrent .NotThreadSafe ;
3435import org .apache .beam .model .fnexecution .v1 .BeamFnApi .Elements ;
5657@ SuppressWarnings ({
5758 "nullness" // TODO(https://github.com/apache/beam/issues/20497)
5859})
59- // The calling thread that invokes sendBufferedDataAndFinishOutboundStreams synchronizes on
60+ // The calling thread that invokes sendOrCollectBufferedDataAndFinishOutboundStreams synchronizes on
6061// flushLock effectively making the periodic flushing no longer read or mutate hasFlushedForBundle
6162// and allowing the calling thread to read and mutate hasFlushedForBundle safely without needing to
6263// create another memory barrier. Also note that flush is always invoked when synchronizing on
@@ -72,33 +73,40 @@ public class BeamFnDataOutboundAggregator {
7273 private static final Logger LOG = LoggerFactory .getLogger (BeamFnDataOutboundAggregator .class );
7374 private final int sizeLimit ;
7475 private final long timeLimit ;
75- private final Supplier < String > processBundleRequestIdSupplier ;
76+ private String instructionId ;
7677 @ VisibleForTesting final Map <String , Receiver <?>> outputDataReceivers ;
7778 @ VisibleForTesting final Map <TimerEndpoint , Receiver <?>> outputTimersReceivers ;
78- private final StreamObserver <Elements > outboundObserver ;
79+ @ Nullable private StreamObserver <Elements > outboundObserver ;
7980 @ Nullable @ VisibleForTesting ScheduledFuture <?> flushFuture ;
8081 private long bytesWrittenSinceFlush ;
8182 private final Object flushLock ;
8283 private final boolean collectElementsIfNoFlushes ;
8384 private boolean hasFlushedForBundle ;
8485
85- public BeamFnDataOutboundAggregator (
86- PipelineOptions options ,
87- Supplier <String > processBundleRequestIdSupplier ,
88- StreamObserver <Elements > outboundObserver ,
89- boolean collectElementsIfNoFlushes ) {
86+ public BeamFnDataOutboundAggregator (PipelineOptions options , boolean collectElementsIfNoFlushes ) {
9087 this .sizeLimit = getSizeLimit (options );
9188 this .timeLimit = getTimeLimit (options );
9289 this .collectElementsIfNoFlushes = collectElementsIfNoFlushes ;
9390 this .outputDataReceivers = new HashMap <>();
9491 this .outputTimersReceivers = new HashMap <>();
95- this .outboundObserver = outboundObserver ;
96- this .processBundleRequestIdSupplier = processBundleRequestIdSupplier ;
9792 this .bytesWrittenSinceFlush = 0L ;
9893 this .flushLock = new Object ();
9994 this .hasFlushedForBundle = false ;
10095 }
10196
97+ public void prepareForInstruction (
98+ String instructionId , StreamObserver <Elements > outboundObserver ) {
99+ if (timeLimit > 0 ) {
100+ synchronized (flushLock ) {
101+ this .instructionId = instructionId ;
102+ this .outboundObserver = outboundObserver ;
103+ }
104+ } else {
105+ this .instructionId = instructionId ;
106+ this .outboundObserver = outboundObserver ;
107+ }
108+ }
109+
102110 /** Starts the flushing daemon thread if data_buffer_time_limit_ms is set. */
103111 public void start () {
104112 if (timeLimit > 0 && this .flushFuture == null ) {
@@ -166,7 +174,7 @@ private void flushInternal() {
166174 }
167175 Elements .Builder elements = convertBufferForTransmission ();
168176 if (elements .getDataCount () > 0 || elements .getTimersCount () > 0 ) {
169- outboundObserver .onNext (elements .build ());
177+ checkNotNull ( outboundObserver ) .onNext (elements .build ());
170178 }
171179 hasFlushedForBundle = true ;
172180 }
@@ -177,6 +185,7 @@ private void flushInternal() {
177185 * collectElementsIfNoFlushes=true, and there was no previous flush in this bundle, otherwise
178186 * returns null.
179187 */
188+ @ Nullable
180189 public Elements sendOrCollectBufferedDataAndFinishOutboundStreams () {
181190 if (outputTimersReceivers .isEmpty () && outputDataReceivers .isEmpty ()) {
182191 return null ;
@@ -189,16 +198,17 @@ public Elements sendOrCollectBufferedDataAndFinishOutboundStreams() {
189198 } else {
190199 bufferedElements = convertBufferForTransmission ();
191200 }
201+ checkNotNull (instructionId );
192202 LOG .debug (
193203 "Closing streams for instruction {} and outbound data {} and timers {}." ,
194- processBundleRequestIdSupplier . get () ,
204+ instructionId ,
195205 outputDataReceivers ,
196206 outputTimersReceivers );
197207 for (Map .Entry <String , Receiver <?>> entry : outputDataReceivers .entrySet ()) {
198208 String pTransformId = entry .getKey ();
199209 bufferedElements
200210 .addDataBuilder ()
201- .setInstructionId (processBundleRequestIdSupplier . get () )
211+ .setInstructionId (instructionId )
202212 .setTransformId (pTransformId )
203213 .setIsLast (true );
204214 entry .getValue ().resetStats ();
@@ -207,34 +217,37 @@ public Elements sendOrCollectBufferedDataAndFinishOutboundStreams() {
207217 TimerEndpoint timerKey = entry .getKey ();
208218 bufferedElements
209219 .addTimersBuilder ()
210- .setInstructionId (processBundleRequestIdSupplier . get () )
220+ .setInstructionId (instructionId )
211221 .setTransformId (timerKey .pTransformId )
212222 .setTimerFamilyId (timerKey .timerFamilyId )
213223 .setIsLast (true );
214224 entry .getValue ().resetStats ();
215225 }
226+ // This is the end of the bundle so we reset state to prepare for future bundles.
227+ instructionId = null ;
216228 if (collectElementsIfNoFlushes && !hasFlushedForBundle ) {
229+ outboundObserver = null ;
217230 return bufferedElements .build ();
218231 }
219- outboundObserver .onNext (bufferedElements .build ());
220- // This is now at the end of a bundle, so we reset hasFlushedForBundle to prepare for new
221- // bundles.
232+ checkNotNull (outboundObserver ).onNext (bufferedElements .build ());
233+ outboundObserver = null ;
222234 hasFlushedForBundle = false ;
223235 return null ;
224236 }
225237
226238 // Send the elements to the StreamObserver associated with this aggregator.
227239 public void sendElements (Elements elements ) {
228- outboundObserver .onNext (elements );
240+ checkNotNull ( outboundObserver ) .onNext (elements );
229241 }
230242
231243 public void discard () {
232244 if (flushFuture != null ) {
233- flushFuture .cancel (true );
245+ flushFuture .cancel (false );
234246 }
235247 }
236248
237249 private Elements .Builder convertBufferForTransmission () {
250+ checkNotNull (instructionId );
238251 Elements .Builder bufferedElements = Elements .newBuilder ();
239252 for (Map .Entry <String , Receiver <?>> entry : outputDataReceivers .entrySet ()) {
240253 if (!entry .getValue ().hasBufferedOutput ()) {
@@ -243,7 +256,7 @@ private Elements.Builder convertBufferForTransmission() {
243256 ByteString bytes = entry .getValue ().toByteStringAndResetBuffer ();
244257 bufferedElements
245258 .addDataBuilder ()
246- .setInstructionId (processBundleRequestIdSupplier . get () )
259+ .setInstructionId (instructionId )
247260 .setTransformId (entry .getKey ())
248261 .setData (bytes );
249262 }
@@ -254,7 +267,7 @@ private Elements.Builder convertBufferForTransmission() {
254267 ByteString bytes = entry .getValue ().toByteStringAndResetBuffer ();
255268 bufferedElements
256269 .addTimersBuilder ()
257- .setInstructionId (processBundleRequestIdSupplier . get () )
270+ .setInstructionId (instructionId )
258271 .setTransformId (entry .getKey ().pTransformId )
259272 .setTimerFamilyId (entry .getKey ().timerFamilyId )
260273 .setTimers (bytes );
@@ -353,10 +366,12 @@ public void accept(T input) throws Exception {
353366 }
354367 }
355368
369+ @ VisibleForTesting
356370 public long getByteCount () {
357371 return perBundleByteCount ;
358372 }
359373
374+ @ VisibleForTesting
360375 public long getElementCount () {
361376 return perBundleElementCount ;
362377 }
0 commit comments