/*
* 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 io.reactivex.netty.client.pool;
import io.netty.channel.EventLoop;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.FastThreadLocal;
import io.reactivex.netty.client.ClientConnectionToChannelBridge;
import io.reactivex.netty.threads.PreferCurrentEventLoopGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Actions;
import rx.functions.Func0;
import java.util.ArrayList;
/**
* An {@link IdleConnectionsHolder} implementation that can identify if the calling thread is an {@link EventLoop} in
* the provided {@link PreferCurrentEventLoopGroup} and prefers a connection registered with the calling
* {@link EventLoop}.
*
* If the calling thread is not an {@link EventLoop} in the provided {@link PreferCurrentEventLoopGroup} then
* {@link #poll()} and {@link #peek()} will iterate over connections from all {@link EventLoop}s however
* {@link #add(PooledConnection)} will attempt to find the {@link EventLoop} of the added {@link PooledConnection}. If
* the {@link EventLoop} of the connection does not belong to the provided {@link PreferCurrentEventLoopGroup} then the
* connection will be discarded.
*
* @param <W> Type of object that is written to the client using this holder.
* @param <R> Type of object that is read from the the client using this holder.
*/
public class PreferCurrentEventLoopHolder<W, R> extends IdleConnectionsHolder<W, R> {
private static final Logger logger = LoggerFactory.getLogger(PreferCurrentEventLoopHolder.class);
private final FastThreadLocal<IdleConnectionsHolder<W, R>> perElHolder = new FastThreadLocal<>();
private final ArrayList<IdleConnectionsHolder<W, R>> allElHolders;
private final Observable<PooledConnection<R, W>> pollObservable;
private final Observable<PooledConnection<R, W>> peekObservable;
PreferCurrentEventLoopHolder(PreferCurrentEventLoopGroup eventLoopGroup) {
this(eventLoopGroup, new FIFOIdleConnectionsHolderFactory<W, R>());
}
PreferCurrentEventLoopHolder(PreferCurrentEventLoopGroup eventLoopGroup,
final IdleConnectionsHolderFactory<W, R> holderFactory) {
final ArrayList<IdleConnectionsHolder<W, R>> _allElHolders = new ArrayList<>();
allElHolders = _allElHolders;
for (final EventExecutor child : eventLoopGroup) {
final IdleConnectionsHolder<W, R> newHolder = holderFactory.call();
allElHolders.add(newHolder);
child.submit(new Runnable() {
@Override
public void run() {
perElHolder.set(newHolder);
}
});
}
Observable<PooledConnection<R, W>> pollOverAllHolders = Observable.empty();
Observable<PooledConnection<R, W>> peekOverAllHolders = Observable.empty();
for (IdleConnectionsHolder<W, R> anElHolder : allElHolders) {
pollOverAllHolders = pollOverAllHolders.concatWith(anElHolder.poll());
peekOverAllHolders = peekOverAllHolders.concatWith(anElHolder.peek());
}
pollObservable = pollOverAllHolders;
peekObservable = peekOverAllHolders;
}
@Override
public Observable<PooledConnection<R, W>> poll() {
return pollObservable;
}
@Override
public Observable<PooledConnection<R, W>> pollThisEventLoopConnections() {
return Observable.create(new OnSubscribe<PooledConnection<R, W>>() {
@Override
public void call(Subscriber<? super PooledConnection<R, W>> subscriber) {
final IdleConnectionsHolder<W, R> holderForThisEL = perElHolder.get();
if (null == holderForThisEL) {
/*Caller is not an eventloop*/
PreferCurrentEventLoopHolder.super.pollThisEventLoopConnections().unsafeSubscribe(subscriber);
} else {
holderForThisEL.poll().unsafeSubscribe(subscriber);
}
}
});
}
@Override
public Observable<PooledConnection<R, W>> peek() {
return peekObservable;
}
@Override
public void add(final PooledConnection<R, W> toAdd) {
final IdleConnectionsHolder<W, R> holderForThisEL = perElHolder.get();
if (null != holderForThisEL) {
holderForThisEL.add(toAdd);
} else {
/*
* This should not happen as the code generally adds the connection from within an eventloop.
* By executing the add on the eventloop, the owner eventloop is correctly discovered for this eventloop.
*/
toAdd.unsafeNettyChannel().eventLoop().execute(new Runnable() {
@Override
public void run() {
IdleConnectionsHolder<W, R> holderForThisEl = perElHolder.get();
if (null == holderForThisEl) {
logger.error("Unrecognized eventloop: " + Thread.currentThread().getName() +
". Returned connection can not be added to the pool. Closing the connection.");
toAdd.unsafeNettyChannel().attr(ClientConnectionToChannelBridge.DISCARD_CONNECTION).set(true);
toAdd.close().subscribe(Actions.empty(), new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
logger.error("Failed to discard connection.", throwable);
}
});
} else {
holderForThisEl.add(toAdd);
}
}
});
}
}
@Override
public boolean remove(PooledConnection<R, W> toRemove) {
for (IdleConnectionsHolder<W, R> anElHolder : allElHolders) {
if (anElHolder.remove(toRemove)) {
return true;
}
}
return false;
}
public interface IdleConnectionsHolderFactory<W, R> extends Func0<IdleConnectionsHolder<W, R>> {
}
private static class FIFOIdleConnectionsHolderFactory<W, R> implements IdleConnectionsHolderFactory<W, R> {
@Override
public IdleConnectionsHolder<W, R> call() {
return new FIFOIdleConnectionsHolder<>();
}
}
}