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

rx.internal.operators.BufferUntilSubscriber Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * Copyright 2014 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.operators;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;

import rx.*;
import rx.functions.Action0;
import rx.subjects.Subject;
import rx.subscriptions.Subscriptions;

/**
 * A solution to the "time gap" problem that occurs with {@code groupBy} and {@code pivot}.
 * 

* This currently has temporary unbounded buffers. It needs to become bounded and then do one of two things: *

    *
  1. blow up and make the user do something about it
  2. *
  3. work with the backpressure solution ... still to be implemented (such as co-routines)
  4. *

* Generally the buffer should be very short lived (milliseconds) and then stops being involved. It can become a * memory leak though if a {@code GroupedObservable} backed by this class is emitted but never subscribed to * (such as filtered out). In that case, either a time-bomb to throw away the buffer, or just blowing up and * making the user do something about it is needed. *

* For example, to filter out {@code GroupedObservable}s, perhaps they need a silent {@code subscribe()} on them * to just blackhole the data. *

* This is an initial start at solving this problem and solves the immediate problem of {@code groupBy} and * {@code pivot} and trades off the possibility of memory leak for deterministic functionality. * * @see the Github issue describing the time gap problem * @param * the type of the items to be buffered */ public final class BufferUntilSubscriber extends Subject { final State state; private boolean forward; /** * Creates a default, unbounded buffering Subject instance. * @param the value type * @return the instance */ public static BufferUntilSubscriber create() { State state = new State(); return new BufferUntilSubscriber(state); } /** The common state. */ static final class State extends AtomicReference> { /** */ private static final long serialVersionUID = 8026705089538090368L; final Object guard = new Object(); /* protected by guard */ boolean emitting; final ConcurrentLinkedQueue buffer = new ConcurrentLinkedQueue(); boolean casObserverRef(Observer expected, Observer next) { return compareAndSet(expected, next); } } static final class OnSubscribeAction implements OnSubscribe { final State state; public OnSubscribeAction(State state) { this.state = state; } @Override public void call(final Subscriber s) { if (state.casObserverRef(null, s)) { s.add(Subscriptions.create(new Action0() { @SuppressWarnings("unchecked") @Override public void call() { state.set(EMPTY_OBSERVER); } })); boolean win = false; synchronized (state.guard) { if (!state.emitting) { state.emitting = true; win = true; } } if (win) { while (true) { Object o; while ((o = state.buffer.poll()) != null) { NotificationLite.accept(state.get(), o); } synchronized (state.guard) { if (state.buffer.isEmpty()) { // Although the buffer is empty, there is still a chance // that further events may be put into the `buffer`. // `emit(Object v)` should handle it. state.emitting = false; break; } } } } } else { s.onError(new IllegalStateException("Only one subscriber allowed!")); } } } private BufferUntilSubscriber(State state) { super(new OnSubscribeAction(state)); this.state = state; } private void emit(Object v) { synchronized (state.guard) { state.buffer.add(v); if (state.get() != null && !state.emitting) { // Have an observer and nobody is emitting, // should drain the `buffer` forward = true; state.emitting = true; } } if (forward) { Object o; while ((o = state.buffer.poll()) != null) { NotificationLite.accept(state.get(), o); } // Because `emit(Object v)` will be called in sequence, // no event will be put into `buffer` after we drain it. } } @Override public void onCompleted() { if (forward) { state.get().onCompleted(); } else { emit(NotificationLite.completed()); } } @Override public void onError(Throwable e) { if (forward) { state.get().onError(e); } else { emit(NotificationLite.error(e)); } } @Override public void onNext(T t) { if (forward) { state.get().onNext(t); } else { emit(NotificationLite.next(t)); } } @Override public boolean hasObservers() { synchronized (state.guard) { return state.get() != null; } } @SuppressWarnings("rawtypes") final static Observer EMPTY_OBSERVER = new Observer() { @Override public void onCompleted() { // deliberately no op } @Override public void onError(Throwable e) { // deliberately no op } @Override public void onNext(Object t) { // deliberately no op } }; }