org.r358.poolnetty.test.funcobs.LifecycleTest.java Source code

Java tutorial

Introduction

Here is the source code for org.r358.poolnetty.test.funcobs.LifecycleTest.java

Source

/*
 * Copyright (c) 2014 R358 https://github.com/R358
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.r358.poolnetty.test.funcobs;

import org.r358.poolnetty.common.*;
import org.r358.poolnetty.pool.NettyConnectionPool;
import org.r358.poolnetty.pool.NettyConnectionPoolBuilder;
import org.r358.poolnetty.pool.reaper.FullPassSimpleLeaseReaper;
import org.r358.poolnetty.test.simpleserver.SimpleInboundHandler;
import org.r358.poolnetty.test.simpleserver.SimpleOutboundHandler;
import org.r358.poolnetty.test.simpleserver.SimpleServer;
import org.r358.poolnetty.test.simpleserver.SimpleServerListener;
import org.r358.poolnetty.test.simpleserver.util.TestPoolProviderListener;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 *
 */
@RunWith(JUnit4.class)
public class LifecycleTest {

    /**
     * Test that during unforced shutdown it:
     * <ul>
     * <l1>Await the return of all leased connections.</l1>
     * <li>Blocks the leasing of connections.</li>
     * <p/>
     * <p>Also test that leases are not granted during this time.</p>
     * </ul>
     *
     * @throws Exception
     */
    @Test
    public void testUnforcedShutdownDuringLease() throws Exception {
        TestPoolProviderListener ppl = new TestPoolProviderListener();
        final ArrayList<Object> serverReceivedMessages = new ArrayList<>();
        String testMessage = "The cat sat on the mat.";

        //
        // The simple server side for testing.
        //

        SimpleServer simpleServer = new SimpleServer("127.0.0.1", 1887, 10, new SimpleServerListener() {

            @Override
            public void newConnection(ChannelHandlerContext ctx) {

            }

            @Override
            public void newValue(ChannelHandlerContext ctx, String val) {
                serverReceivedMessages.add(val);
                ctx.writeAndFlush(val);
            }
        });

        simpleServer.start();

        final CountDownLatch leaseExpiredHandlerCalled = new CountDownLatch(1);

        //
        // Build the pool.
        //

        NettyConnectionPoolBuilder ncb = new NettyConnectionPoolBuilder();
        ncb.withImmortalCount(2);
        ncb.withReaperIntervalMillis(1000); // Set short for testing..
        ncb.withLeaseExpiryHarvester(new FullPassSimpleLeaseReaper());
        ncb.withLeaseExpiredHandler(new LeaseExpiredHandler() {
            @Override
            public boolean closeExpiredLease(LeasedContext context, PoolProvider provider) {
                leaseExpiredHandlerCalled.countDown();
                return true; // Cause lease to be expired.
            }
        });

        final EventLoopGroup elg = new NioEventLoopGroup();

        //
        // Create the boot strap.
        //
        ncb.withBootstrapProvider(new BootstrapProvider() {
            @Override
            public Bootstrap createBootstrap(PoolProvider poolProvider) {
                Bootstrap bs = new Bootstrap();
                bs.group(elg);
                bs.channel(NioSocketChannel.class);
                bs.option(ChannelOption.SO_KEEPALIVE, true);
                bs.option(ChannelOption.AUTO_READ, true);
                return bs;
            }
        });

        //
        // Sets up the connection info and the channel initializer.
        //
        ncb.withConnectionInfoProvider(new ConnectionInfoProvider() {
            @Override
            public ConnectionInfo connectionInfo(PoolProvider poolProvider) {

                return new ConnectionInfo(new InetSocketAddress("127.0.0.1", 1887), null, new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline().addLast("decode", new SimpleInboundHandler(10));
                        ch.pipeline().addLast("encode", new SimpleOutboundHandler(10));
                    }
                });

            }
        });

        //
        // Make the pool add listener and start.
        //
        NettyConnectionPool ncp = ncb.build();
        ncp.addListener(ppl);

        ncp.start(0, TimeUnit.SECONDS);

        LeasedChannel firstLease = ncp.lease(5, TimeUnit.SECONDS, "aardvarks");

        ncp.stop(false);

        //
        // Should not stop because channel is being leased.
        //
        TestCase.assertFalse(ppl.getStoppedLatch().await(1, TimeUnit.SECONDS));

        //
        // Should not grant lease as shutdown is pending.
        //
        try {
            ncp.lease(3, TimeUnit.SECONDS, "ground pigs");
            TestCase.fail();

        } catch (Exception ex) {

        }

        Future<LeasedChannel> future = ncp.leaseAsync(1, TimeUnit.SECONDS, "Erdferkel");
        try {
            future.get(1, TimeUnit.SECONDS);
            TestCase.fail(); // Should fail.
        } catch (Exception ex) {

        }

        ncp.leaseAsync(1, TimeUnit.SECONDS, "Erdferkel", new LeaseListener() {
            @Override
            public void leaseRequest(boolean success, LeasedChannel channel, Throwable th) {
                TestCase.assertFalse(success);
                TestCase.assertTrue(th instanceof IllegalArgumentException);
            }
        });

        firstLease.yield();

        TestCase.assertTrue(ppl.getStoppedLatch().await(5, TimeUnit.SECONDS));

        simpleServer.stop();

    }

    @Test
    public void testLeaseCancellationViaFuture() throws Exception {
        TestPoolProviderListener ppl = new TestPoolProviderListener();
        final ArrayList<Object> serverReceivedMessages = new ArrayList<>();
        String testMessage = "The cat sat on the mat.";

        //
        // The simple server side for testing.
        //

        SimpleServer simpleServer = new SimpleServer("127.0.0.1", 1887, 10, new SimpleServerListener() {

            @Override
            public void newConnection(ChannelHandlerContext ctx) {

            }

            @Override
            public void newValue(ChannelHandlerContext ctx, String val) {
                serverReceivedMessages.add(val);
                ctx.writeAndFlush(val);
            }
        });

        simpleServer.start();

        final CountDownLatch leaseExpiredHandlerCalled = new CountDownLatch(1);

        //
        // Build the pool.
        //

        NettyConnectionPoolBuilder ncb = new NettyConnectionPoolBuilder();
        ncb.withImmortalCount(1);
        ncb.withMaxEphemeralCount(0);
        ncb.withReaperIntervalMillis(1000); // Set short for testing..
        ncb.withLeaseExpiryHarvester(new FullPassSimpleLeaseReaper());
        ncb.withLeaseExpiredHandler(new LeaseExpiredHandler() {
            @Override
            public boolean closeExpiredLease(LeasedContext context, PoolProvider provider) {
                leaseExpiredHandlerCalled.countDown();
                return true; // Cause lease to be expired.
            }
        });

        final EventLoopGroup elg = new NioEventLoopGroup();

        //
        // Create the boot strap.
        //
        ncb.withBootstrapProvider(new BootstrapProvider() {
            @Override
            public Bootstrap createBootstrap(PoolProvider poolProvider) {
                Bootstrap bs = new Bootstrap();
                bs.group(elg);
                bs.channel(NioSocketChannel.class);
                bs.option(ChannelOption.SO_KEEPALIVE, true);
                bs.option(ChannelOption.AUTO_READ, true);
                return bs;
            }
        });

        //
        // Sets up the connection info and the channel initializer.
        //
        ncb.withConnectionInfoProvider(new ConnectionInfoProvider() {
            @Override
            public ConnectionInfo connectionInfo(PoolProvider poolProvider) {

                return new ConnectionInfo(new InetSocketAddress("127.0.0.1", 1887), null, new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline().addLast("decode", new SimpleInboundHandler(10));
                        ch.pipeline().addLast("encode", new SimpleOutboundHandler(10));
                    }
                });

            }
        });

        //
        // Make the pool add listener and start.
        //
        NettyConnectionPool ncp = ncb.build();
        ncp.addListener(ppl);

        ncp.start(0, TimeUnit.SECONDS);

        //
        // Get the first lease.
        //
        LeasedChannel firstLease = ncp.lease(5, TimeUnit.SECONDS, "aardvarks");

        final AtomicBoolean failedInListener = new AtomicBoolean(false);

        Future<LeasedChannel> secondLease = ncp.leaseAsync(10, TimeUnit.SECONDS, "Erdferkel", new LeaseListener() {
            @Override
            public void leaseRequest(boolean success, LeasedChannel channel, Throwable th) {
                failedInListener.set(true);
            }
        });

        try {
            secondLease.get(1, TimeUnit.SECONDS);
            TestCase.fail();
        } catch (Exception ex) {
            TestCase.assertEquals(TimeoutException.class, ex.getClass());
        }

        secondLease.cancel(false); // The flag has no effect.

        firstLease.yield();

        //
        // Lease cancellation is asynchronous to the pool, but the detection of a canceled lease is done
        // 1. Before the lease logic executes on the lease request.
        // 2. After the lease logic executes, which will then cause an immediate yield to execute.
        //
        // However if the lease is pending it will be sitting in a holding queue and will be removed from there.
        //
        // The future listener event is driven from the future so calling cancel() on that will fire
        // the future listener on the thread that called cancel but the pool may fire leaseCanceled
        // potentially at some point in between because it is driven from the pools thread.
        //
        // Between those two sources 0f information the order of notification is indeterminate.

        //
        // The call to cancel() may also through an illegal state exception if the granting of the lease is in
        // progress at that moment.
        //
        // For testing sake we give it a moment to settle.

        Thread.sleep(500); // Excessive.

        TestCase.assertTrue(secondLease.isCancelled());
        TestCase.assertTrue(failedInListener.get());
        TestCase.assertTrue(ppl.getLeaseCanceled().await(5, TimeUnit.SECONDS));

        secondLease = ncp.leaseAsync(10, TimeUnit.SECONDS, "Foo");

        try {
            LeasedChannel lc = secondLease.get(1, TimeUnit.SECONDS);
            TestCase.assertEquals("Foo", lc.getUserObject());
        } catch (Exception ex) {
            TestCase.fail();
        }

        ncp.stop(true);
        TestCase.assertTrue(ppl.getStoppedLatch().await(5, TimeUnit.SECONDS));

        simpleServer.stop();
    }

}