/**
* 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.TimeoutException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import rx.Observable;
import rx.Observable.Operator;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Func3;
import rx.functions.Func4;
import rx.observers.SerializedSubscriber;
import rx.subscriptions.SerialSubscription;
class OperatorTimeoutBase<T> implements Operator<T, T> {
/**
* Set up the timeout action on the first value.
*
* @param <T>
*/
/* package-private */static interface FirstTimeoutStub<T> extends
Func3<TimeoutSubscriber<T>, Long, Scheduler.Worker, Subscription> {
}
/**
* Set up the timeout action based on every value
*
* @param <T>
*/
/* package-private */static interface TimeoutStub<T> extends
Func4<TimeoutSubscriber<T>, Long, T, Scheduler.Worker, Subscription> {
}
private final FirstTimeoutStub<T> firstTimeoutStub;
private final TimeoutStub<T> timeoutStub;
private final Observable<? extends T> other;
private final Scheduler scheduler;
/* package-private */OperatorTimeoutBase(FirstTimeoutStub<T> firstTimeoutStub, TimeoutStub<T> timeoutStub, Observable<? extends T> other, Scheduler scheduler) {
this.firstTimeoutStub = firstTimeoutStub;
this.timeoutStub = timeoutStub;
this.other = other;
this.scheduler = scheduler;
}
@Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
Scheduler.Worker inner = scheduler.createWorker();
subscriber.add(inner);
final SerialSubscription serial = new SerialSubscription();
subscriber.add(serial);
// Use SynchronizedSubscriber for safe memory access
// as the subscriber will be accessed in the current thread or the
// scheduler or other Observables.
final SerializedSubscriber<T> synchronizedSubscriber = new SerializedSubscriber<T>(subscriber);
TimeoutSubscriber<T> timeoutSubscriber = new TimeoutSubscriber<T>(synchronizedSubscriber, timeoutStub, serial, other, inner);
serial.set(firstTimeoutStub.call(timeoutSubscriber, 0L, inner));
return timeoutSubscriber;
}
/* package-private */static class TimeoutSubscriber<T> extends
Subscriber<T> {
private final SerialSubscription serial;
private final Object gate = new Object();
private final SerializedSubscriber<T> serializedSubscriber;
private final TimeoutStub<T> timeoutStub;
private final Observable<? extends T> other;
private final Scheduler.Worker inner;
volatile int terminated;
volatile long actual;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater<TimeoutSubscriber> TERMINATED_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(TimeoutSubscriber.class, "terminated");
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater<TimeoutSubscriber> ACTUAL_UPDATER
= AtomicLongFieldUpdater.newUpdater(TimeoutSubscriber.class, "actual");
private TimeoutSubscriber(
SerializedSubscriber<T> serializedSubscriber,
TimeoutStub<T> timeoutStub, SerialSubscription serial,
Observable<? extends T> other,
Scheduler.Worker inner) {
super(serializedSubscriber);
this.serializedSubscriber = serializedSubscriber;
this.timeoutStub = timeoutStub;
this.serial = serial;
this.other = other;
this.inner = inner;
}
@Override
public void onNext(T value) {
boolean onNextWins = false;
synchronized (gate) {
if (terminated == 0) {
ACTUAL_UPDATER.incrementAndGet(this);
onNextWins = true;
}
}
if (onNextWins) {
serializedSubscriber.onNext(value);
serial.set(timeoutStub.call(this, actual, value, inner));
}
}
@Override
public void onError(Throwable error) {
boolean onErrorWins = false;
synchronized (gate) {
if (TERMINATED_UPDATER.getAndSet(this, 1) == 0) {
onErrorWins = true;
}
}
if (onErrorWins) {
serial.unsubscribe();
serializedSubscriber.onError(error);
}
}
@Override
public void onCompleted() {
boolean onCompletedWins = false;
synchronized (gate) {
if (TERMINATED_UPDATER.getAndSet(this, 1) == 0) {
onCompletedWins = true;
}
}
if (onCompletedWins) {
serial.unsubscribe();
serializedSubscriber.onCompleted();
}
}
public void onTimeout(long seqId) {
long expected = seqId;
boolean timeoutWins = false;
synchronized (gate) {
if (expected == actual && TERMINATED_UPDATER.getAndSet(this, 1) == 0) {
timeoutWins = true;
}
}
if (timeoutWins) {
if (other == null) {
serializedSubscriber.onError(new TimeoutException());
} else {
other.unsafeSubscribe(serializedSubscriber);
serial.set(serializedSubscriber);
}
}
}
}
}