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

Christopher Tubbs reassigned THRIFT-6080:
-----------------------------------------

    Assignee: Christopher Tubbs

> Java binary types generate getters that incorrectly make redundant array 
> copies after resizing the array from the ByteBuffer source
> -----------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: THRIFT-6080
>                 URL: https://issues.apache.org/jira/browse/THRIFT-6080
>             Project: Thrift
>          Issue Type: Bug
>            Reporter: Christopher Tubbs
>            Assignee: Christopher Tubbs
>            Priority: Major
>
> In the Java generator, for binary types, two getters are created, one that 
> looks like:
>     byte[] getFieldname();
> And one that looks like:
>     ByteBuffer bufferForFieldname();
> The first method seems to take the underlying bytes for the field, truncate 
> it to size, making a copy if needed, using TBaseHelper.rightSize(ByteBuffer). 
> Then, it makes an additional copy by calling the public API 
> setFieldname(ByteBuffer), which internally uses 
> TBaseHelper.copyBinary(ByteBuffer). All of this appears to be an attempt to 
> create a protective copy on read of the underlying byte array. However, what 
> it actually returns is the actual underlying array of the currently set 
> ByteBuffer. The next time the method is called, it will make another copy of 
> the ByteBuffer that is wrapping the array that was leaked outside the method 
> the last time it was called. This is not correct behavior if it is trying to 
> make a protective copy on read, and it is not correct behavior if it is 
> trying to merely truncate once and always return that same byte array. 
> Neither possible behavior is correct, so it makes no sense to keep making 
> copies on each read.
> The second method appears to be designed to provide direct access to the 
> underlying ByteBuffer, presumably for efficiency if a copy of the byte array 
> is not needed. However, it also makes a protective copy of the bytes and 
> creates a new ByteBuffer object every time, by calling 
> TBaseHelper.copyBinary(ByteBuffer) each time. It appears that this was 
> previously identified as a problem and a flag was added (unsafe_binaries) to 
> the Java generator to give direct access to the underlying ByteBuffer without 
> making a protective copy. However, the problem with that flag is that it 
> removes protective copies everywhere, including in the constructor and in the 
> setters, rather than just give direct access to the underlying ByteBuffer. 
> So, that flag makes it harder for calling code to know when it needs to make 
> its own protective copy and when it doesn't.
> This was identified as a problem in 
> [https://github.com/apache/accumulo/pull/6455] ; after a few failed attempts 
> to work around the problems, we chose to avoid the expensive 
> bufferForFieldname() methods and the unsafe use of unsafe_binaries and patch 
> the generated code at build time in 
> [https://github.com/apache/accumulo/pull/6459] to avoid the problems with 
> unnecessary copies of the array in the getter that returns an array, by 
> setting the internal field to the output of the 
> TBaseHelper.rightSize(ByteBuffer) method instead of setting it via the 
> setFieldname(ByteBuffer) method that makes the unnecessary copy. This still 
> gives callers access to the shared byte array instead of making a protective 
> copy of it each read, though. However, since it was already leaking the byte 
> array, and we are not modifying it, we found this acceptable behavior.
> Ideally, the getter should be modified as we did in 
> [https://github.com/apache/accumulo/pull/6459,] but the array that is 
> returned should be returned with .clone() so it doesn't leak the original 
> array after resizing. This will potentially result in two copies the first 
> time, and only one copy each subsequent time. The alternative is to never 
> modify the underlying ByteBuffer, and just always make a copy each time, and 
> never call setFieldname(ByteBuffer). I think the unsafe_binaries flag is 
> somewhat worthless at this point... I think the entire purpose of having that 
> method is to get access to the underlying ByteBuffer directly. It makes no 
> sense to be generated at all, if it's going to make a protective copy. If it 
> were going to do that, it should just get the protective copy from the 
> standard getter (once fixed properly) and users can wrap it themselves with 
> ByteBuffer.wrap(byte[]) if they really want to. The bufferFor methods should 
> not be created at all by default, unless requested to generate it using a new 
> flag to be added for that purpose. And when it is generated, it will always 
> be generated without the protective copy, because that's its entire reason 
> for existing. Its behavior should not be affected by the 'unsafe_binaries' 
> flag at all. That flag (which I recommend nobody ever use) should only affect 
> the constructor and setters and the getter that returns an array.



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

Reply via email to