Package net.tomp2p.connection

Source Code of net.tomp2p.connection.ChannelCreator

/*
* Copyright 2013 Thomas Bocek
*
* 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 net.tomp2p.connection;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.rpc.RPC.Commands;
import net.tomp2p.utils.Pair;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Creates the channels. This class is created by {@link net.tomp2p.connection.Reservation.WaitReservationPermanent}
* and should never be called directly. With this class one can create TCP or
* UDP channels up to a certain extend. Thus it must be know beforehand how many
* connections will be created.
*
* @author Thomas Bocek
*/
public class ChannelCreator {
  private static final Logger LOG = LoggerFactory.getLogger(ChannelCreator.class);

  private final EventLoopGroup workerGroup;
  private final ChannelGroup recipients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

  private final int maxPermitsUDP;
  private final int maxPermitsTCP;

  private final Semaphore semaphoreUPD;
  private final Semaphore semaphoreTCP;

  // we should be fair, otherwise we see connection timeouts due to unfairness
  // if busy
  private final ReadWriteLock readWriteLockUDP = new ReentrantReadWriteLock(true);
  private final Lock readUDP = readWriteLockUDP.readLock();
  private final Lock writeUDP = readWriteLockUDP.writeLock();

  private final ReadWriteLock readWriteLockTCP = new ReentrantReadWriteLock(true);
  private final Lock readTCP = readWriteLockTCP.readLock();
  private final Lock writeTCP = readWriteLockTCP.writeLock();

  private final FutureDone<Void> futureChannelCreationDone;

  private final ChannelClientConfiguration channelClientConfiguration;
 
  private EventExecutorGroup handlerExecutor;

  private boolean shutdownUDP = false;
  private boolean shutdownTCP = false;

  /**
   * Package private constructor, since this is created by
   * {@link net.tomp2p.connection.Reservation.WaitReservationPermanent} and should never be called directly.
   *
   * @param workerGroup
   *            The worker group for netty that is shared between TCP and UDP.
   *            This worker group is not shutdown if this instance is shutdown
   * @param futureChannelCreationDone
   *            We need to set this from the outside as we want to attach
   *            listeners to it
   * @param maxPermitsUDP
   *            The number of max. parallel UDP connections.
   * @param maxPermitsTCP
   *            The number of max. parallel TCP connections.
   * @param channelClientConfiguration
   *            The configuration that contains the pipeline filter
   */
  ChannelCreator(final EventLoopGroup workerGroup, final FutureDone<Void> futureChannelCreationDone,
          final int maxPermitsUDP, final int maxPermitsTCP,
          final ChannelClientConfiguration channelClientConfiguration) {
    this.workerGroup = workerGroup;
    this.futureChannelCreationDone = futureChannelCreationDone;
    this.maxPermitsUDP = maxPermitsUDP;
    this.maxPermitsTCP = maxPermitsTCP;
    this.semaphoreUPD = new Semaphore(maxPermitsUDP);
    this.semaphoreTCP = new Semaphore(maxPermitsTCP);
    this.channelClientConfiguration = channelClientConfiguration;
  }

