shashank created CAMEL-25247:
--------------------------------
Summary: camel-thrift - the consumer does not send a route failure
to the client: the synchronous server ignores it, the asynchronous server
writes both the error and a response, so the error answers the next call on the
connection
Key: CAMEL-25247
URL: https://issues.apache.org/jira/browse/CAMEL-25247
Project: Camel
Issue Type: Bug
Reporter: shashank
{{ThriftMethodHandler}} turns a Thrift call into an exchange. It does not
handle a failed exchange correctly in either server mode:
# *Asynchronous server (the default, {{THsHaServer}}).* The completion callback
does
{code:java}
if (exception != null) {
callback.onError(exception);
}
message = exchange.getMessage();
...
callback.onComplete(response);
{code}
so for a failed exchange it calls {{onError}} *and then* {{onComplete}}. For a
{{void}} method, and for any method whose generated {{onComplete}} can write a
result (an object return type such as a struct or a string, or a body of the
failed exchange that converts to the return type), two results are written for
one call. With a framed client on one connection, a failing {{void ping()}}
returns normally, and the {{TApplicationException}} meant for it is read as the
answer to the *next* call ({{add(12, 13)}} fails with "Forced"); every later
answer on that connection is shifted by one. (For a method returning a
primitive whose failed body converts to {{null}}, the generated {{onComplete}}
fails on unboxing before it writes, so only the error is sent: that is why the
asynchronous {{calculate}} case below passes on main.)
# *Synchronous server ({{synchronous=true}}).* After
{{consumer.getProcessor().process(exchange)}} the exception of the exchange is
never looked at: a {{void}} method returns normally (the client sees success),
any other method returns the body of the failed exchange converted to its
return type, or fails with {{TApplicationException: Return type requires not
empty body}} when it does not convert, and an exception declared by the IDL
({{throws (1:InvalidOperation ouch)}}) thrown by the route never reaches the
client as such.
The code has been like this since the component was added (CAMEL-11333, 2017).
h3. Reproduction
Routes for the test {{Calculator}} service ({{add}} answers 25, {{calculate}}
throws {{new InvalidOperation(1, "Forced")}}, other methods throw
{{IllegalStateException("Forced")}}), one with {{synchronous=true}} and one
with the default asynchronous server, and a {{Calculator.Client}} over
{{TFramedTransport}}:
* synchronous: {{calculate}} fails with {{TApplicationException}} instead of
{{InvalidOperation}}; {{ping}} returns normally;
* asynchronous: {{ping}} returns normally and the following {{add(12, 13)}} on
the same connection throws {{TApplicationException: Forced}}.
The test fails on main for these three cases (the asynchronous {{calculate}}
case passes on main, see above).
h3. Proposed fix
* synchronous handler: throw the exception of the exchange, so the Thrift
processor writes a declared exception as such and any other one as
{{TApplicationException}};
* asynchronous handler: complete the call exactly once, with
{{onError(exception)}} when the exchange failed, otherwise with
{{onComplete(response)}}; the "unable to detect the return type" and "null
message" errors also return instead of falling through to {{onComplete}}.
With the fix the new test ({{ThriftConsumerExceptionTest}}, 4 tests) and the
whole camel-thrift suite (45 tests) pass. Clients now get errors for failed
exchanges, so the change gets an upgrade guide note.
Affected: 4.14.x, 4.18.x and main (same code).
Duplicate check (2026-10-01): JIRA text "thrift" (26 issues; no component
filter matches) : nothing about exceptions or failed exchanges in the consumer
(CAMEL-24442 is the data format, CAMEL-16133 multiplexing). GitHub pull
requests "thrift": none about it.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)