Claus Ibsen created CAMEL-24473:
-----------------------------------

             Summary: camel-kafka: consumer/producer readiness health check 
does not detect a downed broker under group.protocol=consumer 
(AsyncKafkaConsumer)
                 Key: CAMEL-24473
                 URL: https://issues.apache.org/jira/browse/CAMEL-24473
             Project: Camel
          Issue Type: Bug
          Components: camel-kafka
            Reporter: Claus Ibsen


h3. Problem

The Kafka consumer readiness health check ({{KafkaConsumerHealthCheck}} -> 
{{KafkaFetchRecords.isReady()}}) determines broker connectivity via reflection 
into the Kafka client's internal {{ConsumerNetworkClient}} and calling 
{{hasReadyNodes(now)}}:

{code:java}
org.apache.kafka.clients.consumer.KafkaConsumer kc = ...;
Object client = 
ReflectionHelper.getField(kc.getClass().getDeclaredField("delegate"), kc);
ConsumerNetworkClient nc = (ConsumerNetworkClient) ReflectionHelper.getField(
        client.getClass().getDeclaredField("client"), client);
ready = nc.hasReadyNodes(System.currentTimeMillis());
{code}

This works for the classic consumer protocol, where {{KafkaConsumer.delegate}} 
is a {{ClassicKafkaConsumer}} that has a {{private final ConsumerNetworkClient 
client}} field.

Under the new consumer group protocol ({{group.protocol=consumer}}, KIP-848), 
{{KafkaConsumer.delegate}} is an {{AsyncKafkaConsumer}}, which has *no* 
{{client}} field and no {{ConsumerNetworkClient}} at all (networking runs on a 
background thread via {{NetworkClientDelegate}}). The 
{{getDeclaredField("client")}} call therefore throws {{NoSuchFieldException}}, 
which is caught and ignored, leaving the local {{ready}} at its default 
{{true}}.

Result: with {{group.protocol=consumer}}, the consumer readiness health check 
reports UP even when the broker is down. It never transitions to DOWN.

Verified against kafka-clients 4.3.1 (current {{<kafka-version>}} in 
parent/pom.xml):
* {{ClassicKafkaConsumer}} -> has {{ConsumerNetworkClient client}} (reflection 
works)
* {{AsyncKafkaConsumer}} -> no such field (reflection fails, swallowed, ready 
stays true)

The producer path ({{KafkaProducer.isReady()}}) uses the same reflection 
pattern and should be reviewed for the equivalent gap.

h3. Impact

Deployments that enable the new Kafka consumer protocol 
({{group.protocol=consumer}}) get a readiness probe that cannot detect a broker 
outage, defeating the purpose of the health check. Default protocol is still 
{{classic}} in Kafka 4.3.1, so the default configuration is unaffected today, 
but the new protocol is GA and adoption will grow.

h3. Suggested direction

Replace the brittle reflection with a protocol-agnostic readiness signal, e.g.:
* use a public/stable Kafka client API where available, or
* detect connectivity from poll/metadata state rather than reaching into 
private client internals, or
* at minimum, handle the AsyncKafkaConsumer structure (background 
{{NetworkClientDelegate}}) explicitly and avoid defaulting {{ready=true}} when 
the expected internals are not found (fail-safe to a conservative state / log a 
warning).

h3. Notes

Discovered while investigating CAMEL-24466 
(KafkaConsumerHealthCheckIT.testReadinessWhenDown). That test failure had a 
different root cause (singleton test service whose shutdown() is a no-op), but 
the investigation surfaced this latent reflection gap.

_Reported by Claude Code on behalf of davsclaus_



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to