    /**
     * Creates a "channel" to the given address. This won't send any message
     * unlike TCP.
     *
     * @param broadcast
     *              Sets this channel to be able to broadcast
     * @param channelHandlers
     *              The handlers to set
     * @param futureResponse
     *              The futureResponse
     * @return The channel future object or null if we are shut down
     */
  public ChannelFuture createUDP(final boolean broadcast,
          final Map<String, Pair<EventExecutorGroup, ChannelHandler>> channelHandlers, FutureResponse futureResponse) {
    readUDP.lock();
    try {
      if (shutdownUDP) {
        return null;
      }
      if (!semaphoreUPD.tryAcquire()) {
        LOG.error("Tried to acquire more resources (UDP) than announced!");
        throw new RuntimeException("Tried to acquire more resources (UDP) than announced!");
      }
      final Bootstrap b = new Bootstrap();
      b.group(workerGroup);
      b.channel(NioDatagramChannel.class);
      b.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(ConnectionBean.UDP_LIMIT));
      if (broadcast) {
        b.option(ChannelOption.SO_BROADCAST, true);
      }
      Map<String, Pair<EventExecutorGroup, ChannelHandler>> channelHandlers2 = channelClientConfiguration.pipelineFilter().filter(channelHandlers, false, true);
      addHandlers(b, channelHandlers2);
      // Here we need to bind, as opposed to the TCP, were we connect if
      // we do a connect, we cannot receive
      // broadcast messages
      final ChannelFuture channelFuture = b.bind(new InetSocketAddress(channelClientConfiguration.senderUDP(), 0));

      recipients.add(channelFuture.channel());
      setupCloseListener(channelFuture, semaphoreUPD, futureResponse);
      return channelFuture;
        } finally {
      readUDP.unlock();
    }
  }

  /**
   * Creates a channel to the given address. This will setup the TCP
   * connection
   *
   * @param socketAddress
   *            The address to send future messages
   * @param connectionTimeoutMillis
   *            The timeout for establishing a TCP connection
   * @param channelHandlers
   *            The handlers to set
   * @param futureResponse
     *            The futureResponse
   * @return The channel future object or null if we are shut down.
   */
  public ChannelFuture createTCP(final SocketAddress socketAddress, final int connectionTimeoutMillis,
          final Map<String, Pair<EventExecutorGroup, ChannelHandler>> channelHandlers, final FutureResponse futureResponse) {
    readTCP.lock();
    try {
      if (shutdownTCP) {
        return null;
      }
      if (!semaphoreTCP.tryAcquire()) {
        LOG.error("Tried to acquire more resources (TCP) than announced!");
        throw new RuntimeException("Tried to acquire more resources (TCP) than announced!");
      }
      Bootstrap b = new Bootstrap();
      b.group(workerGroup);
      b.channel(NioSocketChannel.class);
      b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMillis);
      b.option(ChannelOption.TCP_NODELAY, true);
      b.option(ChannelOption.SO_LINGER, 0);
      b.option(ChannelOption.SO_REUSEADDR, true);
      Map<String, Pair<EventExecutorGroup, ChannelHandler>> channelHandlers2 = channelClientConfiguration.pipelineFilter().filter(channelHandlers, true, true);
      addHandlers(b, channelHandlers2);

      ChannelFuture channelFuture = b.connect(socketAddress, new InetSocketAddress(channelClientConfiguration.senderTCP(), 0));

      recipients.add(channelFuture.channel());
      setupCloseListener(channelFuture, semaphoreTCP, futureResponse);
      return channelFuture;
    } finally {
      readTCP.unlock();
    }
  }

  /**
   * Since we want to add multiple handlers, we need to do this with the
   * pipeline.
   *
   * @param bootstrap
   *            The bootstrap
   * @param channelHandlers
   *            The handlers to be added.
   */
  private void addHandlers(final Bootstrap bootstrap,
          final Map<String, Pair<EventExecutorGroup, ChannelHandler>> channelHandlers) {
        bootstrap.handler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(final Channel ch) throws Exception {
                for (Map.Entry<String, Pair<EventExecutorGroup, ChannelHandler>> entry : channelHandlers.entrySet()) {
                    if (entry.getKey().equals("handler")) {
                        handlerExecutor = entry.getValue().element0();
                    }
                    if (entry.getValue().element0() != null) {
                        ch.pipeline().addLast(entry.getValue().element0(), entry.getKey(), entry.getValue().element1());
                    } else {
                        ch.pipeline().addLast(entry.getKey(), entry.getValue().element1());
                    }
                }
            }
        });
  }

  /**
   * When a channel is closed, the semaphore is released an other channel can
   * be created. Also the lock for the channel creating is being released.
   * This means that the channelCreator can be shutdown.
   *
   * @param channelFuture
   *            The channel future
   * @param semaphore
     *            The semaphore to decrease
     *@param futureResponse
     *            The future response
     *           
   * @return The same future that was passed as an argument
   */
  private ChannelFuture setupCloseListener(final ChannelFuture channelFuture, final Semaphore semaphore, final FutureResponse futureResponse) {
    channelFuture.channel().closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {
      @Override
      public void operationComplete(final ChannelFuture future) throws Exception {
        // it is important that the release of the semaphore and the set
        // of the future happen sequentially. If this is run in this
        // thread it will be a netty thread, and this is not what the
        // user may have wanted. The future response should be executed
        // in the thread of the handler.
        Runnable runner = new Runnable() {
          @Override
          public void run() {
            semaphore.release();
           
            Message request = futureResponse.request();
            if(request.recipient().isSlow() && request.command() != Commands.PING.getNr() && request.command() != Commands.NEIGHBOR.getNr()) {
              // If the request goes to a slow peer, the channel can be closed until the response arrives
              LOG.debug("Ignoring channel close event because recipient is slow peer");
            } else {
              futureResponse.responseNow();
            }
          }
        };
        if (handlerExecutor == null) {
          runner.run();
        } else {
          handlerExecutor.submit(runner);
        }
      }
    });
    return channelFuture;
  }
 
  /**
   * Setup the close listener for a channel that was already created
   * @param channelFuture
     *               The channel future
   * @param futureResponse
     *               The future response
   * @return The same future that was passed as an argument
   */
  public ChannelFuture setupCloseListener(final ChannelFuture channelFuture, final FutureResponse futureResponse) {
    channelFuture.channel().closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {
      @Override
      public void operationComplete(final ChannelFuture future) throws Exception {
        futureResponse.responseNow();
      }
    });
    return channelFuture;
  }
 
  public boolean isShutdown() {
    return shutdownTCP || shutdownUDP;
  }

  /**
   * Shutdown this channel creator. This means that no TCP or UDP connection
   * can be established.
   *
   * @return The shutdown future.
   */
  public FutureDone<Void> shutdown() {
    // set shutdown flag for UDP and TCP, if we acquire a write lock, all
    // read locks are blocked as well
    writeUDP.lock();
    writeTCP.lock();
    try {
      if (shutdownTCP || shutdownUDP) {
        shutdownFuture().failed("already shutting down");
        return shutdownFuture();
      }
      shutdownUDP = true;
      shutdownTCP = true;
    } finally {
      writeTCP.unlock();
      writeUDP.unlock();
    }

    recipients.close().addListener(new GenericFutureListener<ChannelGroupFuture>() {
      @Override
      public void operationComplete(final ChannelGroupFuture future) throws Exception {
        //we can block here as we block in GlobalEventExecutor.INSTANCE
        semaphoreUPD.acquireUninterruptibly(maxPermitsUDP);
        semaphoreTCP.acquireUninterruptibly(maxPermitsTCP);
        shutdownFuture().done();
      }
    });
   
    return shutdownFuture();
  }

  /**
   * @return The shutdown future that is used when calling {@link #shutdown()}
   */
  public FutureDone<Void> shutdownFuture() {
    return futureChannelCreationDone;
  }
 
  public int availableUDPPermits() {
      return semaphoreUPD.availablePermits();
    }
 
  public int availableTCPPermits() {
      return semaphoreTCP.availablePermits();
    }
 
  @Override
  public String toString() {
      StringBuilder sb = new StringBuilder("sem-udp:");
      sb.append(semaphoreUPD.availablePermits());
      sb.append(",sem-tcp:");
      sb.append(semaphoreTCP.availablePermits());
      sb.append(",addrUDP:");
      sb.append(semaphoreUPD);
      return sb.toString();
  }

 
}
TOP

Related Classes of net.tomp2p.connection.ChannelCreator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.