/*
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.core.publisher;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Consumer;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.Disposables.SequentialDisposable;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.scheduler.Scheduler;
/**
* @author Simon Baslé
* @author David Karnok
*/
//adapted from RxJava2Extensions: https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableRefCountTimeout.java
final class FluxRefCountGrace<T> extends Flux<T> implements Scannable, Fuseable {
final ConnectableFlux<T> source;
final int n;
final Duration gracePeriod;
final Scheduler scheduler;
RefConnection connection;
volatile Disposable timeoutTask;
static final AtomicReferenceFieldUpdater<FluxRefCountGrace, Disposable> TIMEOUT_TASK =
AtomicReferenceFieldUpdater.newUpdater(FluxRefCountGrace.class, Disposable.class, "timeoutTask");
FluxRefCountGrace(ConnectableFlux<T> source, int n, Duration gracePeriod, Scheduler scheduler) {
this.source = source;
this.n = n;
this.gracePeriod = gracePeriod;
this.scheduler = scheduler;
}
@Override
public int getPrefetch() {
return source.getPrefetch();
}
@Override
public Object scan(Attr key) {
switch (key){
case PREFETCH:
return getPrefetch();
case PARENT:
return source;
}
return null;
}
@Override
public void subscribe(Subscriber<? super T> s) {
for (; ; ) {
RefConnection conn;
boolean connect = false;
synchronized (this) {
conn = connection;
if (conn == null || conn.terminated) {
conn = new RefConnection(this);
connection = conn;
}
long c = conn.subscriberCount;
if (c == 0L && conn.timer != null) {
conn.timer.dispose();
}
conn.subscriberCount = c + 1;
if (!conn.connected && c + 1 == n) {
connect = true;
conn.connected = true;
}
}
source.subscribe(new RefCountSubscriber<>(s, this, conn));
if (connect) {
source.connect(conn);
}
break;
}
}
void cancel(RefConnection rc) {
SequentialDisposable sd;
synchronized (this) {
if (rc.terminated) {
return;
}
long c = rc.subscriberCount - 1;
rc.subscriberCount = c;
if (c != 0L || !rc.connected) {
return;
}
if (gracePeriod.isZero()) {
timeout(rc);
return;
}
sd = new SequentialDisposable();
rc.timer = sd;
}
sd.replace(scheduler.schedule(rc, gracePeriod.toMillis(), TimeUnit.MILLISECONDS));
}
void terminated(RefConnection rc) {
synchronized (this) {
if (!rc.terminated) {
rc.terminated = true;
connection = null;
}
}
}
void timeout(RefConnection rc) {
synchronized (this) {
if (rc.subscriberCount == 0 && rc == connection) {
Disposables.dispose(RefConnection.SOURCE_DISCONNECTOR, rc);
if (source instanceof Disposable) {
((Disposable) source).dispose();
}
connection = null;
}
}
}
static final class RefConnection implements Runnable, Consumer<Disposable> {
final FluxRefCountGrace<?> parent;
Disposable timer;
long subscriberCount;
boolean connected;
boolean terminated;
volatile Disposable sourceDisconnector;
static final AtomicReferenceFieldUpdater<RefConnection, Disposable> SOURCE_DISCONNECTOR =
AtomicReferenceFieldUpdater.newUpdater(RefConnection.class, Disposable.class, "sourceDisconnector");
RefConnection(FluxRefCountGrace<?> parent) {
this.parent = parent;
}
@Override
public void run() {
parent.timeout(this);
}
@Override
public void accept(Disposable t) {
Disposables.replace(SOURCE_DISCONNECTOR, this, t);
}
}
static final class RefCountSubscriber<T> implements Subscriber<T>, Subscription {
final Subscriber<? super T> actual;
final FluxRefCountGrace<T> parent;
final RefConnection connection;
Subscription upstream;
volatile int parentDone;
static final AtomicIntegerFieldUpdater<RefCountSubscriber> PARENT_DONE =
AtomicIntegerFieldUpdater.newUpdater(RefCountSubscriber.class, "parentDone");
RefCountSubscriber(Subscriber<? super T> actual, FluxRefCountGrace<T> parent,
RefConnection connection) {
this.actual = actual;
this.parent = parent;
this.connection = connection;
}
@Override
public void onNext(T t) {
actual.onNext(t);
}
@Override
public void onError(Throwable t) {
actual.onError(t);
if (PARENT_DONE.compareAndSet(this, 0, 1)) {
parent.terminated(connection);
}
}
@Override
public void onComplete() {
actual.onComplete();
if (PARENT_DONE.compareAndSet(this, 0, 1)) {
parent.terminated(connection);
}
}
@Override
public void request(long n) {
upstream.request(n);
}
@Override
public void cancel() {
upstream.cancel();
if (PARENT_DONE.compareAndSet(this, 0, 1)) {
parent.cancel(connection);
}
}
@Override
public void onSubscribe(Subscription s) {
if (Operators.validate(upstream, s)) {
this.upstream = s;
actual.onSubscribe(this);
}
}
}
}