Skip to content

Commit 3cca8fa

Browse files
authored
[Dataflow Streaming] Access state/timerinternals via StepContext (#38991)
1 parent e3b4d8c commit 3cca8fa

12 files changed

Lines changed: 156 additions & 18 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"comment": "Modify this file in a trivial way to cause this test suite to run!",
3-
"modification": 1,
3+
"modification": 2,
44
}

runners/core-java/src/main/java/org/apache/beam/runners/core/DoFnRunners.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ public static <InputT, OutputT> DoFnRunner<InputT, OutputT> simpleRunner(
8585
public static <K, InputT, OutputT, W extends BoundedWindow>
8686
DoFnRunner<KeyedWorkItem<K, InputT>, KV<K, OutputT>> lateDataDroppingRunner(
8787
DoFnRunner<KeyedWorkItem<K, InputT>, KV<K, OutputT>> wrappedRunner,
88-
TimerInternals timerInternals,
88+
StepContext stepContext,
8989
WindowingStrategy<?, W> windowingStrategy) {
90-
return new LateDataDroppingDoFnRunner<>(wrappedRunner, windowingStrategy, timerInternals);
90+
return new LateDataDroppingDoFnRunner<>(wrappedRunner, windowingStrategy, stepContext);
9191
}
9292

9393
/**

runners/core-java/src/main/java/org/apache/beam/runners/core/LateDataDroppingDoFnRunner.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ public class LateDataDroppingDoFnRunner<K, InputT, OutputT, W extends BoundedWin
5858
public LateDataDroppingDoFnRunner(
5959
DoFnRunner<KeyedWorkItem<K, InputT>, KV<K, OutputT>> doFnRunner,
6060
WindowingStrategy<?, ?> windowingStrategy,
61-
TimerInternals timerInternals) {
61+
StepContext stepContext) {
6262
this.doFnRunner = doFnRunner;
63-
lateDataFilter = new LateDataFilter(windowingStrategy, timerInternals);
63+
lateDataFilter = new LateDataFilter(windowingStrategy, stepContext);
6464
}
6565

6666
@Override
@@ -116,13 +116,12 @@ public <KeyT> void onWindowExpiration(BoundedWindow window, Instant timestamp, K
116116
@VisibleForTesting
117117
static class LateDataFilter {
118118
private final WindowingStrategy<?, ?> windowingStrategy;
119-
private final TimerInternals timerInternals;
119+
private final StepContext stepContext;
120120
private final Counter droppedDueToLateness;
121121

122-
public LateDataFilter(
123-
WindowingStrategy<?, ?> windowingStrategy, TimerInternals timerInternals) {
122+
public LateDataFilter(WindowingStrategy<?, ?> windowingStrategy, StepContext stepContext) {
124123
this.windowingStrategy = windowingStrategy;
125-
this.timerInternals = timerInternals;
124+
this.stepContext = stepContext;
126125
this.droppedDueToLateness =
127126
Metrics.counter(LateDataDroppingDoFnRunner.class, DROPPED_DUE_TO_LATENESS);
128127
}
@@ -146,8 +145,8 @@ public <K, InputT> Iterable<WindowedValue<InputT>> filter(
146145
element.getTimestamp(),
147146
key,
148147
window,
149-
timerInternals.currentInputWatermarkTime(),
150-
timerInternals.currentOutputWatermarkTime());
148+
stepContext.timerInternals().currentInputWatermarkTime(),
149+
stepContext.timerInternals().currentOutputWatermarkTime());
151150
} else {
152151
nonLateElements.add(
153152
WindowedValues.of(
@@ -173,7 +172,7 @@ public <K, InputT> Iterable<WindowedValue<InputT>> filter(
173172
* @return True if element can be dropped.
174173
*/
175174
private boolean canDropDueToExpiredWindow(BoundedWindow window) {
176-
Instant inputWM = timerInternals.currentInputWatermarkTime();
175+
Instant inputWM = stepContext.timerInternals().currentInputWatermarkTime();
177176
return LateDataUtils.garbageCollectionTime(window, windowingStrategy).isBefore(inputWM);
178177
}
179178
}

