Repository: netty/netty
File: transport-classes-io_uring/src/main/java/io/netty/channel/uring/AbstractIoUringServerChannel.java (Java)

Code before:
            throw new UnsupportedOperationException();
        }

        @Override
        protected int scheduleRead0(boolean first) {
            assert acceptId == 0;
            final IoUringRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
            allocHandle.attemptedBytesRead(1);

            int fd = fd().intValue();
            IoUringIoRegistration registration = registration();

            // Depending on if socketWasEmpty is true we will arm the poll upfront and skip the initial transfer
            // attempt.
            // See https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023#socket-state
            //
            // Depending on if this is the first read or not we will use Native.IORING_ACCEPT_DONT_WAIT.
            // The idea is that if the socket is blocking we can do the first read in a blocking fashion
            // and so not need to also register POLLIN. As we can not 100 % sure if reads after the first will
            // be possible directly we schedule these with Native.IORING_ACCEPT_DONT_WAIT. This allows us to still be
            // able to signal the fireChannelReadComplete() in a timely manner and be consistent with other
            // transports.
            final short ioPrio;

            if (first) {
                ioPrio = socketWasEmpty ? Native.IORING_RECVSEND_POLL_FIRST : 0;
            } else {
                ioPrio = Native.IORING_ACCEPT_DONT_WAIT;
            }
            // See https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.10#improvements-for-accept
            IoUringIoOps ops = IoUringIoOps.newAccept(fd, (byte) 0, 0, ioPrio,
                    acceptedAddressMemoryAddress, acceptedAddressLengthMemoryAddress, nextOpsId());
            acceptId = registration.submit(ops);
            if (acceptId == 0) {
                return 0;
            }
            return 1;
        }

        @Override
        protected void readComplete0(byte op, int res, int flags, short data, int outstanding) {
            assert acceptId != 0;
            acceptId = 0;
            final IoUringRecvByteAllocatorHandle allocHandle =
                    (IoUringRecvByteAllocatorHandle) unsafe()
                            .recvBufAllocHandle();
            final ChannelPipeline pipeline = pipeline();
            allocHandle.lastBytesRead(res);

            if (res >= 0) {
                allocHandle.incMessagesRead(1);

Review comment (security):
I think it'd be easier to follow if we extracted a method for this so `socketWasEmpty` becomes a parameter, instead of a field. It's effectively a secret parameter right now, where we have a finally-clause to reset it, for instance. With the field, we have to worry about reentrancy and managing the state between calls.

Code after:

        @Override
        protected int scheduleRead0(boolean first, boolean socketIsEmpty) {
            assert acceptId == 0;
            final IoUringRecvByteAllocatorHandle allocHandle = recvBufAllocHandle();
            allocHandle.attemptedBytesRead(1);

            int fd = fd().intValue();
            IoUringIoRegistration registration = registration();

            // Depending on if socketIsEmpty is true we will arm the poll upfront and skip the initial transfer
            // attempt.
            // See https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023#socket-state
            //
            // Depending on if this is the first read or not we will use Native.IORING_ACCEPT_DONT_WAIT.
            // The idea is that if the socket is blocking we can do the first read in a blocking fashion
            // and so not need to also register POLLIN. As we can not 100 % sure if reads after the first will
            // be possible directly we schedule these with Native.IORING_ACCEPT_DONT_WAIT. This allows us to still be
            // able to signal the fireChannelReadComplete() in a timely manner and be consistent with other
            // transports.
            final short ioPrio;

            if (first) {
                ioPrio = socketIsEmpty ? Native.IORING_RECVSEND_POLL_FIRST : 0;
            } else {
                ioPrio = Native.IORING_ACCEPT_DONT_WAIT;
            }
            // See https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.10#improvements-for-accept
            IoUringIoOps ops = IoUringIoOps.newAccept(fd, (byte) 0, 0, ioPrio,
                    acceptedAddressMemoryAddress, acceptedAddressLengthMemoryAddress, nextOpsId());
            acceptId = registration.submit(ops);
            if (acceptId == 0) {
                return 0;
            }
            return 1;
        }

        @Override
        protected void readComplete0(byte op, int res, int flags, short data, int outstanding) {
            assert acceptId != 0;
            acceptId = 0;
            final IoUringRecvByteAllocatorHandle allocHandle =
                    (IoUringRecvByteAllocatorHandle) unsafe()
                            .recvBufAllocHandle();
            final ChannelPipeline pipeline = pipeline();
            allocHandle.lastBytesRead(res);

            if (res >= 0) {
                allocHandle.incMessagesRead(1);
                try {
                    Channel channel = newChildChannel(