shashank created CAMEL-25611:
--------------------------------
Summary: camel-kudu - the producer ignores the row error of an
insert, update, delete or upsert (duplicate key, row not found, failed
request): the exchange succeeds although the row was not written; and it never
closes its Kudu sessions
Key: CAMEL-25611
URL: https://issues.apache.org/jira/browse/CAMEL-25611
Project: Camel
Issue Type: Bug
Components: camel-kudu
Reporter: shashank
Assignee: shashank
The four row operations of {{KuduProducer}} end with (lines 106, 127, 147, 167
at main a7169d7c6ab6)
{code:java}
connection.newSession().apply(insert); // delete, update, upsert
{code}
and drop the result. Two defects follow from this line.
h3. 1. A row error is reported as a success
A session of kudu-client has the flush mode {{AUTO_FLUSH_SYNC}} by default (the
constructor of {{AsyncKuduSession}}, 1.18.1). In that mode
{{KuduSession.apply}} sends the operation and returns its
{{OperationResponse}}. The client does not throw in two cases; it returns a
response with a row error ({{hasRowError()}}, {{getRowError()}}):
* a row that the tablet server rejected. Examples: an insert of a key that is
already present ("Already present"), an update or a delete of a row that does
not exist ("Not found").
* a request that failed: read from kudu-client 1.18.1, not run against a Kudu
server. {{AsyncKuduSession.doAutoFlushSync}} adds
{{AsyncKuduSession$SingleOperationErrCallback}} as errback of the write. It
turns every {{KuduException}} of the request (a timeout, a tablet server that
cannot be reached, a row outside the range partitions as "Not found") into an
{{OperationResponse}} whose {{RowError}} has the status of the exception, so
{{KuduSession.apply}} returns normally. After such a failure it is not known
whether the row was written.
The producer does not look at the response, so in both cases the exchange
completes without an exception and the route continues as if the row had been
written. With
{{from("jms:orders").to("kudu:host:7051/orders?operation=update")}} an update
for a row that is not there, or an update that timed out, is acknowledged and
gone.
h3. 2. One session per exchange stays registered with the client
{{AsyncKuduClient.newSession()}} adds every session to its {{sessions}} set;
only {{AsyncKuduSession.close()}} removes it ({{removeSession}}). The producer
never closes the session, so the client keeps one more {{AsyncKuduSession}} for
every exchange until the client itself is closed (when the endpoint stops, or
never for a client that the application provides).
h3. Reproduction
New {{KuduProducerRowErrorTest}} (10 tests). It runs the producer against a
mocked Kudu client (the Kudu harness of the module only runs on Linux x86_64);
the mocked session answers as {{AUTO_FLUSH_SYNC}} does, with a response that
carries a row error. With the producer of main all ten fail:
* {{rowErrorFailsTheExchange}} for insert, delete, update and upsert:
{{exception of an exchange whose insert was answered with a row error ==>
Unexpected null value, expected:
<org.apache.camel.component.kudu.KuduRowErrorException> but was: <null>}};
* {{failedRequestFailsTheExchangeWithItsStatus}} (a delete answered with a row
error whose status is "Timed out", the shape the client gives a failed
request): the same message;
* {{sessionIsClosedAfterAnAppliedRow}} for the four operations: {{Wanted but
not invoked: kuduSession.close()}};
* {{sessionIsClosedWhenTheOperationFails}}: {{array lengths differ, expected:
<1> but was: <0>}} (the failure of the close is not attached, because the
session is not closed after an operation that throws).
h3. Proposed fix
One private method for the four operations: apply the operation, throw the new
{{KuduRowErrorException}} (a {{RuntimeCamelException}} that carries the
{{RowError}} of the Kudu client; message "Kudu reported a row error for the
operation: ...") when the response has a row error, and close the session
afterwards. The status of the row error tells a rejected row from a failed
request. When the operation failed, a failure of the close is added to its
exception as suppressed.
Behaviour change, with a 4.24 upgrade-guide entry and a section in the
component documentation: an insert of an existing key and an update or delete
of a missing row now fail the exchange, and so does a write whose request
failed (timeout, tablet server not reachable). Routes that relied on rejected
rows being skipped silently can use {{upsert}} or handle the exception
({{onException(KuduRowErrorException.class).handled(true)}}).
camel-kudu tests with the fix on macOS arm64: 24 run, 0 failures, 12 skipped
(the 12 tests that need the Kudu harness). The existing harness tests do not
provoke a row error (read, not run). They skip by assumption when the harness
cannot start ({{Assumptions.assumeTrue(ikc.hasKuduHarness(), ...)}} in
{{AbstractKuduTest}}), and the open ticket CAMEL-23493 reports that this is
what happens to most of them, so the mocked test may be the only test that
exercises these producer operations.
How it was found: a sweep of the producers for results of the remote API that
are never inspected, and a small TLA+ model (a table, exchanges with one row
operation each, the sessions of the client). For the code of main TLC reports a
successful exchange whose row was not applied and a session left after the
first exchange; with the fix both properties hold, and no applied row fails an
exchange. The model is only a sketch.
Not verified against a Kudu server: that the tablet server reports a duplicate
key and a missing row as row errors is taken from the code of kudu-client
1.18.1 ({{OperationResponse}}, {{RowError}}, the default flush mode) and from
its documentation, not from a run; that a failed request becomes a row error is
taken from the code of kudu-client 1.18.1 only
({{AsyncKuduSession$SingleOperationErrCallback}}). The kudu-client versions of
the older release branches were not read.
Affected: main (a7169d7c6ab6, tested with the mocked client). camel-4.23.x,
camel-4.22.x, camel-4.18.x and camel-4.14.x have the same four lines (106, 127,
147, 167): read, not run.
Duplicate check (2026-10-13): JIRA text "kudu row error", "kudu insert
duplicate": none; GitHub pull requests with "kudu" in the title: dependency
updates, CAMEL-20401 (null client on endpoint stop) and CAMEL-22241, none about
the result of an operation or the sessions.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)