Jens Geyer created THRIFT-6160:
----------------------------------

             Summary: Haxe framed transport cannot read consecutive frames 
without an intervening flush
                 Key: THRIFT-6160
                 URL: https://issues.apache.org/jira/browse/THRIFT-6160
             Project: Thrift
          Issue Type: Bug
          Components: Haxe - Library
            Reporter: Jens Geyer
             Fix For: 0.25.0


h3. Problem

A Haxe {{TFramedTransport}} cannot read a second frame on the same connection 
unless
something flushed in between. The second frame fails on its four-byte header 
with
{{MESSAGE_SIZE_LIMIT}}, having consumed a few kilobytes of a 100 MB allowance.

h3. Mechanism

* {{readFrame()}} binds the read budget to each frame with 
{{UpdateKnownMessageSize()}} and never returns it to the configured maximum 
first.
* {{TSocket}} charges every read against that budget 
({{CountConsumedMessageBytes}}).
* {{ResetConsumedMessageSize()}} refuses to grow a budget an earlier, smaller 
frame narrowed.

Reading a frame therefore leaves the budget at zero, and the next 
{{readFrameSize()}}
throws immediately.

h3. Why request/response hides it

The only resets on the socket path are in {{flush()}} and on connect. A normal 
exchange
flushes when the reply is written, so the budget is restored before the next 
request is
read. A run of *one-way* calls does not: the generated processor returns before 
the
{{flush()}} for a one-way function, so a server handling two one-way calls in a 
row fails
the second one.

h3. Reproduction

Two frames of 16 and 4096 bytes back to back, default configuration:

{noformat}
frame 1: read 16 bytes OK
frame 2: FAILED type=6 : CountConsumedMessageBytes(4): message size exceeds 
limit 104857600
{noformat}

h3. Fix

Reset the budget at the top of {{readFrame()}}, which is what the Delphi 
binding does at the
same point in the message and for the same reason (its endpoint charges reads 
too). Reaching
the budget from a layered transport needs a public entry point, so 
{{TTransport}} gains
{{ResetMessageSizeAndConsumedBytes()}}, named after the Delphi and netstd 
equivalents. It is a
plain method beside the existing {{UpdateKnownMessageSize()}} and 
{{CheckReadBytesAvailable()}}
rather than an abstract one, so no code outside the tree has to implement it.

Three tests are added in {{lib/haxe/test/src/tests/FramedTransportTest.hx}} and 
wired into the
suite. The third declares a 64 MB field inside a 64-byte frame and passes both 
before and after
the change; it is there to show the budget has not been loosened while the 
reset was widened.

h3. Affected versions

Present since 2dcefadba (THRIFT-5370), i.e. 0.15.0 onward.

h3. Notes

Two smaller inconsistencies were found alongside and are *not* addressed here:

* {{TStreamTransport.read}} does not charge reads against the budget, while 
{{TSocket.read}} does, so the two endpoints disagree on accounting.
* {{ResetConsumedMessageSize}} tests {{newSize == null}} where netstd and 
Delphi test {{< 0}}, so {{UpdateKnownMessageSize(-1)}} sets the Haxe budget to 
-1 instead of performing a full reset. That idiom is what netstd relies on, so 
it is a trap when porting.

_Drafted with AI assistance (Claude Opus 5); reviewed and filed by Jens Geyer._



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

Reply via email to