Skip to content

Commit 17e66d1

Browse files
committed
Refactor AsyncDoFn to simplify output receiver and fix type mismatches involving inputTimestamp(#38529)
1 parent 2180622 commit 17e66d1

2 files changed

Lines changed: 17 additions & 116 deletions

File tree

sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/AsyncDoFn.java

Lines changed: 17 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,11 @@ public class AsyncDoFn<K, InputT, OutputT> extends DoFn<KV<K, InputT>, OutputT>
114114
private static final ReentrantLock lock = new ReentrantLock();
115115
private static final boolean verboseLogging = false;
116116

117-
private static class TimestampedOutput<T> {
118-
final T value;
119-
final @Nullable Instant timestamp;
120-
121-
TimestampedOutput(T value, @Nullable Instant timestamp) {
122-
this.value = value;
123-
this.timestamp = timestamp;
124-
}
125-
}
126-
127117
private static class InFlightElement<OutputT> {
128118
final @Nullable Object key;
129-
final CompletableFuture<List<TimestampedOutput<OutputT>>> future;
119+
final CompletableFuture<List<OutputT>> future;
130120

131-
InFlightElement(
132-
@Nullable Object key, CompletableFuture<List<TimestampedOutput<OutputT>>> future) {
121+
InFlightElement(@Nullable Object key, CompletableFuture<List<OutputT>> future) {
133122
this.key = key;
134123
this.future = future;
135124
}
@@ -140,50 +129,30 @@ private static class InFlightElement<OutputT> {
140129
// Buffered elements are only committed downstream once the parent task completes successfully
141130
// and the timer fires.
142131
private static class AccumulatingOutputReceiver<T> implements OutputReceiver<T> {
143-
private final List<TimestampedOutput<T>> outputs = new ArrayList<>();
144-
private final Instant inputTimestamp;
145-
private final BoundedWindow window;
132+
private final List<T> outputs = new ArrayList<>();
146133

147-
AccumulatingOutputReceiver(Instant inputTimestamp, BoundedWindow window) {
148-
this.inputTimestamp = inputTimestamp;
149-
this.window = window;
150-
}
134+
AccumulatingOutputReceiver() {}
151135

152136
@Override
153137
public org.apache.beam.sdk.values.OutputBuilder<T> builder(T value) {
154138
return org.apache.beam.sdk.values.WindowedValues.<T>builder()
155139
.setValue(value)
156-
.setTimestamp(inputTimestamp)
157-
.setWindows(java.util.Collections.singletonList(window))
158-
.setPaneInfo(org.apache.beam.sdk.transforms.windowing.PaneInfo.NO_FIRING)
159-
.setReceiver(
160-
windowedValue ->
161-
outputs.add(
162-
new TimestampedOutput<>(
163-
windowedValue.getValue(), windowedValue.getTimestamp())));
140+
.setReceiver(windowedValue -> outputs.add(windowedValue.getValue()));
164141
}
165142

166143
// Bypasses the nested anonymous OutputBuilder instantiation for standard outputs.
167144
// JVM optimization to prevent garbage collection pressure under high pipeline throughput.
168145
@Override
169146
public void output(T output) {
170-
outputs.add(new TimestampedOutput<>(output, inputTimestamp));
147+
outputs.add(output);
171148
}
172149

173150
@Override
174151
public void outputWithTimestamp(T output, Instant timestamp) {
175-
outputs.add(new TimestampedOutput<>(output, timestamp));
152+
outputs.add(output);
176153
}
177154

178155
public List<T> getOutputs() {
179-
List<T> rawOutputs = new ArrayList<>();
180-
for (TimestampedOutput<T> out : outputs) {
181-
rawOutputs.add(out.value);
182-
}
183-
return rawOutputs;
184-
}
185-
186-
public List<TimestampedOutput<T>> getTimestampedOutputs() {
187156
return outputs;
188157
}
189158
}
@@ -355,12 +324,12 @@ private boolean scheduleIfRoom(
355324
useThreadPool ? getThreadPool() : java.util.concurrent.ForkJoinPool.commonPool();
356325

357326
// Pending asynchronous task that will produce a list of outputs
358-
CompletableFuture<List<TimestampedOutput<OutputT>>> future =
327+
CompletableFuture<List<OutputT>> future =
359328
CompletableFuture.supplyAsync(
360329
() -> {
361330
try {
362331
AccumulatingOutputReceiver<OutputT> receiver =
363-
new AccumulatingOutputReceiver<>(timestamp, window);
332+
new AccumulatingOutputReceiver<>();
364333
DoFnInvoker<InputT, OutputT> invoker = DoFnInvokers.invokerFor(syncFn);
365334

366335
DoFnInvoker.ArgumentProvider<InputT, OutputT> bundleArgProvider =
@@ -451,7 +420,7 @@ public String getErrorContext() {
451420
invoker.invokeProcessElement(processArgProvider);
452421
invoker.invokeFinishBundle(bundleArgProvider);
453422

454-
return receiver.getTimestampedOutputs();
423+
return receiver.getOutputs();
455424
} catch (Exception e) {
456425
throw new CompletionException(e);
457426
}
@@ -460,7 +429,7 @@ public String getErrorContext() {
460429

461430
// Assigned to 'unused' to satisfy ErrorProne while preserving parent future for
462431
// cancellation
463-
CompletableFuture<List<TimestampedOutput<OutputT>>> unused =
432+
CompletableFuture<List<OutputT>> unused =
464433
future.whenComplete(
465434
(res, ex) -> {
466435
getItemsInBuffer().decrementAndGet();
@@ -583,7 +552,7 @@ void commitFinishedItems(
583552

584553
ConcurrentHashMap<Object, InFlightElement<OutputT>> activeElements = getProcessingElements();
585554

586-
List<List<TimestampedOutput<OutputT>>> toReturn = new ArrayList<>();
555+
List<List<OutputT>> toReturn = new ArrayList<>();
587556

588557
List<KV<K, InputT>> toReschedule = new ArrayList<>();
589558

@@ -679,13 +648,10 @@ void commitFinishedItems(
679648

680649
// Emit completed outputs
681650
// (Emit completed tasks immediately; do not wait for all active tasks to finish).
682-
for (List<TimestampedOutput<OutputT>> outputs : toReturn) {
683-
for (TimestampedOutput<OutputT> out : outputs) {
684-
if (out.timestamp != null) {
685-
receiver.outputWithTimestamp(out.value, out.timestamp);
686-
} else {
687-
receiver.output(out.value);
688-
}
651+
// Outputs use processing-time timestamps matching Python behavior
652+
for (List<OutputT> outputs : toReturn) {
653+
for (OutputT out : outputs) {
654+
receiver.output(out);
689655
}
690656
}
691657

@@ -718,8 +684,7 @@ void processDirect(
718684

719685
List<OutputT> commitFinishedItemsDirect(
720686
Instant fireTimestamp, BagState<KV<K, InputT>> toProcessState, Timer timer) {
721-
AccumulatingOutputReceiver<OutputT> receiver =
722-
new AccumulatingOutputReceiver<>(fireTimestamp, GlobalWindow.INSTANCE);
687+
AccumulatingOutputReceiver<OutputT> receiver = new AccumulatingOutputReceiver<>();
723688
commitFinishedItems(fireTimestamp, toProcessState, timer, receiver);
724689
return receiver.getOutputs();
725690
}

sdks/java/core/src/test/java/org/apache/beam/sdk/transforms/AsyncDoFnTest.java

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -104,19 +104,6 @@ public void finishBundle(FinishBundleContext c) {
104104
}
105105
}
106106

107-
private static class TimestampingDoFn extends DoFn<String, String> {
108-
private final Instant outputTimestamp;
109-
110-
TimestampingDoFn(Instant outputTimestamp) {
111-
this.outputTimestamp = outputTimestamp;
112-
}
113-
114-
@ProcessElement
115-
public void processElement(@Element String element, OutputReceiver<String> receiver) {
116-
receiver.outputWithTimestamp(element, outputTimestamp);
117-
}
118-
}
119-
120107
// Used for testing BagState thread safety.
121108
private static class FakeBagState<T> implements BagState<T> {
122109
private final List<T> items;
@@ -888,55 +875,4 @@ public void testResetStateConcurrentTeardown() {
888875
// Verify calling resetState() while background tasks are running finishes cleanly
889876
AsyncDoFn.resetState();
890877
}
891-
892-
// Test 15: testTimestampPropagation
893-
// Verifies that custom timestamps output by the wrapped DoFn are correctly propagated
894-
// and not lost or replaced during async execution.
895-
@Test
896-
public void testTimestampPropagation() {
897-
Instant customTimestamp = new Instant(123456789000L);
898-
TimestampingDoFn dofn = new TimestampingDoFn(customTimestamp);
899-
AsyncDoFn<String, String, String> asyncDoFn =
900-
new AsyncDoFn<>(
901-
dofn, 1, Duration.standardSeconds(5), null, null, null, null, useThreadPool);
902-
asyncDoFn.setup(null);
903-
904-
FakeBagState<KV<String, String>> fakeBagState = new FakeBagState<>();
905-
FakeTimer fakeTimer = new FakeTimer();
906-
907-
class CapturingReceiver implements DoFn.OutputReceiver<String> {
908-
final List<String> values = new ArrayList<>();
909-
final List<Instant> timestamps = new ArrayList<>();
910-
911-
@Override
912-
public org.apache.beam.sdk.values.OutputBuilder<String> builder(String value) {
913-
throw new UnsupportedOperationException();
914-
}
915-
916-
@Override
917-
public void output(String output) {
918-
values.add(output);
919-
timestamps.add(null);
920-
}
921-
922-
@Override
923-
public void outputWithTimestamp(String output, Instant timestamp) {
924-
values.add(output);
925-
timestamps.add(timestamp);
926-
}
927-
}
928-
929-
CapturingReceiver capturingReceiver = new CapturingReceiver();
930-
931-
asyncDoFn.processDirect(
932-
KV.of("key1", "val1"), GlobalWindow.INSTANCE, Instant.now(), fakeBagState, fakeTimer);
933-
934-
waitForEmpty(asyncDoFn);
935-
936-
asyncDoFn.commitFinishedItems(
937-
fakeTimer.getCurrentRelativeTime(), fakeBagState, fakeTimer, capturingReceiver);
938-
939-
assertEquals(Collections.singletonList("val1"), capturingReceiver.values);
940-
assertEquals(Collections.singletonList(customTimestamp), capturingReceiver.timestamps);
941-
}
942878
}

0 commit comments

Comments
 (0)