5959 * receivers.
6060 */
6161class ExecutableStageProcessor
62- implements Processor <byte [], KStreamsPayload <byte [] >, byte [], KStreamsPayload <byte [] >> {
62+ implements Processor <byte [], KStreamsPayload <? >, byte [], KStreamsPayload <? >> {
6363
6464 private static final Logger LOG = LoggerFactory .getLogger (ExecutableStageProcessor .class );
6565
@@ -68,9 +68,13 @@ class ExecutableStageProcessor
6868
6969 // pendingOutputs is enqueued by SDK harness threads (inside the OutputReceiverFactory callback)
7070 // and drained by the Kafka Streams processing thread on bundle close; needs to be thread-safe.
71- private final Queue <WindowedValue <byte []>> pendingOutputs = new ConcurrentLinkedQueue <>();
71+ // The element type is intentionally wildcarded: the runner does not need to know the runtime
72+ // value type — the bundle factory handles all coder application at the Fn-API boundary using
73+ // the PCollection coders from the ExecutableStagePayload. Pretending the type was byte[] was
74+ // only safe because the Impulse output coder happens to be ByteArrayCoder.
75+ private final Queue <WindowedValue <?>> pendingOutputs = new ConcurrentLinkedQueue <>();
7276
73- private @ Nullable ProcessorContext <byte [], KStreamsPayload <byte [] >> context ;
77+ private @ Nullable ProcessorContext <byte [], KStreamsPayload <? >> context ;
7478 private @ Nullable ExecutableStageContext stageContext ;
7579 private @ Nullable StageBundleFactory stageBundleFactory ;
7680 private @ Nullable RemoteBundle currentBundle ;
@@ -81,16 +85,16 @@ class ExecutableStageProcessor
8185 }
8286
8387 @ Override
84- public void init (ProcessorContext <byte [], KStreamsPayload <byte [] >> context ) {
88+ public void init (ProcessorContext <byte [], KStreamsPayload <? >> context ) {
8589 this .context = context ;
8690 ExecutableStage executableStage = ExecutableStage .fromPayload (stagePayload );
8791 this .stageContext = KafkaStreamsExecutableStageContextFactory .getInstance ().get (jobInfo );
8892 this .stageBundleFactory = stageContext .getStageBundleFactory (executableStage );
8993 }
9094
9195 @ Override
92- public void process (Record <byte [], KStreamsPayload <byte [] >> record ) {
93- KStreamsPayload <byte [] > payload = record .value ();
96+ public void process (Record <byte [], KStreamsPayload <? >> record ) {
97+ KStreamsPayload <? > payload = record .value ();
9498 if (payload .isWatermark ()) {
9599 // NOTE: flushing the bundle on every received watermark is provisional. Once the
96100 // WatermarkManager lands, a stage will receive watermarks from multiple parent instances and
@@ -122,7 +126,7 @@ public <OutputT> FnDataReceiver<OutputT> create(String pCollectionId) {
122126 // after the bundle closes.
123127 return receivedElement -> {
124128 if (receivedElement != null ) {
125- pendingOutputs .add ((WindowedValue <byte [] >) receivedElement );
129+ pendingOutputs .add ((WindowedValue <? >) receivedElement );
126130 }
127131 };
128132 }
@@ -143,7 +147,7 @@ private FnDataReceiver<WindowedValue<?>> mainInputReceiver() {
143147 return receiver ;
144148 }
145149
146- private void closeBundleAndFlush (Record <byte [], KStreamsPayload <byte [] >> record ) {
150+ private void closeBundleAndFlush (Record <byte [], KStreamsPayload <? >> record ) {
147151 RemoteBundle bundle = currentBundle ;
148152 if (bundle == null ) {
149153 return ;
@@ -157,26 +161,25 @@ private void closeBundleAndFlush(Record<byte[], KStreamsPayload<byte[]>> record)
157161 } finally {
158162 currentBundle = null ;
159163 }
160- ProcessorContext <byte [], KStreamsPayload <byte [] >> ctx = checkInitialized (context );
164+ ProcessorContext <byte [], KStreamsPayload <? >> ctx = checkInitialized (context );
161165 // The harness has finished the bundle (close() returned) so no further enqueues happen.
162- // ConcurrentLinkedQueue's weakly-consistent iterator is therefore safe to drain via forEach .
163- pendingOutputs . forEach (
164- output ->
165- ctx .forward (
166- new Record <byte [], KStreamsPayload <byte [] >>(
167- record .key (), KStreamsPayload .data (output ), record .timestamp () )));
168- pendingOutputs . clear ();
166+ // Drain via poll() so each element is removed as it is forwarded .
167+ WindowedValue <?> output ;
168+ while (( output = pendingOutputs . poll ()) != null ) {
169+ ctx .forward (
170+ new Record <byte [], KStreamsPayload <? >>(
171+ record .key (), KStreamsPayload .data (output ), record .timestamp ()));
172+ }
169173 }
170174
171- private void forwardWatermark (
172- Record <byte [], KStreamsPayload <byte []>> record , long watermarkMillis ) {
175+ private void forwardWatermark (Record <byte [], KStreamsPayload <?>> record , long watermarkMillis ) {
173176 // TODO(#38743 / WatermarkManager): a watermark must reach every parallel instance of every
174177 // downstream processor, but ctx.forward routes to one downstream partition per Kafka Streams'
175178 // partitioning. The simplest correct approach is to fan the watermark out to all downstream
176179 // partitions; that wiring lands with the WatermarkManager sub-issue (per Jan on PR #38764).
177- ProcessorContext <byte [], KStreamsPayload <byte [] >> ctx = checkInitialized (context );
180+ ProcessorContext <byte [], KStreamsPayload <? >> ctx = checkInitialized (context );
178181 ctx .forward (
179- new Record <byte [], KStreamsPayload <byte [] >>(
182+ new Record <byte [], KStreamsPayload <? >>(
180183 record .key (), KStreamsPayload .watermark (watermarkMillis ), record .timestamp ()));
181184 }
182185
0 commit comments