All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hu.akarnokd.rxjava2.subjects.nbp.NbpUnicastSubject Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC3
Show newest version
/**
 * Copyright 2015 David Karnok and Netflix, Inc.
 * 
 * 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 hu.akarnokd.rxjava2.subjects.nbp;

import java.util.Queue;
import java.util.concurrent.atomic.*;

import hu.akarnokd.rxjava2.disposables.Disposable;
import hu.akarnokd.rxjava2.internal.disposables.EmptyDisposable;
import hu.akarnokd.rxjava2.internal.queue.SpscLinkedArrayQueue;

/**
 * Subject that allows only a single Subscriber to subscribe to it during its lifetime.
 * 
 * 

This subject buffers notifications and replays them to the Subscriber as requested. * *

This subject holds an unbounded internal buffer. * *

If more than one Subscriber attempts to subscribe to this Subject, they * will receive an IllegalStateException if this Subject hasn't terminated yet, * or the Subscribers receive the terminal event (error or completion) if this * Subject has terminated. * * @param the value type unicasted */ public final class NbpUnicastSubject extends NbpSubject { /** * Creates an UnicastSubject with an internal buffer capacity hint 16. * @param the value type * @return an UnicastSubject instance */ public static NbpUnicastSubject create() { return create(16); } /** * Creates an UnicastSubject with the given internal buffer capacity hint. * @param the value type * @param capacityHint the hint to size the internal unbounded buffer * @return an UnicastSubject instance */ public static NbpUnicastSubject create(int capacityHint) { return create(capacityHint, null); } /** * Creates an UnicastSubject with the given internal buffer capacity hint and a callback for * the case when the single Subscriber cancels its subscription. * *

The callback, if not null, is called exactly once and * non-overlapped with any active replay. * * @param the value type * @param capacityHint the hint to size the internal unbounded buffer * @param onCancelled the optional callback * @return an UnicastSubject instance */ public static NbpUnicastSubject create(int capacityHint, Runnable onCancelled) { State state = new State(capacityHint, onCancelled); return new NbpUnicastSubject(state); } /** The subject state. */ final State state; /** * Constructs the Observable base class. * @param state the subject state */ protected NbpUnicastSubject(State state) { super(state); this.state = state; } // TODO may need to have a direct WIP field to avoid clashing on the object header /** Pads the WIP counter. */ static abstract class StatePad0 extends AtomicInteger { /** */ private static final long serialVersionUID = 7779228232971173701L; /** Cache line padding 1. */ volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; /** Cache line padding 2. */ volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; } /** The state of the UnicastSubject. */ static final class State extends StatePad0 implements NbpOnSubscribe, Disposable, NbpSubscriber { /** */ private static final long serialVersionUID = 5058617037583835632L; /** The queue that buffers the source events. */ final Queue queue; /** The single subscriber. */ volatile NbpSubscriber subscriber; /** Updater to the field subscriber. */ @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater SUBSCRIBER = AtomicReferenceFieldUpdater.newUpdater(State.class, NbpSubscriber.class, "subscriber"); /** Indicates the single subscriber has cancelled. */ volatile boolean cancelled; /** Indicates the source has terminated. */ volatile boolean done; /** * The terminal error if not null. * Must be set before writing to done and read after done == true. */ Throwable error; /** Set to 1 atomically for the first and only Subscriber. */ volatile int once; /** Updater to field once. */ @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater ONCE = AtomicIntegerFieldUpdater.newUpdater(State.class, "once"); /** * Called when the Subscriber has called cancel. * This allows early termination for those who emit into this * subject so that they can stop immediately */ Runnable onCancelled; /** * Constructs the state with the given capacity and optional cancellation callback. * @param capacityHint the capacity hint for the internal buffer * @param onCancelled the optional cancellation callback */ public State(int capacityHint, Runnable onCancelled) { this.onCancelled = onCancelled; queue = new SpscLinkedArrayQueue(capacityHint); } @Override public void accept(NbpSubscriber s) { if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { s.onSubscribe(this); SUBSCRIBER.lazySet(this, s); // full barrier in drain drain(); } else { s.onSubscribe(EmptyDisposable.INSTANCE); if (done) { Throwable e = error; if (e != null) { s.onError(e); } else { s.onComplete(); } } else { s.onError(new IllegalStateException("Only a single subscriber allowed.")); } } } @Override public void dispose() { if (!cancelled) { cancelled = true; if (getAndIncrement() == 0) { clear(queue); } } } void notifyOnCancelled() { Runnable r = onCancelled; onCancelled = null; if (r != null) { r.run(); } } /** * Clears the subscriber and the queue. * @param q the queue reference (avoid re-reading instance field). */ void clear(Queue q) { SUBSCRIBER.lazySet(this, null); q.clear(); notifyOnCancelled(); } @Override public void onSubscribe(Disposable s) { if (done || cancelled) { s.dispose(); } } @Override public void onNext(T t) { if (done || cancelled) { return; } if (t == null) { onError(new NullPointerException()); return; } queue.offer(t); drain(); } @Override public void onError(Throwable t) { if (done || cancelled) { return; } if (t == null) { t = new NullPointerException(); } error = t; done = true; drain(); } @Override public void onComplete() { if (done || cancelled) { return; } done = true; drain(); } void drain() { if (getAndIncrement() != 0) { return; } final Queue q = queue; NbpSubscriber a = subscriber; int missed = 1; for (;;) { if (cancelled) { clear(q); notifyOnCancelled(); return; } if (a != null) { boolean d = done; boolean empty = q.isEmpty(); if (d && empty) { SUBSCRIBER.lazySet(this, null); Throwable ex = error; if (ex != null) { a.onError(ex); } else { a.onComplete(); } return; } for (;;) { if (cancelled) { clear(q); notifyOnCancelled(); return; } d = done; T v = queue.poll(); empty = v == null; if (d && empty) { SUBSCRIBER.lazySet(this, null); Throwable ex = error; if (ex != null) { a.onError(ex); } else { a.onComplete(); } return; } if (empty) { break; } a.onNext(v); } } missed = addAndGet(-missed); if (missed == 0) { break; } if (a == null) { a = subscriber; } } } } @Override public void onSubscribe(Disposable s) { state.onSubscribe(s); } @Override public void onNext(T t) { state.onNext(t); } @Override public void onError(Throwable t) { state.onError(t); } @Override public void onComplete() { state.onComplete(); } @Override public boolean hasSubscribers() { return state.subscriber != null; } @Override public Throwable getThrowable() { State s = state; if (s.done) { return s.error; } return null; } @Override public boolean hasThrowable() { State s = state; return s.done && s.error != null; } @Override public boolean hasComplete() { State s = state; return s.done && s.error == null; } @Override public boolean hasValue() { return false; } @Override public T getValue() { return null; } @Override public T[] getValues(T[] array) { if (array.length != 0) { array[0] = null; } return array; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy