@Override public T poll() { long ci = consumerIndex; int offset = (int) ci & mask; T value = get(offset); if (value == null) { return null; } CONSUMER_INDEX.lazySet(this, ci + 1); lazySet(offset, null); return value; }
protected void downstreamRequest(long n) { for (; ; ) { long r = requested; long u; if (r != Long.MAX_VALUE && n == Long.MAX_VALUE) { u = Long.MAX_VALUE; } else if (r + n < 0) { u = Long.MAX_VALUE; } else { u = r + n; } if (REQUESTED.compareAndSet(this, r, u)) { break; } } drain(); }
@Override public boolean offer(T value) { if (value == null) { throw new NullPointerException(); } long pi = producerIndex; int m = mask; int fullCheck = (int) (pi + capacitySkip) & m; if (get(fullCheck) != null) { return false; } int offset = (int) pi & m; PRODUCER_INDEX.lazySet(this, pi + 1); lazySet(offset, value); return true; }
/** * A single-producer single-consumer bounded queue with exact capacity tracking. * * <p>This means that a queue of 10 will allow exactly 10 offers, however, the underlying storage is * still power-of-2. * * <p>The implementation uses field updaters and thus should be platform-safe. */ public final class SpscExactAtomicArrayQueue<T> extends AtomicReferenceArray<T> implements Queue<T> { /** */ private static final long serialVersionUID = 6210984603741293445L; final int mask; final int capacitySkip; volatile long producerIndex; volatile long consumerIndex; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscExactAtomicArrayQueue> PRODUCER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscExactAtomicArrayQueue.class, "producerIndex"); @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscExactAtomicArrayQueue> CONSUMER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscExactAtomicArrayQueue.class, "consumerIndex"); public SpscExactAtomicArrayQueue(int capacity) { super(Pow2.roundToPowerOfTwo(capacity)); int len = length(); this.mask = len - 1; this.capacitySkip = len - capacity; } @Override public boolean offer(T value) { if (value == null) { throw new NullPointerException(); } long pi = producerIndex; int m = mask; int fullCheck = (int) (pi + capacitySkip) & m; if (get(fullCheck) != null) { return false; } int offset = (int) pi & m; PRODUCER_INDEX.lazySet(this, pi + 1); lazySet(offset, value); return true; } @Override public T poll() { long ci = consumerIndex; int offset = (int) ci & mask; T value = get(offset); if (value == null) { return null; } CONSUMER_INDEX.lazySet(this, ci + 1); lazySet(offset, null); return value; } @Override public T peek() { return get((int) consumerIndex & mask); } @Override public void clear() { while (poll() != null || !isEmpty()) ; } @Override public boolean isEmpty() { return producerIndex == consumerIndex; } @Override public int size() { long ci = consumerIndex; for (; ; ) { long pi = producerIndex; long ci2 = consumerIndex; if (ci == ci2) { return (int) (pi - ci2); } ci = ci2; } } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <E> E[] toArray(E[] a) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends T> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean add(T e) { throw new UnsupportedOperationException(); } @Override public T remove() { throw new UnsupportedOperationException(); } @Override public T element() { throw new UnsupportedOperationException(); } }
/** * A single-producer single-consumer array-backed queue which can allocate new arrays in case the * consumer is slower than the producer. */ public final class SpscLinkedArrayQueue<T> implements Queue<T> { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); protected volatile long producerIndex; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscLinkedArrayQueue> PRODUCER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "producerIndex"); protected int producerLookAheadStep; protected long producerLookAhead; protected int producerMask; protected AtomicReferenceArray<Object> producerBuffer; protected int consumerMask; protected AtomicReferenceArray<Object> consumerBuffer; protected volatile long consumerIndex; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscLinkedArrayQueue> CONSUMER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "consumerIndex"); private static final Object HAS_NEXT = new Object(); public SpscLinkedArrayQueue(final int bufferSize) { int p2capacity = Pow2.roundToPowerOfTwo(bufferSize); int mask = p2capacity - 1; AtomicReferenceArray<Object> buffer = new AtomicReferenceArray<>(p2capacity + 1); producerBuffer = buffer; producerMask = mask; adjustLookAheadStep(p2capacity); consumerBuffer = buffer; consumerMask = mask; producerLookAhead = mask - 1; // we know it's all empty to start with soProducerIndex(0L); } /** * {@inheritDoc} * * <p>This implementation is correct for single producer thread use only. */ @Override public final boolean offer(final T e) { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray<Object> buffer = producerBuffer; final long index = lpProducerIndex(); final int mask = producerMask; final int offset = calcWrappedOffset(index, mask); if (index < producerLookAhead) { return writeToQueue(buffer, e, index, offset); } else { final int lookAheadStep = producerLookAheadStep; // go around the buffer or resize if full (unless we hit max capacity) int lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); if (null == lvElement(buffer, lookAheadElementOffset)) { // LoadLoad producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room return writeToQueue(buffer, e, index, offset); } else if (null == lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full return writeToQueue(buffer, e, index, offset); } else { resize(buffer, index, offset, e, mask); // add a buffer and link old to new return true; } } } private boolean writeToQueue( final AtomicReferenceArray<Object> buffer, final T e, final long index, final int offset) { soProducerIndex(index + 1); // this ensures atomic write of long on 32bit platforms soElement(buffer, offset, e); // StoreStore return true; } private void resize( final AtomicReferenceArray<Object> oldBuffer, final long currIndex, final int offset, final T e, final long mask) { final int capacity = oldBuffer.length(); final AtomicReferenceArray<Object> newBuffer = new AtomicReferenceArray<>(capacity); producerBuffer = newBuffer; producerLookAhead = currIndex + mask - 1; soProducerIndex(currIndex + 1); // this ensures correctness on 32bit platforms soElement(newBuffer, offset, e); // StoreStore soNext(oldBuffer, newBuffer); soElement(oldBuffer, offset, HAS_NEXT); // new buffer is visible after element is // inserted } private void soNext(AtomicReferenceArray<Object> curr, AtomicReferenceArray<Object> next) { soElement(curr, calcDirectOffset(curr.length() - 1), next); } @SuppressWarnings("unchecked") private AtomicReferenceArray<Object> lvNext(AtomicReferenceArray<Object> curr) { return (AtomicReferenceArray<Object>) lvElement(curr, calcDirectOffset(curr.length() - 1)); } /** * {@inheritDoc} * * <p>This implementation is correct for single consumer thread use only. */ @SuppressWarnings("unchecked") @Override public final T poll() { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray<Object> buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); final Object e = lvElement(buffer, offset); // LoadLoad boolean isNextBuffer = e == HAS_NEXT; if (null != e && !isNextBuffer) { soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms soElement(buffer, offset, null); // StoreStore return (T) e; } else if (isNextBuffer) { return newBufferPoll(lvNext(buffer), index, mask); } return null; } @SuppressWarnings("unchecked") private T newBufferPoll( AtomicReferenceArray<Object> nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); final T n = (T) lvElement(nextBuffer, offsetInNew); // LoadLoad if (null == n) { return null; } else { soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms soElement(nextBuffer, offsetInNew, null); // StoreStore return n; } } /** * {@inheritDoc} * * <p>This implementation is correct for single consumer thread use only. */ @SuppressWarnings("unchecked") @Override public final T peek() { final AtomicReferenceArray<Object> buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); final Object e = lvElement(buffer, offset); // LoadLoad if (e == HAS_NEXT) { return newBufferPeek(lvNext(buffer), index, mask); } return (T) e; } @Override public void clear() { while (poll() != null || !isEmpty()) ; } @SuppressWarnings("unchecked") private T newBufferPeek( AtomicReferenceArray<Object> nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); return (T) lvElement(nextBuffer, offsetInNew); // LoadLoad } @Override public final int size() { /* * It is possible for a thread to be interrupted or reschedule between the read of the producer and * consumer indices, therefore protection is required to ensure size is within valid range. In the * event of concurrent polls/offers to this method the size is OVER estimated as we read consumer * index BEFORE the producer index. */ long after = lvConsumerIndex(); while (true) { final long before = after; final long currentProducerIndex = lvProducerIndex(); after = lvConsumerIndex(); if (before == after) { return (int) (currentProducerIndex - after); } } } @Override public boolean isEmpty() { return lvProducerIndex() == lvConsumerIndex(); } private void adjustLookAheadStep(int capacity) { producerLookAheadStep = Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP); } private long lvProducerIndex() { return producerIndex; } private long lvConsumerIndex() { return consumerIndex; } private long lpProducerIndex() { return producerIndex; } private long lpConsumerIndex() { return consumerIndex; } private void soProducerIndex(long v) { PRODUCER_INDEX.lazySet(this, v); } private void soConsumerIndex(long v) { CONSUMER_INDEX.lazySet(this, v); } private static final int calcWrappedOffset(long index, int mask) { return calcDirectOffset((int) index & mask); } private static final int calcDirectOffset(int index) { return index; } private static final void soElement(AtomicReferenceArray<Object> buffer, int offset, Object e) { buffer.lazySet(offset, e); } private static final <E> Object lvElement(AtomicReferenceArray<Object> buffer, int offset) { return buffer.get(offset); } @Override public final Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <E> E[] toArray(E[] a) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends T> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean add(T e) { throw new UnsupportedOperationException(); } @Override public T remove() { throw new UnsupportedOperationException(); } @Override public T element() { throw new UnsupportedOperationException(); } /** * Offer two elements at the same time. * * <p>Don't use the regular offer() with this at all! * * @param first * @param second * @return */ public boolean offer(T first, T second) { final AtomicReferenceArray<Object> buffer = producerBuffer; final long p = producerIndex; final int m = producerMask; int pi = calcWrappedOffset(p + 2, m); if (null == lvElement(buffer, pi)) { pi = calcWrappedOffset(p, m); soElement(buffer, pi + 1, second); soProducerIndex(p + 2); soElement(buffer, pi, first); } else { final int capacity = buffer.length(); final AtomicReferenceArray<Object> newBuffer = new AtomicReferenceArray<>(capacity); producerBuffer = newBuffer; pi = calcWrappedOffset(p, m); soElement(newBuffer, pi + 1, second); // StoreStore soElement(newBuffer, pi, first); soNext(buffer, newBuffer); soProducerIndex(p + 2); // this ensures correctness on 32bit platforms soElement(buffer, pi, HAS_NEXT); // new buffer is visible after element is } return true; } }
private void soConsumerIndex(long v) { CONSUMER_INDEX.lazySet(this, v); }
private void soProducerIndex(long v) { PRODUCER_INDEX.lazySet(this, v); }
static final class SourceSubscriber<T> extends Subscriber<Observable<? extends T>> { final NotificationLite<T> nl = NotificationLite.instance(); final int maxConcurrency; final Subscriber<T> s; final CompositeSubscription csub; final Object guard; volatile int wip; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater<SourceSubscriber> WIP = AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "wip"); volatile int sourceIndex; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater<SourceSubscriber> SOURCE_INDEX = AtomicIntegerFieldUpdater.newUpdater(SourceSubscriber.class, "sourceIndex"); /** Guarded by guard. */ int active; /** Guarded by guard. */ final Queue<Observable<? extends T>> queue; /** Indicates the emitting phase. Guarded by this. */ boolean emitting; /** Counts the missed emitting calls. Guarded by this. */ int missedEmitting; /** The last buffer index in the round-robin drain scheme. Accessed while emitting == true. */ int lastIndex; /** Guarded by itself. */ final List<MergeItemSubscriber> subscribers; volatile long requested; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SourceSubscriber> REQUESTED = AtomicLongFieldUpdater.newUpdater(SourceSubscriber.class, "requested"); public SourceSubscriber(int maxConcurrency, Subscriber<T> s, CompositeSubscription csub) { super(s); this.maxConcurrency = maxConcurrency; this.s = s; this.csub = csub; this.guard = new Object(); this.queue = new ArrayDeque<Observable<? extends T>>(maxConcurrency); this.subscribers = Collections.synchronizedList(new ArrayList<MergeItemSubscriber>()); this.wip = 1; } @Override public void onStart() { request(maxConcurrency); } @Override public void onNext(Observable<? extends T> t) { synchronized (guard) { queue.add(t); } subscribeNext(); } void subscribeNext() { Observable<? extends T> t; synchronized (guard) { t = queue.peek(); if (t == null || active >= maxConcurrency) { return; } active++; queue.poll(); } MergeItemSubscriber itemSub = new MergeItemSubscriber(SOURCE_INDEX.getAndIncrement(this)); subscribers.add(itemSub); csub.add(itemSub); WIP.incrementAndGet(this); t.unsafeSubscribe(itemSub); request(1); } @Override public void onError(Throwable e) { Object[] active; synchronized (subscribers) { active = subscribers.toArray(); subscribers.clear(); } try { s.onError(e); unsubscribe(); } finally { for (Object o : active) { @SuppressWarnings("unchecked") MergeItemSubscriber a = (MergeItemSubscriber) o; a.release(); } } } @Override public void onCompleted() { WIP.decrementAndGet(this); drain(); } protected void downstreamRequest(long n) { for (; ; ) { long r = requested; long u; if (r != Long.MAX_VALUE && n == Long.MAX_VALUE) { u = Long.MAX_VALUE; } else if (r + n < 0) { u = Long.MAX_VALUE; } else { u = r + n; } if (REQUESTED.compareAndSet(this, r, u)) { break; } } drain(); } protected void drain() { synchronized (this) { if (emitting) { missedEmitting++; return; } emitting = true; missedEmitting = 0; } final List<SourceSubscriber<T>.MergeItemSubscriber> subs = subscribers; final Subscriber<T> child = s; Object[] active = new Object[subs.size()]; do { long r; outer: while ((r = requested) > 0) { int idx = lastIndex; synchronized (subs) { if (subs.size() == active.length) { active = subs.toArray(active); } else { active = subs.toArray(); } } int resumeIndex = 0; int j = 0; for (Object o : active) { @SuppressWarnings("unchecked") MergeItemSubscriber e = (MergeItemSubscriber) o; if (e.index == idx) { resumeIndex = j; break; } j++; } int sumConsumed = 0; for (int i = 0; i < active.length; i++) { j = (i + resumeIndex) % active.length; @SuppressWarnings("unchecked") final MergeItemSubscriber e = (MergeItemSubscriber) active[j]; final RxRingBuffer b = e.buffer; lastIndex = e.index; if (!e.once && b.peek() == null) { subs.remove(e); synchronized (guard) { this.active--; } csub.remove(e); e.release(); subscribeNext(); WIP.decrementAndGet(this); continue outer; } int consumed = 0; Object v; while (r > 0 && (v = b.poll()) != null) { nl.accept(child, v); if (child.isUnsubscribed()) { return; } r--; consumed++; } if (consumed > 0) { sumConsumed += consumed; REQUESTED.addAndGet(this, -consumed); e.requestMore(consumed); } if (r == 0) { break outer; } } if (sumConsumed == 0) { break; } } if (active.length == 0) { if (wip == 0) { child.onCompleted(); return; } } synchronized (this) { if (missedEmitting == 0) { emitting = false; break; } missedEmitting = 0; } } while (true); } final class MergeItemSubscriber extends Subscriber<T> { volatile boolean once = true; final int index; final RxRingBuffer buffer; public MergeItemSubscriber(int index) { buffer = RxRingBuffer.getSpmcInstance(); this.index = index; } @Override public void onStart() { request(RxRingBuffer.SIZE); } @Override public void onNext(T t) { try { buffer.onNext(t); } catch (MissingBackpressureException ex) { onError(ex); return; } drain(); } @Override public void onError(Throwable e) { SourceSubscriber.this.onError(e); } @Override public void onCompleted() { if (once) { once = false; drain(); } } /** Request more from upstream. */ void requestMore(long n) { request(n); } void release() { // NO-OP for now buffer.release(); } } }
protected void drain() { synchronized (this) { if (emitting) { missedEmitting++; return; } emitting = true; missedEmitting = 0; } final List<SourceSubscriber<T>.MergeItemSubscriber> subs = subscribers; final Subscriber<T> child = s; Object[] active = new Object[subs.size()]; do { long r; outer: while ((r = requested) > 0) { int idx = lastIndex; synchronized (subs) { if (subs.size() == active.length) { active = subs.toArray(active); } else { active = subs.toArray(); } } int resumeIndex = 0; int j = 0; for (Object o : active) { @SuppressWarnings("unchecked") MergeItemSubscriber e = (MergeItemSubscriber) o; if (e.index == idx) { resumeIndex = j; break; } j++; } int sumConsumed = 0; for (int i = 0; i < active.length; i++) { j = (i + resumeIndex) % active.length; @SuppressWarnings("unchecked") final MergeItemSubscriber e = (MergeItemSubscriber) active[j]; final RxRingBuffer b = e.buffer; lastIndex = e.index; if (!e.once && b.peek() == null) { subs.remove(e); synchronized (guard) { this.active--; } csub.remove(e); e.release(); subscribeNext(); WIP.decrementAndGet(this); continue outer; } int consumed = 0; Object v; while (r > 0 && (v = b.poll()) != null) { nl.accept(child, v); if (child.isUnsubscribed()) { return; } r--; consumed++; } if (consumed > 0) { sumConsumed += consumed; REQUESTED.addAndGet(this, -consumed); e.requestMore(consumed); } if (r == 0) { break outer; } } if (sumConsumed == 0) { break; } } if (active.length == 0) { if (wip == 0) { child.onCompleted(); return; } } synchronized (this) { if (missedEmitting == 0) { emitting = false; break; } missedEmitting = 0; } } while (true); }