3838import org .apache .beam .sdk .metrics .Counter ;
3939import org .apache .beam .sdk .metrics .Metrics ;
4040import org .apache .beam .sdk .options .PipelineOptions ;
41+ import org .apache .beam .sdk .state .CombiningState ;
4142import org .apache .beam .sdk .state .TimeDomain ;
4243import org .apache .beam .sdk .transforms .windowing .BoundedWindow ;
4344import org .apache .beam .sdk .transforms .windowing .PaneInfo ;
@@ -107,6 +108,12 @@ public class ReduceFnRunner<K, InputT, OutputT, W extends BoundedWindow> {
107108 * <li>It uses discarding or accumulation mode according to the {@link WindowingStrategy}.
108109 * </ul>
109110 */
111+ static final StateTag <CombiningState <CombinedMetadata , CombinedMetadata , CombinedMetadata >>
112+ METADATA_TAG =
113+ StateTags .makeSystemTagInternal (
114+ StateTags .combiningValue (
115+ "combinedMetadata" , CombinedMetadata .Coder .of (), CombinedMetadataCombiner .of ()));
116+
110117 private final WindowingStrategy <Object , W > windowingStrategy ;
111118
112119 private final WindowedValueReceiver <KV <K , OutputT >> outputter ;
@@ -376,7 +383,7 @@ public void processElements(Iterable<WindowedValue<InputT>> values) throws Excep
376383 emit (
377384 contextFactory .base (window , StateStyle .DIRECT ),
378385 contextFactory .base (window , StateStyle .RENAMED ),
379- CausedByDrain . NORMAL );
386+ CombinedMetadata . createDefault () );
380387 }
381388
382389 // We're all done with merging and emitting elements so can compress the activeWindow state.
@@ -590,6 +597,7 @@ private void processElement(Map<W, W> windowToMergeResult, WindowedValue<InputT>
590597 value .getTimestamp (),
591598 StateStyle .DIRECT ,
592599 value .causedByDrain ());
600+
593601 if (triggerRunner .isClosed (directContext .state ())) {
594602 // This window has already been closed.
595603 droppedDueToClosedWindow .inc ();
@@ -604,6 +612,11 @@ private void processElement(Map<W, W> windowToMergeResult, WindowedValue<InputT>
604612 continue ;
605613 }
606614
615+ CombinedMetadata metadata = CombinedMetadata .create (value .causedByDrain ());
616+ if (!metadata .equals (CombinedMetadata .createDefault ())) {
617+ directContext .state ().access (METADATA_TAG ).add (metadata );
618+ }
619+
607620 activeWindows .ensureWindowIsActive (window );
608621 ReduceFn <K , InputT , OutputT , W >.ProcessValueContext renamedContext =
609622 contextFactory .forValue (
@@ -649,15 +662,15 @@ private class WindowActivation {
649662 // garbage collect the window. We'll consider any timer at or after the
650663 // end-of-window time to be a signal to garbage collect.
651664 public final boolean isGarbageCollection ;
652- public final CausedByDrain causedByDrain ;
665+ public final CombinedMetadata combinedMetadata ;
653666
654667 WindowActivation (
655668 ReduceFn <K , InputT , OutputT , W >.Context directContext ,
656669 ReduceFn <K , InputT , OutputT , W >.Context renamedContext ,
657- CausedByDrain causedByDrain ) {
670+ CombinedMetadata combinedMetadata ) {
658671 this .directContext = directContext ;
659672 this .renamedContext = renamedContext ;
660- this .causedByDrain = causedByDrain ;
673+ this .combinedMetadata = combinedMetadata ;
661674 W window = directContext .window ();
662675
663676 // The output watermark is before the end of the window if it is either unknown
@@ -742,7 +755,8 @@ public void onTimers(Iterable<TimerData> timers) throws Exception {
742755 ReduceFn <K , InputT , OutputT , W >.Context renamedContext =
743756 contextFactory .base (window , StateStyle .RENAMED );
744757 WindowActivation windowActivation =
745- new WindowActivation (directContext , renamedContext , timer .causedByDrain ());
758+ new WindowActivation (
759+ directContext , renamedContext , CombinedMetadata .create (timer .causedByDrain ()));
746760 windowActivations .put (window , windowActivation );
747761
748762 // Perform prefetching of state to determine if the trigger should fire.
@@ -778,7 +792,7 @@ public void onTimers(Iterable<TimerData> timers) throws Exception {
778792 directContext .window (),
779793 timerInternals .currentInputWatermarkTime (),
780794 timerInternals .currentOutputWatermarkTime (),
781- windowActivation .causedByDrain );
795+ windowActivation .combinedMetadata . causedByDrain () );
782796
783797 boolean windowIsActiveAndOpen = windowActivation .windowIsActiveAndOpen ();
784798 if (windowIsActiveAndOpen ) {
@@ -792,7 +806,7 @@ public void onTimers(Iterable<TimerData> timers) throws Exception {
792806 renamedContext ,
793807 true /* isFinished */ ,
794808 windowActivation .isEndOfWindow ,
795- windowActivation .causedByDrain );
809+ windowActivation .combinedMetadata );
796810 checkState (newHold == null , "Hold placed at %s despite isFinished being true." , newHold );
797811 }
798812
@@ -810,7 +824,7 @@ public void onTimers(Iterable<TimerData> timers) throws Exception {
810824 if (windowActivation .windowIsActiveAndOpen ()
811825 && triggerRunner .shouldFire (
812826 directContext .window (), directContext .timers (), directContext .state ())) {
813- emit (directContext , renamedContext , windowActivation .causedByDrain );
827+ emit (directContext , renamedContext , windowActivation .combinedMetadata );
814828 }
815829
816830 if (windowActivation .isEndOfWindow ) {
@@ -874,6 +888,7 @@ private void clearAllState(
874888 triggerRunner .clearState (
875889 directContext .window (), directContext .timers (), directContext .state ());
876890 paneInfoTracker .clear (directContext .state ());
891+ directContext .state ().access (METADATA_TAG ).clear ();
877892 } else {
878893 // If !windowIsActiveAndOpen then !activeWindows.isActive (1) or triggerRunner.isClosed (2).
879894 // For (1), if !activeWindows.isActive then the window must be merging and has been
@@ -934,8 +949,9 @@ private void prefetchEmit(
934949 private void emit (
935950 ReduceFn <K , InputT , OutputT , W >.Context directContext ,
936951 ReduceFn <K , InputT , OutputT , W >.Context renamedContext ,
937- CausedByDrain causedByDrain )
952+ CombinedMetadata metadata )
938953 throws Exception {
954+
939955 checkState (
940956 triggerRunner .shouldFire (
941957 directContext .window (), directContext .timers (), directContext .state ()));
@@ -950,13 +966,14 @@ private void emit(
950966 // Run onTrigger to produce the actual pane contents.
951967 // As a side effect it will clear all element holds, but not necessarily any
952968 // end-of-window or garbage collection holds.
953- onTrigger (directContext , renamedContext , isFinished , false /*isEndOfWindow*/ , causedByDrain );
969+ onTrigger (directContext , renamedContext , isFinished , false /*isEndOfWindow*/ , metadata );
954970
955971 // Now that we've triggered, the pane is empty.
956972 nonEmptyPanes .clearPane (renamedContext .state ());
957973
958974 // Cleanup buffered data if appropriate
959975 if (shouldDiscard ) {
976+ directContext .state ().access (METADATA_TAG ).clear ();
960977 // Cleanup flavor C: The user does not want any buffered data to persist between panes.
961978 reduceFn .clearState (renamedContext );
962979 }
@@ -1009,8 +1026,22 @@ private void prefetchOnTrigger(
10091026 ReduceFn <K , InputT , OutputT , W >.Context renamedContext ,
10101027 final boolean isFinished ,
10111028 boolean isEndOfWindow ,
1012- CausedByDrain causedByDrain )
1029+ CombinedMetadata metadata )
10131030 throws Exception {
1031+ CombiningState <CombinedMetadata , CombinedMetadata , CombinedMetadata > metadataState =
1032+ directContext .state ().access (METADATA_TAG );
1033+ CombinedMetadata aggregatedMetadata = metadataState .read ();
1034+ if (aggregatedMetadata == null ) {
1035+ aggregatedMetadata = CombinedMetadata .createDefault ();
1036+ }
1037+ CombinedMetadata fullyAggregatedMetadata =
1038+ CombinedMetadataCombiner .of ().addInput (aggregatedMetadata , metadata );
1039+ final CausedByDrain aggregatedCausedByDrain = fullyAggregatedMetadata .causedByDrain ();
1040+ if (isFinished ) {
1041+ metadataState .clear ();
1042+ } else if (!metadata .equals (CombinedMetadata .createDefault ())) {
1043+ metadataState .add (metadata );
1044+ }
10141045 // Extract the window hold, and as a side effect clear it.
10151046 final WatermarkHold .OldAndNewHolds pair =
10161047 watermarkHold .extractAndRelease (renamedContext , isFinished ).read ();
@@ -1081,12 +1112,12 @@ private void prefetchOnTrigger(
10811112 .setValue (KV .of (key , toOutput ))
10821113 .setTimestamp (outputTimestamp )
10831114 .setWindows (windows )
1084- .setCausedByDrain (causedByDrain )
1115+ .setCausedByDrain (aggregatedCausedByDrain )
10851116 .setPaneInfo (paneInfo )
10861117 .setReceiver (outputter )
10871118 .output ();
10881119 },
1089- causedByDrain );
1120+ aggregatedCausedByDrain );
10901121
10911122 reduceFn .onTrigger (renamedTriggerContext );
10921123 }
0 commit comments