/*
* Copyright 2016 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.embedded.EmbeddedChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.reactivex.netty.channel.Connection;
import io.reactivex.netty.channel.ConnectionImpl;
import io.reactivex.netty.client.pool.PooledConnection.Owner;
import io.reactivex.netty.client.pool.PreferCurrentEventLoopHolder.IdleConnectionsHolderFactory;
import io.reactivex.netty.events.EventAttributeKeys;
import io.reactivex.netty.events.EventPublisher;
import io.reactivex.netty.test.util.MockEventPublisher;
import io.reactivex.netty.threads.PreferCurrentEventLoopGroup;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import rx.Observable;
import rx.schedulers.Schedulers;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class PreferCurrentEventLoopHolderTest {
@Rule
public final PreferCurrentELHolderRule preferCurrentELHolderRule = new PreferCurrentELHolderRule();
@Test(timeout = 60000)
public void testPollOutOfEventloop() throws Exception {
PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
PooledConnection<String, String> connection = preferCurrentELHolderRule.holder.poll()
.defaultIfEmpty(null).toBlocking()
.single();
assertThat("Unexpected connection.", connection, is(connection1));
}
@Test(timeout = 60000)
public void testPollInEventloop() throws Exception {
final PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
preferCurrentELHolderRule.runFromChannelEventLoop(new Callable<Void>() {
@Override
public Void call() throws Exception {
PooledConnection<String, String> connection = preferCurrentELHolderRule.holder.poll()
.defaultIfEmpty(null)
.toBlocking()
.single();
assertThat("Connection available in the eventloop.", connection, is(connection1));
return null;
}
}).get(1, TimeUnit.MINUTES);
}
@Test
public void testPollThisEventLoopConnectionsInEl() throws Exception {
final PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
preferCurrentELHolderRule.runFromChannelEventLoop(new Callable<Void>() {
@Override
public Void call() throws Exception {
PooledConnection<String, String> connection =
preferCurrentELHolderRule.holder.pollThisEventLoopConnections()
.defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection available in the eventloop.", connection, is(connection1));
return null;
}
}).get(1, TimeUnit.MINUTES);
}
@Test(timeout = 60000)
public void testPollRemovesItem() throws Exception {
PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
PooledConnection<String, String> connection = preferCurrentELHolderRule.holder.poll()
.defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection not available with poll.", connection, is(connection1));
connection = preferCurrentELHolderRule.holder.poll()
.defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection available after poll.", connection, is(nullValue()));
}
@Test(timeout = 60000)
public void testPeek() throws Exception {
PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
PooledConnection<String, String> connection = preferCurrentELHolderRule.holder.peek()
.defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection not available with peek.", connection, is(connection1));
connection = preferCurrentELHolderRule.holder.peek().defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection not available after peek.", connection, not(nullValue()));
assertThat("Unexpected connection on peek.", connection, is(connection1));
}
@Test(timeout = 60000)
public void testRemove() throws Exception {
PooledConnection<String, String> connection1 = preferCurrentELHolderRule.addConnection();
PooledConnection<String, String> connection = preferCurrentELHolderRule.holder.peek()
.defaultIfEmpty(null)
.toBlocking().single();
assertThat("Connection not available with peek.", connection, is(connection1));
preferCurrentELHolderRule.holder.remove(connection1);
connection = preferCurrentELHolderRule.holder.peek().defaultIfEmpty(null).toBlocking().single();
assertThat("Connection not removed.", connection, is(nullValue()));
}
public static class PreferCurrentELHolderRule extends ExternalResource implements Owner {
private PreferCurrentEventLoopHolder<String, String> holder;
private EventPublisher eventPublisher;
private PoolConfig<String, String> poolConfig;
private ConcurrentLinkedQueue<PooledConnection<?, ?>> discarded;
private ConcurrentLinkedQueue<PooledConnection<?, ?>> released;
private ExecutorService eventLoopThread;
private EmbeddedChannel channel;
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
eventLoopThread = Executors.newFixedThreadPool(1);
channel = new EmbeddedChannel(new LoggingHandler());
PreferCurrentEventLoopGroup eventLoopGroup = new PreferCurrentEventLoopGroup(channel.eventLoop());
eventPublisher = MockEventPublisher.disabled();
channel.attr(EventAttributeKeys.EVENT_PUBLISHER).set(eventPublisher);
holder = new PreferCurrentEventLoopHolder<>(eventLoopGroup,
new IdleConnectionsHolderFactoryImpl());
poolConfig = new PoolConfig<>();
poolConfig.maxIdleTimeoutMillis(TimeUnit.DAYS.toMillis(1))
.idleConnectionsCleanupTimer(Observable.timer(1, TimeUnit.DAYS, Schedulers.test()))
.limitDeterminationStrategy(new MaxConnectionsBasedStrategy(1))
.idleConnectionsHolder(holder);
discarded = new ConcurrentLinkedQueue<>();
released = new ConcurrentLinkedQueue<>();
base.evaluate();
}
};
}
public PooledConnection<String, String> addConnection() throws Exception {
Connection<String, String> connection = ConnectionImpl.fromChannel(channel);
PooledConnection<String, String> pooledConnection = PooledConnection.create(this,
poolConfig.getMaxIdleTimeMillis(),
connection);
holder.add(pooledConnection);
runAllPendingTasksOnChannel();
return pooledConnection;
}
@Override
public Observable<Void> release(PooledConnection<?, ?> connection) {
released.add(connection);
return Observable.empty();
}
@Override
public Observable<Void> discard(PooledConnection<?, ?> connection) {
discarded.add(connection);
return Observable.empty();
}
public Future<Void> runFromChannelEventLoop(Callable<Void> runnable) throws Exception {
Future<Void> toReturn = channel.eventLoop().submit(runnable);
runAllPendingTasksOnChannel();
return toReturn;
}
public void runAllPendingTasksOnChannel() throws Exception {
eventLoopThread.submit(new Runnable() {
@Override
public void run() {
channel.runPendingTasks();
}
}).get(1, TimeUnit.MINUTES);
}
private static class IdleConnectionsHolderFactoryImpl implements IdleConnectionsHolderFactory<String, String> {
@Override
public IdleConnectionsHolder<String, String> call() {
return new FIFOIdleConnectionsHolder<>();
}
}
}
}