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

rx.internal.producers.QueuedProducer Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * Copyright 2015 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 rx.internal.producers;

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

import rx.*;
import rx.exceptions.*;
import rx.internal.operators.BackpressureUtils;
import rx.internal.util.atomic.SpscLinkedAtomicQueue;
import rx.internal.util.unsafe.*;

/**
 * Producer that holds an unbounded (or custom) queue, handles terminal events, 
 * enqueues values and relays them to a child subscriber on request.
 *
 * @param  the value type
 */
public final class QueuedProducer extends AtomicLong implements Producer, Observer {
     
    /** */
    private static final long serialVersionUID = 7277121710709137047L;
    
    final Subscriber child;
    final Queue queue;
    final AtomicInteger wip;
    
    Throwable error;
    volatile boolean done;
    
    static final Object NULL_SENTINEL = new Object();
    
    /**
     * Constructs an instance with the target child subscriber and an Spsc Linked (Atomic) Queue
     * as the queue implementation.
     * @param child the target child subscriber
     */
    public QueuedProducer(Subscriber child) {
        this(child, UnsafeAccess.isUnsafeAvailable() 
                ? new SpscLinkedQueue() : new SpscLinkedAtomicQueue());
    }
    /**
     * Constructs an instance with the target child subscriber and a custom queue implementation
     * @param child the target child subscriber
     * @param queue the queue to use
     */
    public QueuedProducer(Subscriber child, Queue queue) {
        this.child = child;
        this.queue = queue;
        this.wip = new AtomicInteger();
    }
     
    @Override
    public void request(long n) {
        if (n < 0) {
            throw new IllegalArgumentException("n >= 0 required");
        }
        if (n > 0) {
            BackpressureUtils.getAndAddRequest(this, n);
            drain();
        }
    }
    
    /**
     * Offers a value to this producer and tries to emit any queud values
     * if the child requests allow it.
     * @param value the value to enqueue and attempt to drain
     * @return true if the queue accepted the offer, false otherwise
     */
    public boolean offer(T value) {
        if (value == null) {
            if (!queue.offer(NULL_SENTINEL)) {
                return false;
            }
        } else {
            if (!queue.offer(value)) {
                return false;
            }
        }
        drain();
        return true;
    }
    
    @Override
    public void onNext(T value) {
        if (!offer(value)) {
            onError(new MissingBackpressureException());
        }
    }
     
    @Override
    public void onError(Throwable e) {
        error = e;
        done = true;
        drain();
    }
     
    @Override
    public void onCompleted() {
        done = true;
        drain();
    }
    
    private boolean checkTerminated(boolean isDone, 
            boolean isEmpty) {
        if (child.isUnsubscribed()) {
            return true;
        }
        if (isDone) {
            Throwable e = error;
            if (e != null) {
                queue.clear();
                child.onError(e);
                return true;
            } else
            if (isEmpty) {
                child.onCompleted();
                return true;
            }
        }
        return false;
    }
    
    private void drain() {
        if (wip.getAndIncrement() == 0) {
            final Subscriber c = child;
            final Queue q = queue;

            do {
                if (checkTerminated(done, q.isEmpty())) {    // (1)
                    return;
                }
                 
                wip.lazySet(1);
                 
                long r = get();
                long e = 0;
 
                while (r != 0) {
                    boolean d = done;
                    Object v = q.poll();
                    if (checkTerminated(d, v == null)) {
                        return;
                    } else
                    if (v == null) {
                        break;
                    }
                     
                    try {
                        if (v == NULL_SENTINEL) {
                            c.onNext(null);
                        } else {
                            @SuppressWarnings("unchecked")
                            T t = (T)v;
                            c.onNext(t);
                        }
                    } catch (Throwable ex) {
                        Exceptions.throwOrReport(ex, c, v != NULL_SENTINEL ? v : null);
                        return;
                    }
                    r--;
                    e++;
                }
                 
                if (e != 0 && get() != Long.MAX_VALUE) {
                    addAndGet(-e);
                }
            } while (wip.decrementAndGet() != 0);
        }
    }
}