@Test
  public void serverNotEnoughWorkerThreadsCausesBindTimerToCloseChannel() throws Exception {
    BlockThreadSmppServerHandler serverHandler0 = new BlockThreadSmppServerHandler();
    SmppServerConfiguration configuration = createSmppServerConfiguration();
    // permit up to 0.5 seconds to bind
    configuration.setBindTimeout(500);
    DefaultSmppServer server0 = new DefaultSmppServer(configuration, serverHandler0);
    server0.start();

    try {
      // there is an issue without telling the server how many worker threads
      // to create beforehand with starvation only Runtime.getRuntime().availableProcessors()
      // worker threads are created by default!!! (yikes)
      int workersToStarveWith = Runtime.getRuntime().availableProcessors();

      // initiate bind requests on all sessions we care about -- this should
      // technicaly "starve" the server of worker threads since they'll all
      // be blocked in a Thread.sleep
      for (int i = 0; i < workersToStarveWith; i++) {
        DefaultSmppClient client0 = new DefaultSmppClient();
        SmppSessionConfiguration sessionConfig0 = createDefaultConfiguration();
        sessionConfig0.setName("WorkerTest.Session." + i);
        // don't use default method of binding, connect the socket first
        DefaultSmppSession session0 =
            client0.doOpen(sessionConfig0, new DefaultSmppSessionHandler());
        // try to bind and execute a bind request and wait for a bind response
        BaseBind bindRequest = client0.createBindRequest(sessionConfig0);
        try {
          // just send the request without caring if it succeeds
          session0.sendRequestPdu(bindRequest, 2000, false);
        } catch (SmppChannelException e) {
          System.out.println(e.getMessage());
        }
      }

      // now try to bind normally -- since all previous workers are "starved"
      // this should fail to bind and the socket closed by the "BindTimer"
      DefaultSmppClient client0 = new DefaultSmppClient();
      SmppSessionConfiguration sessionConfig0 = createDefaultConfiguration();
      sessionConfig0.setName("WorkerTestChannelClosed.Session");
      sessionConfig0.setBindTimeout(750);

      try {
        client0.bind(sessionConfig0);
        Assert.fail();
      } catch (SmppTimeoutException e) {
        // the BindTimer should end up closing the connection since the
        // worker thread were "starved"
        logger.debug("Correctly received SmppChannelException during bind");
      }

    } finally {
      Thread.sleep(10500);
      Assert.assertEquals(0, server0.getChannels().size());
      Assert.assertEquals(3, server0.getCounters().getBindTimeouts());
      server0.destroy();
    }
  }
 /**
  * Creates a new default SmppServer. Window monitoring and automatic expiration of requests will
  * be disabled with no monitorExecutors. A "cachedDaemonThreadPool" will be used for IO worker
  * threads.
  *
  * @param configuration The server configuration to create this server with
  * @param serverHandler The handler implementation for handling bind requests and
  *     creating/destroying sessions.
  */
 public DefaultSmppServer(SmppServerConfiguration configuration, SmppServerHandler serverHandler) {
   this(
       configuration,
       serverHandler,
       null,
       configuration.isNonBlockingSocketsEnabled()
           ? new NioEventLoopGroup()
           : new OioEventLoopGroup(),
       configuration.isNonBlockingSocketsEnabled()
           ? new NioEventLoopGroup()
           : new OioEventLoopGroup());
 }
  /**
   * Creates a new default SmppServer.
   *
   * @param configuration The server configuration to create this server with
   * @param serverHandler The handler implementation for handling bind requests and
   *     creating/destroying sessions.
   * @param monitorExecutor The scheduled executor that all sessions will share to monitor
   *     themselves and expire requests. If null monitoring will be disabled.
   * @param bossGroup Specify the EventLoopGroup to accept new connections and handle accepted
   *     connections. The {@link EventLoopGroup} is used to handle all the events and IO for {@link
   *     SocketChannel}.
   * @param workerGroup The {@link EventLoopGroup} is used to handle all the events and IO for
   *     {@link Channel}.
   */
  public DefaultSmppServer(
      final SmppServerConfiguration configuration,
      SmppServerHandler serverHandler,
      ScheduledExecutorService monitorExecutor,
      EventLoopGroup bossGroup,
      EventLoopGroup workerGroup) {
    this.configuration = configuration;
    // the same group we'll put every server channel
    this.channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    this.serverHandler = serverHandler;

    // tie the server bootstrap to this server socket channel factory
    this.serverBootstrap = new ServerBootstrap();

    // a factory for creating channels (connections)
    if (configuration.isNonBlockingSocketsEnabled()) {
      this.serverBootstrap.channel(NioServerSocketChannel.class);
    } else {
      this.serverBootstrap.channel(OioServerSocketChannel.class);
    }

    this.bossGroup = bossGroup;
    this.workerGroup = workerGroup;
    this.serverBootstrap.group(this.bossGroup, this.workerGroup);

    // set options for the server socket that are useful
    this.serverBootstrap.option(ChannelOption.SO_REUSEADDR, configuration.isReuseAddress());

    // we use the same default pipeline for all new channels - no need for a factory
    this.serverConnector = new SmppServerConnector(channels, this);

    this.serverBootstrap.childHandler(
        new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
                .addLast(SmppChannelConstants.PIPELINE_SERVER_CONNECTOR_NAME, serverConnector);
          }
        });

    // a shared timer used to make sure new channels are bound within X milliseconds
    this.bindTimer = new Timer(configuration.getName() + "-BindTimer0", true);
    // NOTE: this would permit us to customize the "transcoding" context for a server if needed
    this.transcoder = new DefaultPduTranscoder(new DefaultPduTranscoderContext());
    this.sessionIdSequence = new AtomicLong(0);
    this.monitorExecutor = monitorExecutor;
    this.counters = new DefaultSmppServerCounters();
    if (configuration.isJmxEnabled()) {
      registerMBean();
    }
  }
  protected void destroySession(Long sessionId, DefaultSmppSession session) {
    // session destroyed, now pass it upstream
    counters.incrementSessionDestroyedAndGet();
    decrementSessionSizeCounters(session);
    serverHandler.sessionDestroyed(sessionId, session);

    // unregister this session as an mbean
    if (configuration.isJmxEnabled()) {
      session.unregisterMBean(
          configuration.getJmxDomain()
              + ":type="
              + configuration.getName()
              + "Sessions,name="
              + sessionId);
    }
  }
 private void registerMBean() {
   if (configuration == null) {
     return;
   }
   if (configuration.isJmxEnabled()) {
     // register the this queue manager as an mbean
     try {
       ObjectName name =
           new ObjectName(configuration.getJmxDomain() + ":name=" + configuration.getName());
       ManagementFactory.getPlatformMBeanServer().registerMBean(this, name);
     } catch (Exception e) {
       // log the error, but don't throw an exception for this datasource
       logger.error("Unable to register DefaultSmppServerMXBean [{}]", configuration.getName(), e);
     }
   }
 }
 @Override
 public void stop() {
   if (this.channels.size() > 0) {
     logger.info(
         "{} currently has [{}] open child channel(s) that will be closed as part of stop()",
         configuration.getName(),
         this.channels.size());
   }
   // close all channels still open within this session "bootstrap"
   this.channels.close().awaitUninterruptibly();
   // clean up all external resources
   if (this.serverChannel != null) {
     this.serverChannel.close().awaitUninterruptibly();
     this.serverChannel = null;
   }
   logger.info("{} stopped on SMPP port [{}]", configuration.getName(), configuration.getPort());
 }
  protected BaseBindResp createBindResponse(BaseBind bindRequest, int statusCode) {
    BaseBindResp bindResponse = (BaseBindResp) bindRequest.createResponse();
    bindResponse.setCommandStatus(statusCode);
    bindResponse.setSystemId(configuration.getSystemId());

    // if the server supports an SMPP server version >= 3.4 AND the bind request
    // included an interface version >= 3.4, include an optional parameter with configured
    // sc_interface_version TLV
    if (configuration.getInterfaceVersion() >= SmppConstants.VERSION_3_4
        && bindRequest.getInterfaceVersion() >= SmppConstants.VERSION_3_4) {
      Tlv scInterfaceVersion =
          new Tlv(
              SmppConstants.TAG_SC_INTERFACE_VERSION,
              new byte[] {configuration.getInterfaceVersion()});
      bindResponse.addOptionalParameter(scInterfaceVersion);
    }

    return bindResponse;
  }
  @Override
  public void destroy() {
    this.bindTimer.cancel();
    stop();

    // Shut down all event loops to terminate all threads.
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();

    try {
      // Wait until all threads are terminated.
      bossGroup.terminationFuture().sync();
      workerGroup.terminationFuture().sync();
    } catch (InterruptedException e) {
      // is ok
    }

    this.serverBootstrap = null;
    unregisterMBean();
    logger.info("{} destroyed on SMPP port [{}]", configuration.getName(), configuration.getPort());
  }
  @Override
  public void start() throws SmppChannelException {
    if (isDestroyed()) {
      throw new SmppChannelException("Unable to start: server is destroyed");
    }
    try {
      ChannelFuture f = this.serverBootstrap.bind(new InetSocketAddress(configuration.getPort()));

      // wait until the connection is made successfully
      boolean timeout = !f.await(configuration.getBindTimeout());

      if (timeout || !f.isSuccess())
        throw new SmppChannelException(
            "Can't bind to port "
                + configuration.getPort()
                + " after "
                + configuration.getBindTimeout()
                + " milliseconds");

      logger.info("{} started on SMPP port [{}]", configuration.getName(), configuration.getPort());
      serverChannel = f.channel();
    } catch (ChannelException e) {
      throw new SmppChannelException(e.getMessage(), e);
    } catch (InterruptedException e) {
      throw new SmppChannelException(e.getMessage(), e);
    }
  }
 public void setConfiguration() {
   this.configuration = new SmppServerConfiguration();
   configuration.setPort(PORT);
   configuration.setMaxConnectionSize(10);
   configuration.setNonBlockingSocketsEnabled(true);
   configuration.setDefaultRequestExpiryTimeout(30000);
   configuration.setDefaultWindowMonitorInterval(15000);
   configuration.setDefaultWindowSize(5);
   configuration.setDefaultWindowWaitTimeout(configuration.getDefaultRequestExpiryTimeout());
   configuration.setDefaultSessionCountersEnabled(true);
   configuration.setJmxEnabled(true);
   configuration.setReuseAddress(true);
 }
  protected void createSession(
      Long sessionId,
      Channel channel,
      SmppSessionConfiguration config,
      BaseBindResp preparedBindResponse)
      throws SmppProcessingException {
    // NOTE: exactly one PDU (bind request) was read from the channel, we
    // now need to delegate permitting this bind request by calling a method
    // further upstream.  Only after the server-side is completely ready to
    // start processing requests from this session, do we want to actually
    // return the bind response and start reading further requests -- we'll
    // initially block reading from the channel first -- this will be turned
    // back on via the "serverReady()" method call on the session object

    // make sure the channel is not being read/processed (until we flag we're ready later on)
    channel.config().setAutoRead(false);

    // auto negotiate the interface version in use based on the requested interface version
    byte interfaceVersion = this.autoNegotiateInterfaceVersion(config.getInterfaceVersion());

    // create a new server session associated with this server
    DefaultSmppSession session =
        new DefaultSmppSession(
            SmppSession.Type.SERVER,
            config,
            channel,
            this,
            sessionId,
            preparedBindResponse,
            interfaceVersion,
            monitorExecutor);

    // replace name of thread used for renaming
    SmppSessionThreadRenamer threadRenamer =
        (SmppSessionThreadRenamer)
            channel.pipeline().get(SmppChannelConstants.PIPELINE_SESSION_THREAD_RENAMER_NAME);
    threadRenamer.setThreadName(config.getName());

    // add a logging handler after the thread renamer
    SmppSessionLogger loggingHandler =
        new SmppSessionLogger(
            DefaultSmppSession.class.getCanonicalName(), config.getLoggingOptions());
    channel
        .pipeline()
        .addAfter(
            SmppChannelConstants.PIPELINE_SESSION_THREAD_RENAMER_NAME,
            SmppChannelConstants.PIPELINE_SESSION_LOGGER_NAME,
            loggingHandler);

    // decoder in pipeline is ok (keep it)

    // create a new wrapper around a session to pass the pdu up the chain
    channel.pipeline().remove(SmppChannelConstants.PIPELINE_SESSION_WRAPPER_NAME);
    channel
        .pipeline()
        .addLast(
            SmppChannelConstants.PIPELINE_SESSION_WRAPPER_NAME, new SmppSessionWrapper(session));

    // check if the # of channels exceeds maxConnections
    if (this.channels.size() > this.configuration.getMaxConnectionSize()) {
      logger.warn(
          "The current connection size [{}] exceeds the configured max connection size [{}]",
          this.channels.size(),
          this.configuration.getMaxConnectionSize());
    }

    // session created, now pass it upstream
    counters.incrementSessionCreatedAndGet();
    incrementSessionSizeCounters(session);
    this.serverHandler.sessionCreated(sessionId, session, preparedBindResponse);

    // register this session as an mbean
    if (configuration.isJmxEnabled()) {
      session.registerMBean(
          configuration.getJmxDomain()
              + ":type="
              + configuration.getName()
              + "Sessions,name="
              + sessionId);
    }
  }
 public SmppServerConfiguration createSmppServerConfiguration() {
   SmppServerConfiguration configuration = new SmppServerConfiguration();
   configuration.setPort(PORT);
   configuration.setSystemId("cloudhopper");
   return configuration;
 }