/*
* Copyright (C) 2012-2016 Facebook, 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 com.facebook.nifty.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.thrift.TException;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.facebook.nifty.processor.NiftyProcessor;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
public class TestConnectionContext extends AbstractLiveTest
{
@Test
public void testContextNormal() throws IOException, TException, InterruptedException
{
final SynchronousQueue<RequestContext> requestContextQueue = new SynchronousQueue<>();
final SynchronousQueue<SettableFuture<Boolean>> sendResponseQueue = new SynchronousQueue<>();
NiftyProcessor processor = mockProcessor(null, null, requestContextQueue, sendResponseQueue);
try (FakeServer server = listen(processor);
FakeClient client = connect(server)) {
// Issue a fake request and wait for it to arrive
client.sendRequest();
RequestContext requestContext = requestContextQueue.poll(30, TimeUnit.SECONDS);
Preconditions.checkNotNull(requestContext, "Either deadlock, or your computer is really slow");
ConnectionContext actualContext = requestContext.getConnectionContext();
SettableFuture<Boolean> sendResponse = sendResponseQueue.take();
// ConnectionContext should show the request from the client
Assert.assertNotNull(
actualContext.getRemoteAddress(),
"remote address non-null");
Assert.assertEquals(
((InetSocketAddress) actualContext.getRemoteAddress()).getPort(),
client.getClientPort(),
"context has correct port");
sendResponse.set(false);
}
}
@Test
public void testContextOnClosedConnection() throws IOException, TException, InterruptedException
{
// An ExecutorService which lets us delay calls from NiftyDispatcher to the processor
final SynchronousQueue<Semaphore> tasksWaitingToRun = new SynchronousQueue<>();
final ExecutorService threadpool = Executors.newCachedThreadPool();
Executor slowExecutor = new Executor() {
@Override
public void execute(final Runnable task) {
threadpool.execute(new Runnable() {
@Override
public void run() {
Semaphore allowMeToContinue = new Semaphore(0);
Uninterruptibles.putUninterruptibly(tasksWaitingToRun, allowMeToContinue);
allowMeToContinue.acquireUninterruptibly();
task.run();
}
});
}
};
final SynchronousQueue<RequestContext> requestContextQueue = new SynchronousQueue<>();
final SynchronousQueue<SettableFuture<Boolean>> sendResponseQueue = new SynchronousQueue<>();
NiftyProcessor processor = mockProcessor(null, null, requestContextQueue, sendResponseQueue);
ChannelGroup channels = new DefaultChannelGroup();
try (FakeServer server = listen(processor, slowExecutor, channels);
FakeClient client = connect(server)) {
// Send a request, and wait for NiftyDispatcher to put it in the queue
client.sendRequest();
Semaphore allowNiftyProcessorToRun = tasksWaitingToRun.poll(30, TimeUnit.SECONDS);
Preconditions.checkNotNull(allowNiftyProcessorToRun, "Either deadlock, or your computer is really slow");
// Close the channel. We do the close on the server side because that
// lets us control when all the close handlers are run (namely, on
// our thread) which in turn lets us wait until the channel has finished
// closing.
CountDownLatch latch = new CountDownLatch(2);
final AtomicReference<Thread> threadThatProcessedClose = new AtomicReference<>();
Channel channelToClient = Iterables.getOnlyElement(channels);
channelToClient.getCloseFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
threadThatProcessedClose.set(Thread.currentThread());
latch.countDown();
}
});
channelToClient.close();
final Thread[] expectedThread = new Thread[1];
channelToClient.getPipeline().execute(new Runnable() {
@Override
public void run() {
expectedThread[0] = Thread.currentThread();
latch.countDown();
}
});
latch.await();
// Check that we got a callback for close on the io thread.
Preconditions.checkState(threadThatProcessedClose.get() == expectedThread[0]);
// Now allow the NiftyProcessor to run, and wait for it to finish
allowNiftyProcessorToRun.release();
RequestContext requestContext = requestContextQueue.poll(30, TimeUnit.SECONDS);
Preconditions.checkNotNull(requestContext, "Either deadlock, or your computer is really slow");
ConnectionContext actualContext = requestContext.getConnectionContext();
SettableFuture<Boolean> sendResponse = sendResponseQueue.take();
// The connection context should still be correct
Assert.assertNotNull(
actualContext.getRemoteAddress(),
"remote address non-null");
Assert.assertEquals(
((InetSocketAddress) actualContext.getRemoteAddress()).getPort(),
client.getClientPort(),
"context has correct port");
sendResponse.set(false);
} finally {
threadpool.shutdown();
}
}
}