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

Code before:
    private final long tombstone;
    private int size;
    private int head;
    private int tail = -1;

    CompletionBuffer(int numCompletions, long tombstone) {
        capacity = MathUtil.findNextPositivePowerOfTwo(numCompletions * 2);
        array = new long[capacity];
        mask = capacity - 1;
        for (int i = 1; i < capacity; i++) {
            if (i % 2 == 0) {
                array[i] = tombstone;
            }
        }
        this.tombstone = tombstone;
    }

    private boolean add(int res, int flags, long udata) {
        if (udata == tombstone) {
            throw new IllegalStateException("udata can't be the same as the tombstone");
        }
        // Pack res and flag into the first slot.
        array[combinedIdx(tail + 1)] = (((long) res) << 32) | (flags & 0xffffffffL);
        array[udataIdx(tail + 1)] = udata;

        tail += 2;
        size += 2;
        return size < capacity;
    }

    /**
     * Drain completions from the given {@link CompletionQueue}.
     *
     * @param queue the queue to drain from.
     * @return      {@code true} if the whole queue was drained, {@code false} otherwise.
     */
    boolean drain(CompletionQueue queue) {
        queue.process(callback);
        return !queue.hasCompletions();
    }

    /**
     * Process buffered completions via the given {@link CompletionCallback}.
     *
     * @param callback  the callback to use.
     * @return          the number of processed completions.
     */
    int processNow(CompletionCallback callback) {
        int i = 0;

        boolean processing = true;

Review comment (suggestion):
We need to prevent data corruption in the case where `drain` is called but the `CompletionBuffer` is already full.

Code after:
    private final long tombstone;
    private int size;
    private int head;
    private int tail = -1;

    CompletionBuffer(int numCompletions, long tombstone) {
        capacity = MathUtil.findNextPositivePowerOfTwo(numCompletions * 2);
        array = new long[capacity];
        mask = capacity - 1;
        for (int i = 0; i < capacity; i += 2) {
            array[i] = tombstone;
        }
        this.tombstone = tombstone;
    }

    private boolean add(int res, int flags, long udata) {
        if (udata == tombstone) {
            throw new IllegalStateException("udata can't be the same as the tombstone");
        }
        // Pack res and flag into the first slot.
        array[combinedIdx(tail + 1)] = (((long) res) << 32) | (flags & 0xffffffffL);
        array[udataIdx(tail + 1)] = udata;

        tail += 2;
        size += 2;
        return size < capacity;
    }

    /**
     * Drain completions from the given {@link CompletionQueue}.
     *
     * @param queue the queue to drain from.
     * @return      {@code true} if the whole queue was drained, {@code false} otherwise.
     */
    boolean drain(CompletionQueue queue) {
        if (size == capacity) {
            // The buffer is already full.
            return false;
        }
        queue.process(callback);
        return !queue.hasCompletions();
    }

    /**
     * Process buffered completions via the given {@link CompletionCallback}.
     *
     * @param callback  the callback to use.
     * @return          the number of processed completions.
     */
    int processNow(CompletionCallback callback) {
        int i = 0;