You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Claus Ibsen (Jira)" <ji...@apache.org> on 2026/10/02 09:36:00 UTC

[jira] [Resolved] (CAMEL-25247) 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

     [ https://issues.apache.org/jira/browse/CAMEL-25247?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Claus Ibsen resolved CAMEL-25247.
---------------------------------
    Resolution: Fixed

Fixed by https://github.com/apache/camel/pull/27244

_Claude Code on behalf of davsclaus_

> 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
>            Assignee: shashank
>            Priority: Major
>             Fix For: 4.23.0
>
>
> {{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)