/** * Copyright 2014 Ricardo Padilha * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.dsys.snio.impl.buffer; import javax.annotation.Nonnull; import javax.annotation.meta.When; import net.dsys.snio.api.buffer.InterruptedByClose; import com.lmax.disruptor.AlertException; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceBarrier; import com.lmax.disruptor.TimeoutException; /** * An iterator for {@link RingBuffer}. * <br> * Typical usage: * <pre> * RingBuffer rb = ... * RingBufferIterator it = new RingBufferIterator(rb); * it.open(); * while (true) { * it.await(); * while (it.hasNext()) { * Object o = it.get(); * ... processing ... * it.advance(); * } * } * </pre> * * Non-blocking usage: * <pre> * RingBuffer rb = ... * RingBufferIterator it = new RingBufferIterator(rb); * it.open(); * while (true) { * it.update(); * while (it.hasNext()) { * Object o = it.get(); * ... processing ... * it.advance(); * } * } * </pre> * * @author Ricardo Padilha */ final class RingBufferIterator<E> { private final RingBuffer<E> buffer; private final Sequence[] leading; private SequenceBarrier barrier; private Sequence sequence; private long available; private long cursor; private volatile boolean closed; /** * Creates an iterator that will follow the leading sequences. * * @param buffer * @param leading */ private RingBufferIterator(@Nonnull final RingBuffer<E> buffer, @Nonnull(when = When.MAYBE) final Sequence... leading) { if (buffer == null) { throw new NullPointerException("buffer == null"); } this.buffer = buffer; this.leading = leading; this.closed = false; } /** * Creates an iterator for a given RingBuffer. * * @param buffer */ RingBufferIterator(@Nonnull final RingBuffer<E> buffer) { this(buffer, (Sequence[]) null); } /** * Creates an iterator that will follow all the leading iterators. * This iterator will only process events once all leading iterators advanced. * * @param leaders */ @SafeVarargs RingBufferIterator(@Nonnull final RingBufferIterator<E>... leaders) { if (leaders == null) { throw new NullPointerException("leaders == null"); } final int k = leaders.length; if (k == 0) { throw new IllegalArgumentException("leaders.length == 0"); } this.leading = new Sequence[k]; RingBuffer<E> buffer = null; for (int i = 0; i < k; i++) { final RingBufferIterator<E> it = leaders[i]; if (it == null) { throw new NullPointerException("leaders[" + i + "] == null"); } if (!it.isOpen()) { throw new IllegalArgumentException("leaders[" + i + "] is not open"); } if (buffer == null) { buffer = leaders[i].buffer; } else if (!buffer.equals(leaders[i].buffer)) { throw new IllegalArgumentException("leaders[" + i + "] has a different RingBuffer"); } leading[i] = it.sequence; } this.buffer = buffer; } /** * An iterator does not affect the RingBuffer until it is opened. */ public void open() { this.barrier = buffer.newBarrier(leading); final long start = buffer.getCursor(); this.sequence = new Sequence(start); this.available = start; this.cursor = start; buffer.addGatingSequences(sequence); } /** * @return <code>true</code> if this iterator is opened. */ public boolean isOpen() { return closed; } /** * Closes this iterator. Removes it from the RingBuffer. */ public void close() { buffer.removeGatingSequence(sequence); barrier.alert(); closed = true; } /** * Creates a new iterator that will follow the same barrier as this one. * The new iterator will be able to process the same events as this one, in parallel. */ @Nonnull public RingBufferIterator<E> createPeer() { if (!isOpen()) { throw new IllegalStateException("iterator must be opened before creating a peer"); } return new RingBufferIterator<>(buffer, leading); } /** * Creates a new iterator that will follow this iterator. * The new iterator will only be able to process new events once this one advances. */ @Nonnull public RingBufferIterator<E> createFollower() { if (!isOpen()) { throw new IllegalStateException("iterator must be opened before creating a follower"); } return new RingBufferIterator<>(buffer, sequence); } /** * Updates the available sequence by waiting on the barrier. * * @throws InterruptedException * @see SequenceBarrier#waitFor(long) */ public void await() throws InterruptedException { if (!isOpen()) { throw new InterruptedByClose(); } try { available = barrier.waitFor(cursor); } catch (final AlertException e) { throw new InterruptedByClose(); } catch (final TimeoutException e) { throw new RuntimeException(e); } } /** * Non-blocking update of the available sequence on the barrier. * * @see SequenceBarrier#getCursor() */ public void update() { long n = buffer.getCursor(); while (!buffer.isPublished(n)) { n--; } available = n; } /** * @return <code>true</code> if this iterator's sequence is lower than the * (cached) available */ public boolean hasNext() { return cursor <= available; } /** * @return the element at this iterator's current sequence. */ @Nonnull public E get() { return buffer.get(cursor); } /** * @return this iterator's sequence. */ public long cursor() { return cursor; } /** * Increments this iterator's sequence. */ public void advance() { if (cursor == available) { sequence.set(cursor); } cursor++; } }