Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do various doc and other minor improvements #1157

Merged
merged 4 commits into from
Jul 27, 2023

Conversation

stIncMale
Copy link
Member

Various things I noticed while working on JAVA-5018.

@stIncMale stIncMale self-assigned this Jul 5, 2023
@stIncMale stIncMale requested a review from jyemin July 5, 2023 04:16
@stIncMale stIncMale changed the base branch from master to 4.10.x July 5, 2023 04:58
@stIncMale stIncMale changed the base branch from 4.10.x to master July 5, 2023 04:58
@@ -46,7 +46,7 @@ public interface Stream extends BufferProvider{
/**
* Write each buffer in the list to the stream in order, blocking until all are completely written.
*
* @param buffers the buffers to write
* @param buffers the buffers to write. This method must {@linkplain ByteBuf#release() release} each buffer when it no longer needs it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? SocketStream does not seem to do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like ByteBufferBsonOutput#close, called from InternalStreamConnection#sendCommandMessage, is responsible for the release.

Copy link
Member Author

@stIncMale stIncMale Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apparent deviation from this requirement that you see in SocketStream/AsynchronousChannelStream.write is a result of two peculiarities (I am being generous here):

  1. Neither SocketStream.write nor AsynchronousChannelStream.write use the buffers they were given after completing (either returning in the synchronous case, or completing the handler in the asynchronous case). So even if the buffers stay unreleased by write, the method does not leave a reference to them in the heap memory. This, in turn, means that there may neither be negative effects of aliasing, nor memory leaks caused by write not releasing the buffers. However, not releasing may render buffer pooling useless. The latter does not happen due to the next item.
  2. SocketStream/AsynchronousChannelStream.write receive the PooledByteBufNIO implementation of ByteBuf backed by ByteBufNIO, or (I am not sure) CompositeByteBuf. All of these ByteBuf implementations implement the duplicate method by creating a new wrapping ByteBuf whose reference count is unrelated to the wrapped buffer: duplicate() does not retain the wrapper, and duplicate().release() does not release it. Such implementations are, of course, a recipe for disaster, because it allows the wrapped buffer to be released despite the wrapping buffer still having positive reference count, which may result in the wrapped buffer being aliased.

TL;DR: SocketStream/AsynchronousChannelStream.write do not release buffers, but releasing the buffers they are given is completely pointless anyway due to how they are implemented. Our code here is brittle and seemingly works only due to a lucky coincidence.

The situation with the NettyByteBuf implementation of ByteBuf and NettyStream is different: NettyByteBuf.duplicate does retain the wrapped buffer and the wrapper delegates its retain/release/getReferenceCount to the wrapped buffer. These two things (retaining when wrapping + delegation) guarantees that the wrapper's lifetime is not larger than the wrapped's lifetime. And NettyStream.write actually releases the buffers: "outbound messages are created by your application, and it is the responsibility of Netty to release these after writing them out to the wire".

So, the documentation change for the Stream.write message is correct, but needs a refinement in light of OutputBuffer.getByteBuffers behaving not the way I expected (I checked how it behaves with NettyByteBuf, but the behavior turned out to be different for other ByteBuf implementations). SocketStream/AsynchronousChannelStream violate it and get away with that only because ByteBufNIO and CompositeByteBuf are also broken. The behavior of ByteBufNIO/CompositeByteBuf.duplicate means that I need to fix the doc change for the OutputBuffer.getByteBuffers method (see the new commit).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not so sure. Generally when a reference counted object is passed as a parameter, my assumption is that it's the caller's responsibility to release its own reference to to it when the call completes. The receiving object should only release if it also retains, and it doesn't need to retain unless it keeps it for longer than the completion of the call.

For "async" calls it's a bit different in that the above applies not until the async call returns, but until the callback is invoked.

So to my eyes the bug is that InternalStreamConnection calls ByteBufferBsonOutput#getByteBuffers and receives a list of List<ByteBuf> that it, in come cases, does not release.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rolled back changes in Stream and created https://jira.mongodb.org/browse/JAVA-5084.

@@ -100,7 +100,7 @@ default ByteBuf read(int numBytes, int additionalTimeout) throws IOException {
* Write each buffer in the list to the stream in order, asynchronously. This method should return immediately, and invoke the given
* callback on completion.
*
* @param buffers the buffers to write
* @param buffers the buffers to write. This method must {@linkplain ByteBuf#release() release} each buffer when it no longer needs it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. AsynchronousChannelStream doesn't seem to do it.

@stIncMale stIncMale requested review from jyemin and rozza July 8, 2023 00:18
Copy link
Member

@rozza rozza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline.

@stIncMale stIncMale requested a review from jyemin July 26, 2023 15:19
@stIncMale stIncMale requested a review from katcharov July 26, 2023 16:40
@katcharov katcharov removed their request for review July 26, 2023 17:19
@stIncMale stIncMale merged commit 2493753 into mongodb:master Jul 27, 2023
54 checks passed
@stIncMale stIncMale deleted the docImprovements branch July 27, 2023 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants