Skip to content

Commit 2535530

Browse files
committed
[FLINK-12351][DataStream] Fix AsyncWaitOperator to deep copy StreamElement when object reuse is enabled #8321 (#8321)
1 parent 2413649 commit 2535530

2 files changed

Lines changed: 106 additions & 6 deletions

File tree

flink-streaming-java/src/main/java/org/apache/flink/streaming/api/operators/async/AsyncWaitOperator.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public class AsyncWaitOperator<IN, OUT>
105105

106106
private transient TimestampedCollector<OUT> timestampedCollector;
107107

108+
/** Whether object reuse has been enabled or disabled. */
109+
private transient boolean isObjectReuseEnabled;
110+
108111
public AsyncWaitOperator(
109112
@Nonnull AsyncFunction<IN, OUT> asyncFunction,
110113
long timeout,
@@ -158,6 +161,8 @@ public void setup(
158161
public void open() throws Exception {
159162
super.open();
160163

164+
this.isObjectReuseEnabled = getExecutionConfig().isObjectReuseEnabled();
165+
161166
if (recoveredStreamElements != null) {
162167
for (StreamElement element : recoveredStreamElements.get()) {
163168
if (element.isRecord()) {
@@ -178,7 +183,16 @@ public void open() throws Exception {
178183
}
179184

180185
@Override
181-
public void processElement(StreamRecord<IN> element) throws Exception {
186+
public void processElement(StreamRecord<IN> record) throws Exception {
187+
StreamRecord<IN> element;
188+
// copy the element avoid the element is reused
189+
if (isObjectReuseEnabled) {
190+
//noinspection unchecked
191+
element = (StreamRecord<IN>) inStreamElementSerializer.copy(record);
192+
} else {
193+
element = record;
194+
}
195+
182196
// add element first to the queue
183197
final ResultFuture<OUT> entry = addToWorkQueue(element);
184198

flink-streaming-java/src/test/java/org/apache/flink/streaming/api/operators/async/AsyncWaitOperatorTest.java

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import org.apache.flink.api.common.functions.RichMapFunction;
2323
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
2424
import org.apache.flink.api.common.typeinfo.TypeInformation;
25+
import org.apache.flink.api.common.typeutils.TypeSerializer;
2526
import org.apache.flink.api.common.typeutils.base.IntSerializer;
2627
import org.apache.flink.api.java.Utils;
28+
import org.apache.flink.api.java.tuple.Tuple1;
2729
import org.apache.flink.api.java.typeutils.TypeExtractor;
30+
import org.apache.flink.api.java.typeutils.runtime.TupleSerializer;
2831
import org.apache.flink.configuration.Configuration;
2932
import org.apache.flink.core.testutils.OneShotLatch;
3033
import org.apache.flink.runtime.checkpoint.CheckpointMetaData;
@@ -100,9 +103,10 @@
100103
public class AsyncWaitOperatorTest extends TestLogger {
101104
private static final long TIMEOUT = 1000L;
102105

103-
@Rule public Timeout timeoutRule = new Timeout(10, TimeUnit.SECONDS);
106+
@Rule public Timeout timeoutRule = new Timeout(100, TimeUnit.SECONDS);
104107

105-
private static class MyAsyncFunction extends RichAsyncFunction<Integer, Integer> {
108+
private abstract static class MyAbstractAsyncFunction<IN>
109+
extends RichAsyncFunction<IN, Integer> {
106110
private static final long serialVersionUID = 8522411971886428444L;
107111

108112
private static final long TERMINATION_TIMEOUT = 5000L;
@@ -115,7 +119,7 @@ private static class MyAsyncFunction extends RichAsyncFunction<Integer, Integer>
115119
public void open(Configuration parameters) throws Exception {
116120
super.open(parameters);
117121

118-
synchronized (MyAsyncFunction.class) {
122+
synchronized (MyAbstractAsyncFunction.class) {
119123
if (counter == 0) {
120124
executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
121125
}
@@ -132,7 +136,7 @@ public void close() throws Exception {
132136
}
133137

134138
private void freeExecutor() {
135-
synchronized (MyAsyncFunction.class) {
139+
synchronized (MyAbstractAsyncFunction.class) {
136140
--counter;
137141

138142
if (counter == 0) {
@@ -151,6 +155,10 @@ private void freeExecutor() {
151155
}
152156
}
153157
}
158+
}
159+
160+
private static class MyAsyncFunction extends MyAbstractAsyncFunction<Integer> {
161+
private static final long serialVersionUID = -1504699677704123889L;
154162

155163
@Override
156164
public void asyncInvoke(final Integer input, final ResultFuture<Integer> resultFuture)
@@ -183,7 +191,7 @@ public LazyAsyncFunction() {
183191
@Override
184192
public void asyncInvoke(final Integer input, final ResultFuture<Integer> resultFuture)
185193
throws Exception {
186-
this.executorService.submit(
194+
executorService.submit(
187195
new Runnable() {
188196
@Override
189197
public void run() {
@@ -203,6 +211,23 @@ public static void countDown() {
203211
}
204212
}
205213

214+
private static class InputReusedAsyncFunction extends MyAbstractAsyncFunction<Tuple1<Integer>> {
215+
216+
private static final long serialVersionUID = 8627909616410487720L;
217+
218+
@Override
219+
public void asyncInvoke(Tuple1<Integer> input, ResultFuture<Integer> resultFuture)
220+
throws Exception {
221+
executorService.submit(
222+
new Runnable() {
223+
@Override
224+
public void run() {
225+
resultFuture.complete(Collections.singletonList(input.f0 * 2));
226+
}
227+
});
228+
}
229+
}
230+
206231
/**
207232
* A special {@link LazyAsyncFunction} for timeout handling. Complete the result future with 3
208233
* times the input when the timeout occurred.
@@ -596,6 +621,67 @@ public void testStateSnapshotAndRestore() throws Exception {
596621
restoredTaskHarness.getOutput());
597622
}
598623

624+
@SuppressWarnings("rawtypes")
625+
@Test
626+
public void testStateSnapshotAndRestoreWithObjectReused() throws Exception {
627+
TypeSerializer[] fieldSerializers = new TypeSerializer[] {IntSerializer.INSTANCE};
628+
TupleSerializer<Tuple1> inputSerializer =
629+
new TupleSerializer<>(Tuple1.class, fieldSerializers);
630+
AsyncWaitOperatorFactory<Tuple1<Integer>, Integer> factory =
631+
new AsyncWaitOperatorFactory<>(
632+
new InputReusedAsyncFunction(),
633+
TIMEOUT,
634+
4,
635+
AsyncDataStream.OutputMode.ORDERED);
636+
637+
//noinspection unchecked
638+
final OneInputStreamOperatorTestHarness<Tuple1<Integer>, Integer> testHarness =
639+
new OneInputStreamOperatorTestHarness(factory, inputSerializer);
640+
// enable object reuse
641+
testHarness.getExecutionConfig().enableObjectReuse();
642+
643+
final long initialTime = 0L;
644+
Tuple1<Integer> reusedTuple = new Tuple1<>();
645+
StreamRecord<Tuple1<Integer>> reusedRecord = new StreamRecord<>(reusedTuple, -1L);
646+
647+
testHarness.setup();
648+
testHarness.open();
649+
650+
synchronized (testHarness.getCheckpointLock()) {
651+
reusedTuple.setFields(1);
652+
reusedRecord.setTimestamp(initialTime + 1);
653+
testHarness.processElement(reusedRecord);
654+
655+
reusedTuple.setFields(2);
656+
reusedRecord.setTimestamp(initialTime + 2);
657+
testHarness.processElement(reusedRecord);
658+
659+
reusedTuple.setFields(3);
660+
reusedRecord.setTimestamp(initialTime + 3);
661+
testHarness.processElement(reusedRecord);
662+
663+
reusedTuple.setFields(4);
664+
reusedRecord.setTimestamp(initialTime + 4);
665+
testHarness.processElement(reusedRecord);
666+
}
667+
668+
ConcurrentLinkedQueue<Object> expectedOutput = new ConcurrentLinkedQueue<>();
669+
expectedOutput.add(new StreamRecord<>(2, initialTime + 1));
670+
expectedOutput.add(new StreamRecord<>(4, initialTime + 2));
671+
expectedOutput.add(new StreamRecord<>(6, initialTime + 3));
672+
expectedOutput.add(new StreamRecord<>(8, initialTime + 4));
673+
674+
synchronized (testHarness.getCheckpointLock()) {
675+
testHarness.endInput();
676+
testHarness.close();
677+
}
678+
679+
TestHarnessUtil.assertOutputEquals(
680+
"StateAndRestoredWithObjectReuse Test Output was not correct.",
681+
expectedOutput,
682+
testHarness.getOutput());
683+
}
684+
599685
@Test
600686
public void testAsyncTimeoutFailure() throws Exception {
601687
testAsyncTimeout(

0 commit comments

Comments
 (0)