Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Use a non-blocking output stream for socket writes #1769

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

Merged
merged 2 commits into from
Feb 4, 2022

Conversation

derklaro
Copy link
Contributor

@derklaro derklaro commented Dec 31, 2021

Essentially: Channels.newOutputStream and Channels.newInputStream are synchronizing on the blocking lock provided by the SelectableChannel#blockingLock method. This prevents writing a command to a container stdin while there is no output printed to stdout. This is a long known issue in java and was fixed partially in java 13 (see https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8222774). Concurrent read/writes aren't a problem there anymore as well.

The OutputStream implementation used is adapted from the one returned by Channels.newOutputStream and moves the code of Channels.writeFullyImpl directly into the write method.

References:

Closes #1768

@derklaro derklaro changed the title Use a non-blocking output streams for socket writes Use a non-blocking output stream for socket writes Dec 31, 2021
@derklaro
Copy link
Contributor Author

Sorry for the checkstyle issues, tested this as an anonymus class before moving to an inner class and cleaning it up properly :/

@bsideup
Copy link
Member

bsideup commented Dec 31, 2021

@derklaro no worries, and thanks for spotting the issue and providing a fix!

Do you think this is something testable? Would really love to have it covered, to avoid regressions in the future in this critical piece of code πŸ‘

@derklaro
Copy link
Contributor Author

I'm not sure if this can be tested actually as it's requiring java 16+ to run while all builds can only run on java 8 πŸ€”

@bsideup
Copy link
Member

bsideup commented Jan 3, 2022

@derklaro ah, I didn't notice that this code is only for UnixSocket that requires Java 16. But then I am confused - you said that it is fixed in Java 13, and the minimum Java requirement for it to run is Java 16. Why do we need a fix then?

@derklaro
Copy link
Contributor Author

derklaro commented Jan 3, 2022

I was wrong there. They resolved other issues by replacing it with locks but not the synchronize issue. The point there is that other channels might not allow concurrent reading and writing which may lead to problems...

@derklaro
Copy link
Contributor Author

derklaro commented Jan 4, 2022

To bring everything in order again as my comment is a bit messed up. JDK-4774871 (Channels.newInputStream() and newOutputStream() synchronize too much) describes the issue I was oberserving with the channel input and output stream methods previously. JDK-4509080 (Streams inhibit concurrent reading & writing) is explecitly talking about the Socket#write and Socket#read methods which are blocking as well, comment on that issue: "The socket adaptors were partly re-implemented as part of JDK-8222774 and so are no longer constrained to use the blockingLock. As a result, concurrent reading and writing with the socket adpator is possible", which does not mean that Channels.newInputStream and Channels.newOutputStream are no longer blocking as they will still block on the lock provided by SelectableByteChannel.

To sum it up: Socket#write and Socket#read are no longer blocking while the Channels.new Input/Output Stream methods are still using the blocking lock (which is an expected behaviour).

So there are 2 possibilities here: Either we re-implement our own input and output stream, or (what i did) implement one of them on our own. (OutputStream is the better choice imo as it isn't as much code as the InputStream) to prevent the concurrent synchronize on the blocking lock.

Hope this explains everything better now! :)

@0utplay
Copy link

0utplay commented Jan 17, 2022

Hey, are there any updates on this? I'm facing the same issue 😒

@bsideup
Copy link
Member

bsideup commented Jan 26, 2022

Hi @derklaro,

I finally got a chance to dive deeper into this issue. Do I understand correctly that the actual problem is in Channels#writeFully that uses sc.blockingLock() in case of a SelectableChannel?

If so, perhaps a much simpler solution would be to make passed WritableByteChannel not implement SelectableChannel so that it won't be synchronized on sc.blockingLock()?

@derklaro
Copy link
Contributor Author

Hey,

You mean by creating a special implementation of WriteableByteChannel which just passes all method calls down to the actual channel? Thats sound easier to me as well πŸ˜‚

@bsideup
Copy link
Member

bsideup commented Jan 26, 2022

@derklaro yep, that's the idea :) To reuse as much JDK's code as possible. Something like:

        class PlainWritableByteChannel implements WritableByteChannel {

            @Override
            public int write(ByteBuffer src) throws IOException {
                return socketChannel.write(src);
            }

            @Override
            public boolean isOpen() {
                return socketChannel.isOpen();
            }

            @Override
            public void close() throws IOException {
                socketChannel.close();
            }
        }

        return Channels.newOutputStream(new PlainWritableByteChannel());

@bsideup
Copy link
Member

bsideup commented Jan 26, 2022

Also... any chance we can unit test this?

@derklaro
Copy link
Contributor Author

The only way i see is to write a test which only runs when we test on Java 16+ or above, which kinda breaks the purpose for a test...

@bsideup
Copy link
Member

bsideup commented Jan 26, 2022

@derklaro okay, I tried playing with implementing it with JDK8 only classes but the test becomes a bit too involved.

Also, AttachContainerCmdIT#attachContainerWithStdin will catch this (and also confirms that the PR fixes the issue), so it should be a matter of adding JDK16+ testing to the CI matrix (can/should be done separately), so I am fine with proceeding without a test.

As a result, concurrent reading and writing with the socket adpator is possible", which does not mean that Channels.newInputStream and Channels.newOutputStream are no longer blocking as they will still block on the lock provided by SelectableByteChannel.

Let me double check the implications of removing the synchronization with the JDK community because it must be there for a reason, and if it's okay for us to not have it, I will be happy to finally merge this πŸŽ‰

@derklaro
Copy link
Contributor Author

Sounds good to me πŸ‘

@bsideup
Copy link
Member

bsideup commented Jan 27, 2022

FTR I just added Java 17 to the CI matrix. In fact, this PR should fix it because currently the main branch is broken and fails on that "attach" test :D

@bsideup
Copy link
Member

bsideup commented Feb 4, 2022

Okay... after not hearing any concerns from anyone, I am going to YOLO it πŸ˜‚

I can confirm that it fixed the test, and other tests are passing as well, so I see no reasons why we shouldn't be doing this.

@bsideup bsideup merged commit e100e25 into docker-java:master Feb 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Writes to stdin not recevied directly
3 participants