<commit_before>/*
 * SessionConsoleProcessSocket.cpp
 *
 * Copyright (C) 2009-17 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include <session/SessionConsoleProcessSocket.hpp>

#include <boost/make_shared.hpp>

#include <core/FilePath.hpp>
#include <core/json/Json.hpp>


namespace rstudio {
namespace session {
namespace console_process {

using namespace rstudio::core;

namespace {

// rapid reseeding via srand(time) causes same "random" sequence to be
// returned by rand; only an issue for unit tests, really
bool s_didSeedRand = false;

} // anonymous namespace

ConsoleProcessSocket::ConsoleProcessSocket()
   :
     port_(0),
     serverRunning_(false),
     activeConnections_(0)
{
}

ConsoleProcessSocket::~ConsoleProcessSocket()
{
   try
   {
      stopServer();
   }
   CATCH_UNEXPECTED_EXCEPTION
}

Error ConsoleProcessSocket::ensureServerRunning()
{
   if (serverRunning_)
      return Success();

   long port = 0;
   unsigned portRetries = 0;

   // initialize seed for random port selection
   if (!s_didSeedRand)
   {
      srand(time(NULL));
      s_didSeedRand = true;
   }

   // no user-specified port; pick a random port
   port = 3000 + (rand() % 5000);

   try
   {
      pwsServer_.reset(new terminalServer());

      pwsServer_->set_access_channels(websocketpp::log::alevel::none);
      pwsServer_->init_asio();

      pwsServer_->set_message_handler(
               boost::bind(&ConsoleProcessSocket::onMessage, this, &*pwsServer_, _1, _2));
      pwsServer_->set_http_handler(
               boost::bind(&ConsoleProcessSocket::onHttp, this, &*pwsServer_, _1));
      pwsServer_->set_close_handler(
               boost::bind(&ConsoleProcessSocket::onClose, this, &*pwsServer_, _1));
      pwsServer_->set_open_handler(
               boost::bind(&ConsoleProcessSocket::onOpen, this, &*pwsServer_, _1));

      // try to bind to the given port
      do
      {
         try
         {
            // TODO (gary) can we just try ipv6 without sniffing, then do
            // ipv4 if ipv6 fails?
#if !defined(_WIN32) && !defined(__APPLE__)
            if (core::FilePath("/proc/net/if_inet6").exists())
            {
               // listen will fail without ipv6 support on the machine so we
               // only use it for machines with a ipv6 stack
               pwsServer_->listen(port);
            }
            else
#endif
            {
               // no ipv6 support, fall back to ipv4
               pwsServer_->listen(boost::asio::ip::tcp::v4(), port);
            }

            pwsServer_->start_accept();
            break;
         }
         catch (websocketpp::exception const& e)
         {
            // fail if this isn't the code we're expecting
            // (we're only trying to deal with address in use errors here)
            if (e.code() != websocketpp::transport::asio::error::pass_through)
            {
               return systemError(boost::system::errc::invalid_argument,
                                  e.what(), ERROR_LOCATION);
            }

            // try another random port
            port = 3000 + (rand() % 5000);
         }
      }
      while (++portRetries < 20);

      // if we used up all our retries, abort now
      if (portRetries == 20)
      {
         return systemError(boost::system::errc::not_supported,
                            "Couldn't find an available port",
                            ERROR_LOCATION);
      }

      // start server
      core::thread::safeLaunchThread(
               boost::bind(&ConsoleProcessSocket::watchSocket, this),
               &websocketThread_);

      port_ = port;
      serverRunning_ = true;
   }
   catch (websocketpp::exception const& e)
   {
      LOG_ERROR_MESSAGE(e.what());
      return systemError(boost::system::errc::invalid_argument,
                            e.what(), ERROR_LOCATION);
   }
   CATCH_UNEXPECTED_EXCEPTION

   return Success();
}

void ConsoleProcessSocket::stopServer()
{
   try
   {
      if (serverRunning_)
      {
         releaseAllConnections();

         pwsServer_->stop();
         serverRunning_ = false;
         port_ = 0;
         pwsServer_.reset();
         websocketThread_.join();
      }
   }
   catch (websocketpp::exception const& e)
   {
      LOG_ERROR_MESSAGE(e.what());
   }
   CATCH_UNEXPECTED_EXCEPTION
}

Error ConsoleProcessSocket::listen(
      const std::string& terminalHandle,
      const ConsoleProcessSocketConnectionCallbacks& connectionCallbacks)
{
   if (!serverRunning_)
   {
      return systemError(boost::system::errc::not_connected,
                         terminalHandle,
                         ERROR_LOCATION);
   }

   ConsoleProcessSocketConnectionDetails details = connections_.get(terminalHandle);
   details.handle_ = terminalHandle;
   details.connectionCallbacks_ = connectionCallbacks;
   connections_.set(terminalHandle, details);
   return Success();<commit_msg>fix issue found via unit tests on Linux; the join was never returning because the pwsServer_ had already been released (Mac and Windows didn't care about this)<commit_after>/*
 * SessionConsoleProcessSocket.cpp
 *
 * Copyright (C) 2009-17 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include <session/SessionConsoleProcessSocket.hpp>

#include <boost/make_shared.hpp>

#include <core/FilePath.hpp>
#include <core/json/Json.hpp>


namespace rstudio {
namespace session {
namespace console_process {

using namespace rstudio::core;

namespace {

// rapid reseeding via srand(time) causes same "random" sequence to be
// returned by rand; only an issue for unit tests, really
bool s_didSeedRand = false;

} // anonymous namespace

ConsoleProcessSocket::ConsoleProcessSocket()
   :
     port_(0),
     serverRunning_(false),
     activeConnections_(0)
{
}

ConsoleProcessSocket::~ConsoleProcessSocket()
{
   try
   {
      stopServer();
   }
   CATCH_UNEXPECTED_EXCEPTION
}

Error ConsoleProcessSocket::ensureServerRunning()
{
   if (serverRunning_)
      return Success();

   long port = 0;
   unsigned portRetries = 0;

   // initialize seed for random port selection
   if (!s_didSeedRand)
   {
      srand(time(NULL));
      s_didSeedRand = true;
   }

   // no user-specified port; pick a random port
   port = 3000 + (rand() % 5000);

   try
   {
      pwsServer_.reset(new terminalServer());

      pwsServer_->set_access_channels(websocketpp::log::alevel::none);
      pwsServer_->init_asio();

      pwsServer_->set_message_handler(
               boost::bind(&ConsoleProcessSocket::onMessage, this, &*pwsServer_, _1, _2));
      pwsServer_->set_http_handler(
               boost::bind(&ConsoleProcessSocket::onHttp, this, &*pwsServer_, _1));
      pwsServer_->set_close_handler(
               boost::bind(&ConsoleProcessSocket::onClose, this, &*pwsServer_, _1));
      pwsServer_->set_open_handler(
               boost::bind(&ConsoleProcessSocket::onOpen, this, &*pwsServer_, _1));

      // try to bind to the given port
      do
      {
         try
         {
            // TODO (gary) can we just try ipv6 without sniffing, then do
            // ipv4 if ipv6 fails?
#if !defined(_WIN32) && !defined(__APPLE__)
            if (core::FilePath("/proc/net/if_inet6").exists())
            {
               // listen will fail without ipv6 support on the machine so we
               // only use it for machines with a ipv6 stack
               pwsServer_->listen(port);
            }
            else
#endif
            {
               // no ipv6 support, fall back to ipv4
               pwsServer_->listen(boost::asio::ip::tcp::v4(), port);
            }

            pwsServer_->start_accept();
            break;
         }
         catch (websocketpp::exception const& e)
         {
            // fail if this isn't the code we're expecting
            // (we're only trying to deal with address in use errors here)
            if (e.code() != websocketpp::transport::asio::error::pass_through)
            {
               return systemError(boost::system::errc::invalid_argument,
                                  e.what(), ERROR_LOCATION);
            }

            // try another random port
            port = 3000 + (rand() % 5000);
         }
      }
      while (++portRetries < 20);

      // if we used up all our retries, abort now
      if (portRetries == 20)
      {
         return systemError(boost::system::errc::not_supported,
                            "Couldn't find an available port",
                            ERROR_LOCATION);
      }

      // start server
      core::thread::safeLaunchThread(
               boost::bind(&ConsoleProcessSocket::watchSocket, this),
               &websocketThread_);

      port_ = port;
      serverRunning_ = true;
   }
   catch (websocketpp::exception const& e)
   {
      LOG_ERROR_MESSAGE(e.what());
      return systemError(boost::system::errc::invalid_argument,
                            e.what(), ERROR_LOCATION);
   }
   CATCH_UNEXPECTED_EXCEPTION

   return Success();
}

void ConsoleProcessSocket::stopServer()
{
   try
   {
      if (serverRunning_)
      {
         releaseAllConnections();

         pwsServer_->stop();
         serverRunning_ = false;
         port_ = 0;
         websocketThread_.join();
         pwsServer_.reset();
      }
   }
   catch (websocketpp::exception const& e)
   {
      LOG_ERROR_MESSAGE(e.what());
   }
   CATCH_UNEXPECTED_EXCEPTION
}

Error ConsoleProcessSocket::listen(
      const std::string& terminalHandle,
      const ConsoleProcessSocketConnectionCallbacks& connectionCallbacks)
{
   if (!serverRunning_)
   {
      return systemError(boost::system::errc::not_connected,
                         terminalHandle,
                         ERROR_LOCATION);
   }

   ConsoleProcessSocketConnectionDetails details = connections_.get(terminalHandle);
   details.handle_ = terminalHandle;
   details.connectionCallbacks_ = connectionCallbacks;
   connections_.set(terminalHandle, details);
   return Success();
{'fraction_non_alphanumeric': 0.10174152153987168, 'fraction_numerical': 0.00916590284142988, 'mean_word_length': 2.393779160186625, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7313002', 'n_tokens_mistral': 3143, 'n_tokens_neox': 2988, 'n_words': 783}