21
21
import org .apache .flink .api .common .eventtime .SerializableTimestampAssigner ;
22
22
import org .apache .flink .api .common .eventtime .WatermarkStrategy ;
23
23
import org .apache .flink .api .common .functions .FilterFunction ;
24
+ import org .apache .flink .api .connector .source .SplitEnumerator ;
25
+ import org .apache .flink .api .connector .source .SplitEnumeratorContext ;
26
+ import org .apache .flink .api .connector .source .SupportsBatchSnapshot ;
24
27
import org .apache .flink .api .connector .source .lib .NumberSequenceSource ;
28
+ import org .apache .flink .api .connector .source .lib .NumberSequenceSource .NumberSequenceSplit ;
25
29
import org .apache .flink .streaming .api .datastream .DataStream ;
26
30
import org .apache .flink .streaming .api .environment .StreamExecutionEnvironment ;
27
31
28
32
import org .junit .jupiter .api .Assertions ;
29
33
import org .junit .jupiter .api .Test ;
30
34
35
+ import javax .annotation .Nullable ;
36
+
31
37
import java .time .Duration ;
38
+ import java .util .ArrayDeque ;
39
+ import java .util .Collection ;
32
40
import java .util .Collections ;
33
41
import java .util .List ;
42
+ import java .util .Queue ;
34
43
import java .util .stream .Collectors ;
35
44
import java .util .stream .LongStream ;
36
45
46
+ import static org .apache .flink .util .Preconditions .checkNotNull ;
47
+
37
48
/** This ITCase class tests the behavior of task execution with watermark alignment. */
38
49
class WatermarkAlignmentITCase {
39
50
@@ -45,13 +56,13 @@ class WatermarkAlignmentITCase {
45
56
@ Test
46
57
void testTaskFinishedWithWatermarkAlignmentExecution () throws Exception {
47
58
// Set up the execution environment with parallelism of 2
48
- final StreamExecutionEnvironment env = StreamExecutionEnvironment .getExecutionEnvironment ();
59
+ StreamExecutionEnvironment env = StreamExecutionEnvironment .getExecutionEnvironment ();
49
60
env .setParallelism (2 );
50
61
51
62
// Create a stream from a custom source with watermark strategy
52
63
DataStream <Long > stream =
53
64
env .fromSource (
54
- new NumberSequenceSource (0 , 100 ),
65
+ new EagerlyFinishingNumberSequenceSource (0 , 100 ),
55
66
WatermarkStrategy .<Long >forMonotonousTimestamps ()
56
67
.withTimestampAssigner (
57
68
(SerializableTimestampAssigner <Long >)
@@ -67,11 +78,81 @@ void testTaskFinishedWithWatermarkAlignmentExecution() throws Exception {
67
78
});
68
79
69
80
// Execute the stream and collect the results
70
- final List <Long > result = stream .executeAndCollect (101 );
81
+ List <Long > result = stream .executeAndCollect (101 );
71
82
Collections .sort (result );
72
83
73
84
// Assert that the collected result contains all numbers from 0 to 100
74
85
Assertions .assertIterableEquals (
75
86
result , LongStream .rangeClosed (0 , 100 ).boxed ().collect (Collectors .toList ()));
76
87
}
88
+
89
+ static class EagerlyFinishingNumberSequenceSource extends NumberSequenceSource {
90
+ public EagerlyFinishingNumberSequenceSource (long from , long to ) {
91
+ super (from , to );
92
+ }
93
+
94
+ @ Override
95
+ public SplitEnumerator <NumberSequenceSplit , Collection <NumberSequenceSplit >>
96
+ createEnumerator (SplitEnumeratorContext <NumberSequenceSplit > enumContext ) {
97
+
98
+ List <NumberSequenceSplit > splits =
99
+ splitNumberRange (getFrom (), getTo (), enumContext .currentParallelism ());
100
+ return new EagerlyFinishingIteratorSourceEnumerator (enumContext , splits );
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Contrary to the {@link
106
+ * org.apache.flink.api.connector.source.lib.util.IteratorSourceEnumerator}, this enumerator
107
+ * signals no more available splits as soon as possible.
108
+ */
109
+ static class EagerlyFinishingIteratorSourceEnumerator
110
+ implements SplitEnumerator <NumberSequenceSplit , Collection <NumberSequenceSplit >>,
111
+ SupportsBatchSnapshot {
112
+
113
+ private final SplitEnumeratorContext <NumberSequenceSplit > context ;
114
+ private final Queue <NumberSequenceSplit > remainingSplits ;
115
+
116
+ public EagerlyFinishingIteratorSourceEnumerator (
117
+ SplitEnumeratorContext <NumberSequenceSplit > context ,
118
+ Collection <NumberSequenceSplit > splits ) {
119
+ this .context = checkNotNull (context );
120
+ this .remainingSplits = new ArrayDeque <>(splits );
121
+ this .context
122
+ .metricGroup ()
123
+ .setUnassignedSplitsGauge (() -> (long ) remainingSplits .size ());
124
+ }
125
+
126
+ @ Override
127
+ public void start () {}
128
+
129
+ @ Override
130
+ public void close () {}
131
+
132
+ @ Override
133
+ public void handleSplitRequest (int subtaskId , @ Nullable String requesterHostname ) {
134
+ NumberSequenceSplit nextSplit = remainingSplits .poll ();
135
+ if (nextSplit != null ) {
136
+ context .assignSplit (nextSplit , subtaskId );
137
+ }
138
+ if (remainingSplits .size () == 0 ) {
139
+ for (int i = 0 ; i < context .currentParallelism (); i ++) {
140
+ context .signalNoMoreSplits (i );
141
+ }
142
+ }
143
+ }
144
+
145
+ @ Override
146
+ public void addSplitsBack (List <NumberSequenceSplit > splits , int subtaskId ) {
147
+ throw new UnsupportedOperationException ();
148
+ }
149
+
150
+ @ Override
151
+ public Collection <NumberSequenceSplit > snapshotState (long checkpointId ) throws Exception {
152
+ return remainingSplits ;
153
+ }
154
+
155
+ @ Override
156
+ public void addReader (int subtaskId ) {}
157
+ }
77
158
}
0 commit comments