runners/core-java/src/test/java/org/apache/beam/runners/core/LateDataDroppingDoFnRunnerTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class LateDataDroppingDoFnRunnerTest {
4949
private static final FixedWindows WINDOW_FN = FixedWindows.of(Duration.millis(10));
5050

5151
@Mock private TimerInternals mockTimerInternals;
52+
@Mock private StepContext mockStepContext;
5253

5354
@Before
5455
public void setUp() {
@@ -60,9 +61,10 @@ public void testLateDataFilter() throws Exception {
6061
MetricsContainerImpl container = new MetricsContainerImpl("any");
6162
MetricsEnvironment.setCurrentContainer(container);
6263
when(mockTimerInternals.currentInputWatermarkTime()).thenReturn(new Instant(15L));
64+
when(mockStepContext.timerInternals()).thenReturn(mockTimerInternals);
6365

6466
LateDataFilter lateDataFilter =
65-
new LateDataFilter(WindowingStrategy.of(WINDOW_FN), mockTimerInternals);
67+
new LateDataFilter(WindowingStrategy.of(WINDOW_FN), mockStepContext);
6668

6769
Iterable<WindowedValue<Integer>> actual =
6870
lateDataFilter.filter(

runners/core-java/src/test/java/org/apache/beam/runners/core/ReduceFnTester.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.beam.sdk.state.TimeDomain;
4848
import org.apache.beam.sdk.transforms.Combine.CombineFn;
4949
import org.apache.beam.sdk.transforms.CombineWithContext.CombineFnWithContext;
50+
import org.apache.beam.sdk.transforms.DoFn.BundleFinalizer;
5051
import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
5152
import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
5253
import org.apache.beam.sdk.transforms.windowing.PaneInfo;
@@ -94,6 +95,23 @@ public class ReduceFnTester<InputT, OutputT, W extends BoundedWindow> {
9495
private final TestInMemoryStateInternals<String> stateInternals =
9596
new TestInMemoryStateInternals<>(KEY);
9697
private final InMemoryTimerInternals timerInternals = new InMemoryTimerInternals();
98+
private final StepContext stepContext =
99+
new StepContext() {
100+
@Override
101+
public StateInternals stateInternals() {
102+
return stateInternals;
103+
}
104+
105+
@Override
106+
public TimerInternals timerInternals() {
107+
return timerInternals;
108+
}
109+
110+
@Override
111+
public BundleFinalizer bundleFinalizer() {
112+
throw new UnsupportedOperationException();
113+
}
114+
};
97115

98116
private final WindowFn<Object, W> windowFn;
99117
private final TestWindowedValueReceiver testOutputter;
@@ -577,7 +595,7 @@ public final void injectElements(List<TimestampedValue<InputT>> values) throws E
577595

578596
ReduceFnRunner<String, InputT, OutputT, W> runner = createRunner();
579597
runner.processElements(
580-
new LateDataDroppingDoFnRunner.LateDataFilter(objectStrategy, timerInternals)
598+
new LateDataDroppingDoFnRunner.LateDataFilter(objectStrategy, stepContext)
581599
.filter(KEY, inputs));
582600

583601
// Persist after each bundle.

runners/flink/src/main/java/org/apache/beam/runners/flink/translation/wrappers/streaming/WindowDoFnOperator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ protected DoFnRunner<KeyedWorkItem<K, InputT>, KV<K, OutputT>> createWrappingDoF
118118
// for some K, V
119119

120120
return DoFnRunners.lateDataDroppingRunner(
121-
(DoFnRunner) doFnRunner, timerInternals, windowingStrategy);
121+
(DoFnRunner) doFnRunner, stepContext, windowingStrategy);
122122
}
123123

124124
@Override

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/GroupAlsoByWindowsParDoFn.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ private DoFnRunner<InputT, KV<K, Iterable<V>>> createRunner() {
216216
}
217217
return (DoFnRunner<InputT, KV<K, Iterable<V>>>)
218218
DoFnRunners.<K, V, Iterable<V>, W>lateDataDroppingRunner(
219-
streamingGABWRunner, stepContext.timerInternals(), windowingStrategy);
219+
streamingGABWRunner, stepContext, windowingStrategy);
220220
} else {
221221
if (hasStreamingSideInput) {
222222
return new StreamingSideInputDoFnRunner<>(

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/StreamingModeExecutionContext.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,26 @@ public void flushState() {
891891
checkStateNotNull(systemTimerInternals).persistTo(builder);
892892
checkStateNotNull(userTimerInternals).persistTo(builder);
893893
}
894+
poisonStateAndTimerInternals();
895+
}
896+
897+
/**
898+
* Poisons the state and timer internals to prevent any subsequent (stale) usage.
899+
*
900+
* <p>This ensures that if these key-specific internals are incorrectly cached and used after
901+
* the key's execution context has finished, it will fail fast with a clear error rather than
902+
* silently corrupting state.
903+
*/
904+
private void poisonStateAndTimerInternals() {
905+
if (stateInternals != null) {
906+
stateInternals.poison();
907+
}
908+
if (systemTimerInternals != null) {
909+
systemTimerInternals.poison();
910+
}
911+
if (userTimerInternals != null) {
912+
userTimerInternals.poison();
913+
}
894914
}
895915

896916
@Override

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/WindmillTimerInternals.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ class WindmillTimerInternals implements TimerInternals {
6464
private final Consumer<TimerData> onTimerModified;
6565
private final WindmillTagEncoding windmillTagEncoding;
6666

67+
private boolean poisoned = false;
68+
69+
public void poison() {
70+
this.poisoned = true;
71+
}
72+
73+
private void checkNotPoisoned() {
74+
if (poisoned) {
75+
throw new IllegalStateException(
76+
"WindmillTimerInternals is poisoned and cannot be used after flushState().");
77+
}
78+
}
79+
6780
public WindmillTimerInternals(
6881
String stateFamily, // unique identifies a step
6982
WindmillTimerType type,
@@ -80,12 +93,14 @@ public WindmillTimerInternals(
8093
}
8194

8295
public WindmillTimerInternals withType(WindmillTimerType type) {
96+
checkNotPoisoned();
8397
return new WindmillTimerInternals(
8498
stateFamily, type, processingTime, watermarks, windmillTagEncoding, onTimerModified);
8599
}
86100

87101
@Override
88102
public void setTimer(TimerData timerKey) {
103+
checkNotPoisoned();
89104
String timerDataKey = getTimerDataKey(timerKey.getTimerId(), timerKey.getTimerFamilyId());
90105
timerMap.put(
91106
new SimpleEntry<>(timerDataKey, timerKey.getNamespace()),
@@ -101,6 +116,7 @@ public void setTimer(
101116
Instant timestamp,
102117
Instant outputTimestamp,
103118
TimeDomain timeDomain) {
119+
checkNotPoisoned();
104120
TimerData timer =
105121
TimerData.of(
106122
timerId,
@@ -124,6 +140,7 @@ private static String getTimerDataKey(String timerId, String timerFamilyId) {
124140

125141
@Override
126142
public void deleteTimer(TimerData timerKey) {
143+
checkNotPoisoned();
127144
String timerDataKey = getTimerDataKey(timerKey.getTimerId(), timerKey.getTimerFamilyId());
128145
timerMap.put(
129146
new SimpleEntry<>(timerDataKey, timerKey.getNamespace()),
@@ -139,6 +156,7 @@ public void deleteTimer(StateNamespace namespace, String timerId, String timerFa
139156
@Override
140157
public void deleteTimer(
141158
StateNamespace namespace, String timerId, String timerFamilyId, TimeDomain timeDomain) {
159+
checkNotPoisoned();
142160
deleteTimer(
143161
TimerData.of(
144162
timerId,
@@ -152,12 +170,14 @@ public void deleteTimer(
152170

153171
@Override
154172
public Instant currentProcessingTime() {
173+
checkNotPoisoned();
155174
Instant now = Instant.now();
156175
return processingTime.isAfter(now) ? processingTime : now;
157176
}
158177

159178
@Override
160179
public @Nullable Instant currentSynchronizedProcessingTime() {
180+
checkNotPoisoned();
161181
return watermarks.synchronizedProcessingTime();
162182
}
163183

@@ -172,6 +192,7 @@ public Instant currentProcessingTime() {
172192
*/
173193
@Override
174194
public Instant currentInputWatermarkTime() {
195+
checkNotPoisoned();
175196
return watermarks.inputDataWatermark();
176197
}
177198

@@ -186,10 +207,12 @@ public Instant currentInputWatermarkTime() {
186207
*/
187208
@Override
188209
public @Nullable Instant currentOutputWatermarkTime() {
210+
checkNotPoisoned();
189211
return watermarks.outputDataWatermark();
190212
}
191213

192214
public void persistTo(Windmill.WorkItemCommitRequest.Builder outputBuilder) {
215+
checkNotPoisoned();
193216
for (Entry<TimerData, Boolean> value : timerMap.values()) {
194217
// Regardless of whether it is set or not, it must have some TimerData stored so we
195218
// can know its time domain

runners/google-cloud-dataflow-java/worker/src/main/java/org/apache/beam/runners/dataflow/worker/windmill/state/WindmillStateInternals.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ public class WindmillStateInternals<K> implements StateInternals {
5555
private final CachingStateTable workItemDerivedState;
5656
private final Supplier<Closeable> scopedReadStateSupplier;
5757

58+
private boolean poisoned = false;
59+
60+
public void poison() {
61+
this.poisoned = true;
62+
}
63+
64+
private void checkNotPoisoned() {
65+
if (poisoned) {
66+
throw new IllegalStateException(
67+
"WindmillStateInternals is poisoned and cannot be used after flushState().");
68+
}
69+
}
70+
5871
public WindmillStateInternals(
5972
@Nullable K key,
6073
String stateFamily,
@@ -78,6 +91,7 @@ public WindmillStateInternals(
7891

7992
@Override
8093
public @Nullable K getKey() {
94+
checkNotPoisoned();
8195
return key;
8296
}
8397

@@ -104,6 +118,7 @@ private void persist(
104118
}
105119

106120
public void persist(final Windmill.WorkItemCommitRequest.Builder commitBuilder) {
121+
checkNotPoisoned();
107122
List<Future<WorkItemCommitRequest>> commitsToMerge = new ArrayList<>();
108123

109124
// Call persist on each first, which may schedule some futures for reading.
@@ -126,12 +141,14 @@ public void persist(final Windmill.WorkItemCommitRequest.Builder commitBuilder)
126141

127142
@Override
128143
public <T extends State> T state(StateNamespace namespace, StateTag<T> address) {
144+
checkNotPoisoned();
129145
return workItemState.get(namespace, address, StateContexts.nullContext());
130146
}
131147

132148
@Override
133149
public <T extends State> T state(
134150
StateNamespace namespace, StateTag<T> address, StateContext<?> c) {
151+
checkNotPoisoned();
135152
return workItemState.get(namespace, address, c);
136153
}
137154

0 commit comments

Comments
 (0)