/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */
#ifndef _THRIFT_TEST_SERVERTHREAD_TCC_
#define _THRIFT_TEST_SERVERTHREAD_TCC_ 1

#include "ServerThread.h"

#include <thrift/concurrency/ThreadFactory.h>
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TServerSocket.h>

namespace apache {
namespace thrift {
namespace test {

void ServerThread::start() {
  assert(!running_);
  running_ = true;

  helper_.reset(new Helper(this));

  // Start the other thread
  concurrency::ThreadFactory threadFactory;
  threadFactory.setDetached(false);
  thread_ = threadFactory.newThread(helper_);

  thread_->start();

  // Wait on the other thread to tell us that it has successfully
  // bound to the port and started listening (or until an error occurs).
  concurrency::Synchronized s(serverMonitor_);
  while (!serving_ && !error_) {
    serverMonitor_.waitForever();
  }

  if (error_) {
    throw transport::TTransportException(transport::TTransportException::NOT_OPEN,
                                         "failed to bind on server socket");
  }
}

void ServerThread::stop() {
  if (!running_) {
    return;
  }

  // Tell the server to stop
  server_->stop();
  running_ = false;

  // Wait for the server thread to exit
  //
  // Note: this only works if all client connections have closed.  The servers
  // generally wait for everything to be closed before exiting; there currently
  // isn't a way to tell them to just exit now, and shut down existing
  // connections.
  thread_->join();
}

void ServerThread::run() {
  /*
   * Try binding to several ports, in case the one we want is already in use.
   */
  port_ = 12345;
  unsigned int maxRetries = 10;
  for (unsigned int n = 0; n < maxRetries; ++n) {
    // Create the server
    server_ = serverState_->createServer(port_);
    // Install our helper as the server event handler, so that our
    // preServe() method will be called once we've successfully bound to
    // the port and are about to start listening.
    server_->setServerEventHandler(helper_);

    try {
      // Try to serve requests
      server_->serve();
    } catch (const TException&) {
      // TNonblockingServer throws a generic TException if it fails to bind.
      // If we get a TException, we'll optimistically assume the bind failed.
      ++port_;
      continue;
    }

    // Seriously?  serve() is pretty lame.  If it fails to start serving it
    // just returns rather than throwing an exception.
    //
    // We have to use our preServe() hook to tell if serve() successfully
    // started serving and is returning because stop() is called, or if it just
    // failed to start serving in the first place.
    concurrency::Synchronized s(serverMonitor_);
    if (serving_) {
      // Oh good, we started serving and are exiting because
      // we're trying to stop.
      serving_ = false;
      return;
    } else {
      // We never started serving, probably because we failed to bind to the
      // port.  Increment the port number and try again.
      ++port_;
      continue;
    }
  }

  // We failed to bind on any port.
  concurrency::Synchronized s(serverMonitor_);
  error_ = true;
  serverMonitor_.notify();
}

void ServerThread::preServe() {
  // We bound to the port successfully, and are about to start serving requests
  serverState_->bindSuccessful(port_);

  // Set the real server event handler (replacing ourself)
  std::shared_ptr<server::TServerEventHandler> serverEventHandler
      = serverState_->getServerEventHandler();
  server_->setServerEventHandler(serverEventHandler);

  // Notify the main thread that we have successfully started serving requests
  concurrency::Synchronized s(serverMonitor_);
  serving_ = true;
  serverMonitor_.notify();

  // Invoke preServe() on the real event handler, since we ate
  // the original preServe() event.
  if (serverEventHandler) {
    serverEventHandler->preServe();
  }
}
}
}
} // apache::thrift::test

#endif // _THRIFT_TEST_SERVERTHREAD_TCC_
