package netflix.ocelli.rxnetty.internal; import io.reactivex.netty.client.ConnectionProvider; import io.reactivex.netty.protocol.tcp.client.events.TcpClientEventListener; import netflix.ocelli.Instance; import netflix.ocelli.rxnetty.FailureListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Observable.Operator; import rx.Scheduler; import rx.Subscriber; import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Actions; import rx.functions.Func1; import rx.schedulers.Schedulers; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; class HostCollector<W, R> implements Operator<List<HostConnectionProvider<W, R>>, Instance<ConnectionProvider<W, R>>> { private static final Logger logger = LoggerFactory.getLogger(HostCollector.class); protected final CopyOnWriteArrayList<HostConnectionProvider<W, R>> currentHosts = new CopyOnWriteArrayList<>(); private final Func1<FailureListener, ? extends TcpClientEventListener> eventListenerFactory; HostCollector(Func1<FailureListener, ? extends TcpClientEventListener> eventListenerFactory) { this.eventListenerFactory = eventListenerFactory; } @Override public Subscriber<? super Instance<ConnectionProvider<W, R>>> call(final Subscriber<? super List<HostConnectionProvider<W, R>>> o) { return new Subscriber<Instance<ConnectionProvider<W, R>>>() { @Override public void onCompleted() { o.onCompleted(); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onNext(Instance<ConnectionProvider<W, R>> i) { final ConnectionProvider<W, R> provider = i.getValue(); final TcpClientEventListener listener = eventListenerFactory.call(newFailureListener(provider, o)); final HostConnectionProvider<W, R> hcp = new HostConnectionProvider<>(i.getValue(), listener); addHost(hcp, o); bindToInstanceLifecycle(i, hcp, o); } }; } protected void removeHost(HostConnectionProvider<W, R> toRemove, Subscriber<? super List<HostConnectionProvider<W, R>>> hostListListener) { /*It's a copy-on-write list, so removal makes a copy with no interference to reads*/ currentHosts.remove(toRemove); hostListListener.onNext(currentHosts); } protected void addHost(HostConnectionProvider<W, R> toAdd, Subscriber<? super List<HostConnectionProvider<W, R>>> hostListListener) { /*It's a copy-on-write list, so addition makes a copy with no interference to reads*/ currentHosts.add(toAdd); hostListListener.onNext(currentHosts); } protected FailureListener newFailureListener(final ConnectionProvider<W, R> provider, final Subscriber<? super List<HostConnectionProvider<W, R>>> hostListListener) { return new FailureListener() { @Override public void remove() { HostConnectionProvider.removeFrom(currentHosts, provider); hostListListener.onNext(currentHosts); } @Override public void quarantine(long quarantineDuration, TimeUnit timeUnit) { quarantine(quarantineDuration, timeUnit, Schedulers.computation()); } @Override public void quarantine(long quarantineDuration, TimeUnit timeUnit, Scheduler timerScheduler) { final FailureListener fl = this; remove(); Observable.timer(quarantineDuration, timeUnit, timerScheduler) .subscribe(new Action1<Long>() { @Override public void call(Long aLong) { TcpClientEventListener listener = eventListenerFactory.call(fl); addHost(new HostConnectionProvider<W, R>(provider, listener), hostListListener); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { logger.error("Error while adding back a quarantine instance to the load balancer.", throwable); } }); } }; } protected void bindToInstanceLifecycle(Instance<ConnectionProvider<W, R>> i, final HostConnectionProvider<W, R> hcp, final Subscriber<? super List<HostConnectionProvider<W, R>>> o) { i.getLifecycle() .finallyDo(new Action0() { @Override public void call() { removeHost(hcp, o); } }) .subscribe(Actions.empty(), new Action1<Throwable>() { @Override public void call(Throwable throwable) { // Do nothing as finallyDo takes care of both complete and error. } }); } }