<commit_before>/** @file

  A brief file description

  @section license License

  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.
 */

/***************************************************************************
 LogHost.cc

 ***************************************************************************/
#include "ts/ink_platform.h"

#include "LogUtils.h"
#include "LogSock.h"
#include "LogField.h"
#include "LogFile.h"
#include "LogFormat.h"
#include "LogBuffer.h"
#include "LogHost.h"
#include "LogObject.h"
#include "LogConfig.h"
#include "Log.h"

#include "LogCollationClientSM.h"

#define PING true
#define NOPING false

static Ptr<LogFile>
make_orphan_logfile(LogHost *lh, const char *filename)
{
  const char *ext   = "orphan";
  unsigned name_len = (unsigned)(strlen(filename) + strlen(lh->name()) + strlen(ext) + 16);
  char *name_buf    = (char *)ats_malloc(name_len);

  // NT: replace ':'s with '-'s.  This change is necessary because
  // NT doesn't like filenames with ':'s in them.  ^_^
  snprintf(name_buf, name_len, "%s%s%s-%u.%s", filename, LOGFILE_SEPARATOR_STRING, lh->name(), lh->port(), ext);

  // XXX should check for conflicts with orphan filename

  Ptr<LogFile> orphan(new LogFile(name_buf, nullptr, LOG_FILE_ASCII, lh->signature()));

  ats_free(name_buf);
  return orphan;
}

/*-------------------------------------------------------------------------
  LogHost
  -------------------------------------------------------------------------*/

LogHost::LogHost(const char *object_filename, uint64_t object_signature)
  : m_object_filename(ats_strdup(object_filename)),
    m_object_signature(object_signature),
    m_port(0),
    m_name(nullptr),
    m_sock(nullptr),
    m_sock_fd(-1),
    m_connected(false),
    m_orphan_file(nullptr),
    m_log_collation_client_sm(nullptr)
{
  ink_zero(m_ip);
  ink_zero(m_ipstr);
}

LogHost::LogHost(const LogHost &rhs)
  : m_object_filename(ats_strdup(rhs.m_object_filename)),
    m_object_signature(rhs.m_object_signature),
    m_ip(rhs.m_ip),
    m_port(0),
    m_name(ats_strdup(rhs.m_name)),
    m_sock(nullptr),
    m_sock_fd(-1),
    m_connected(false),
    m_orphan_file(nullptr),
    m_log_collation_client_sm(nullptr)
{
  memcpy(m_ipstr, rhs.m_ipstr, sizeof(m_ipstr));
  m_orphan_file = make_orphan_logfile(this, m_object_filename);
}

LogHost::~LogHost()
{
  clear();
  ats_free(m_object_filename);
}

//
// There are 3 ways to establish a LogHost:
// - by "hostname:port" or IP:port", where IP is a string of the
//   form "xxx.xxx.xxx.xxx".
// - by specifying a hostname and a port (as separate arguments).
// - by specifying an ip and a port (as separate arguments).
//
bool
LogHost::set_name_port(const char *hostname, unsigned int pt)
{
  if (!hostname || hostname[0] == 0) {
    Note("Cannot establish LogHost with NULL hostname");
    return false;
  }

  clear(); // remove all previous state for this LogHost

  m_name = ats_strdup(hostname);
  m_port = pt;

  Debug("log-host", "LogHost established as %s:%u", this->name(), this->port());

  m_orphan_file = make_orphan_logfile(this, m_object_filename);
  return true;
}

bool
LogHost::set_ipstr_port(const char *ipstr, unsigned int pt)
{
  if (!ipstr || ipstr[0] == 0) {
    Note("Cannot establish LogHost with NULL ipstr");
    return false;
  }

  clear(); // remove all previous state for this LogHost

  if (0 != m_ip.load(ipstr)) {
    Note("Log host failed to parse IP address %s", ipstr);
  }

  m_port = pt;
  ink_strlcpy(m_ipstr, ipstr, sizeof(m_ipstr));
  m_name = ats_strdup(ipstr);

  Debug("log-host", "LogHost established as %s:%u", name(), pt);

  m_orphan_file = make_orphan_logfile(this, m_object_filename);
  return true;
}

bool
LogHost::set_name_or_ipstr(const char *name_or_ip)
{
  if (name_or_ip && name_or_ip[0] != '\0') {
    std::string_view addr, port;
    if (ats_ip_parse(std::string_view(name_or_ip), &addr, &port) == 0) {
      uint16_t p = port.empty() ? Log::config->collation_port : atoi(port.data());
      char *n    = const_cast<char *>(addr.data());
      // Force termination. We know we can do this because the address
      // string is followed by either a nul or a colon.
      n[addr.size()] = 0;
      if (AF_UNSPEC == ats_ip_check_characters(addr)) {
        return set_name_port(n, p);
      } else {
        return set_ipstr_port(n, p);
      }
    }
  }

  return false;
}

bool
LogHost::connected(bool ping)
{
  if (m_connected && m_sock && m_sock_fd >= 0) {
    if (m_sock->is_connected(m_sock_fd, ping)) {
      return true;
    }
  }
  return false;
}

bool
LogHost::connect()
{
  if (!m_ip.isValid()) {
    Note("Cannot connect to LogHost; host IP has not been established");
    return false;
  }

  if (connected(PING)) {
    return true;
  }

  IpEndpoint target;
  ip_port_text_buffer ipb;
  target.assign(m_ip, htons(m_port));

  if (is_debug_tag_set("log-host")) {
    Debug("log-host", "Connecting to LogHost %s", ats_ip_nptop(&target, ipb, sizeof ipb));
  }

  disconnect(); // make sure connection members are initialized

  if (m_sock == nullptr) {
    m_sock = new LogSock();
    ink_assert(m_sock != nullptr);
  }
  m_sock_fd = m_sock->connect(&target.sa);
  if (m_sock_fd < 0) {
    Note("Connection to LogHost %s failed", ats_ip_nptop(&target, ipb, sizeof ipb));
    return false;
  }
  m_connected = true;

  if (!authenticated()) {
    Note("Authentication to LogHost %s failed", ats_ip_nptop(&target, ipb, sizeof ipb));
    disconnect();
    return false;
  }

  return true;
}

void
LogHost::disconnect()
{
  if (m_sock && m_sock_fd >= 0) {
    m_sock->close(m_sock_fd);
    m_sock_fd = -1;
  }
  if (m_log_collation_client_sm) {
    delete m_log_collation_client_sm;
    m_log_collation_client_sm = nullptr;
  }
  m_connected = false;
}

//
// preprocess the given buffer data before sent to target host
// and try to delete it when its reference become zero.
//
bool
LogHost::preproc_and_try_delete(LogBuffer *&lb)
{
  if (lb == nullptr) {
    Note("Cannot write LogBuffer to LogHost %s; LogBuffer is NULL", name());
    return false;
  }

  LogBufferHeader *buffer_header = lb->header();
  if (buffer_header == nullptr) {
    Note("Cannot write LogBuffer to LogHost %s; LogBufferHeader is NULL", name());
    goto done;
  }

  if (buffer_header->entry_count == 0) {
    // no bytes to write
    goto done;
  }

  // create a new collation client if necessary
  if (m_log_collation_client_sm == nullptr) {
    m_log_collation_client_sm = new LogCollationClientSM(this);
    ink_assert(m_log_collation_client_sm != nullptr);
  }

  // send log_buffer
  if (m_log_collation_client_sm->send(lb) <= 0) {
    goto done;
  }

  return true;

done:
  LogBuffer::destroy(lb);
  return false;
}

//
// write the given buffer data to orphan file and
// try to delete it when its reference become zero.
//
void
LogHost::orphan_write_and_try_delete(LogBuffer *&lb)
{
  RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_num_lost_before_sent_to_network_stat,
                 lb->header()->entry_count);

  RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_bytes_lost_before_sent_to_network_stat,
                 lb->header()->byte_count);

  if (!Log::config->logging_space_exhausted) {
    Debug("log-host", "Sending LogBuffer to orphan file %s", m_orphan_file->get_name());
    m_orphan_file->preproc_and_try_delete(lb);
  } else {
    Debug("log-host", "logging space exhausted, failed to write orphan file, drop(%" PRIu32 ") bytes", lb->header()->byte_count);
    LogBuffer::destroy(lb);
  }
}

void
LogHost::display(FILE *fd)
{
  fprintf(fd, "LogHost: %s:%u, %s\n", name(), port(), (connected(NOPING)) ? "connected" : "not connected");

  LogHost *host = this;
  while (host->failover_link.next != nullptr) {
    fprintf(fd, "Failover: %s:%u, %s\n", host->name(), host->port(), (host->connected(NOPING)) ? "connected" : "not connected");
    host = host->failover_link.next;
  }
}

void
LogHost::clear()
{
  // close an established connection and clear the state of this host

  disconnect();

  ats_free(m_name);
  delete m_sock;
  m_orphan_file.clear();

  ink_zero(m_ip);
  m_port = 0;
  ink_zero(m_ipstr);
  m_name      = nullptr;
  m_sock      = nullptr;
  m_sock_fd   = -1;
  m_connected = false;
}

bool
LogHost::authenticated()
{
  if (!connected(NOPING)) {
    Note("Cannot authenticate LogHost %s; not connected", name());
    return false;
  }

  Debug("log-host", "Authenticating LogHost %s ...", name());
  char *auth_key        = Log::config->collation_secret;
  unsigned auth_key_len = (unsigned)::strlen(auth_key) + 1; // incl null
  int bytes             = m_sock->write(m_sock_fd, auth_key, auth_key_len);
  if ((unsigned)bytes != auth_key_len) {
    Debug("log-host", "... bad write on authenticate");
    return false;
  }

  Debug("log-host", "... authenticated");
  return true;
}

/*-------------------------------------------------------------------------
  LogHostList
  -------------------------------------------------------------------------*/

LogHostList::LogHostList() {}

LogHostList::~LogHostList()
{
  clear();
}

void
LogHostList::add(LogHost *object, bool copy)
{
  ink_assert(object != nullptr);
  if (copy) {
    m_host_list.enqueue(new LogHost(*object));
  } else {
    m_host_list.enqueue(object);
  }
}

unsigned
LogHostList::count()
{
  unsigned cnt = 0;
  for (LogHost *host = first(); host; host = next(host)) {
    cnt++;
  }
  return cnt;
}

void
LogHostList::clear()
{
  LogHost *host;
  while ((host = m_host_list.dequeue())) {
    delete host;
  }
}

int
LogHostList::preproc_and_try_delete(LogBuffer *lb)
{
  int success = false;
  unsigned nr_host, nr;
  bool need_orphan        = true;
  LogHost *available_host = nullptr;

  ink_release_assert(lb->m_references == 0);

  nr_host = nr = count();
  ink_atomic_increment(&lb->m_references, nr_host);

  for (LogHost *host = first(); host && nr; host = next(host)) {
    LogHost *lh    = host;
    available_host = lh;

    do {
      ink_atomic_increment(&lb->m_references, 1);
      success     = lh->preproc_and_try_delete(lb);
      need_orphan = need_orphan && (success == false);
    } while (lb && (success == false) && (lh = lh->failover_link.next));

    nr--;
  }

  if (lb != nullptr && need_orphan && available_host) {
    ink_atomic_increment(&lb->m_references, 1);
    available_host->orphan_write_and_try_delete(lb);
  }

  if (lb != nullptr) {
    LogBuffer::destroy(lb);
  }
  return 0;
}

void
LogHostList::display(FILE *fd)
{
  for (LogHost *host = first(); host; host = next(host)) {
    host->display(fd);
  }
}

bool
LogHostList::operator==(LogHostList &rhs)
{
  LogHost *host;
  for (host = first(); host; host = next(host)) {
    LogHost *rhs_host;
    for (rhs_host = rhs.first(); rhs_host; rhs_host = next(host)) {
      if ((host->port() == rhs_host->port() && host->ip_addr().isValid() && host->ip_addr() == rhs_host->ip_addr()) ||
          (host->name() && rhs_host->name() && (strcmp(host->name(), rhs_host->name()) == 0)) ||
          (*(host->ipstr()) && *(rhs_host->ipstr()) && (strcmp(host->ipstr(), rhs_host->ipstr()) == 0))) {
        break;
      }
    }
    if (rhs_host == nullptr) {
      return false;
    }
  }
  return true;
}
<commit_msg>Log Collation - Memory leak when all hosts are down.<commit_after>/** @file

  A brief file description

  @section license License

  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.
 */

/***************************************************************************
 LogHost.cc

 ***************************************************************************/
#include "ts/ink_platform.h"

#include "LogUtils.h"
#include "LogSock.h"
#include "LogField.h"
#include "LogFile.h"
#include "LogFormat.h"
#include "LogBuffer.h"
#include "LogHost.h"
#include "LogObject.h"
#include "LogConfig.h"
#include "Log.h"

#include "LogCollationClientSM.h"

#define PING true
#define NOPING false

static Ptr<LogFile>
make_orphan_logfile(LogHost *lh, const char *filename)
{
  const char *ext   = "orphan";
  unsigned name_len = (unsigned)(strlen(filename) + strlen(lh->name()) + strlen(ext) + 16);
  char *name_buf    = (char *)ats_malloc(name_len);

  // NT: replace ':'s with '-'s.  This change is necessary because
  // NT doesn't like filenames with ':'s in them.  ^_^
  snprintf(name_buf, name_len, "%s%s%s-%u.%s", filename, LOGFILE_SEPARATOR_STRING, lh->name(), lh->port(), ext);

  // XXX should check for conflicts with orphan filename

  Ptr<LogFile> orphan(new LogFile(name_buf, nullptr, LOG_FILE_ASCII, lh->signature()));

  ats_free(name_buf);
  return orphan;
}

/*-------------------------------------------------------------------------
  LogHost
  -------------------------------------------------------------------------*/

LogHost::LogHost(const char *object_filename, uint64_t object_signature)
  : m_object_filename(ats_strdup(object_filename)),
    m_object_signature(object_signature),
    m_port(0),
    m_name(nullptr),
    m_sock(nullptr),
    m_sock_fd(-1),
    m_connected(false),
    m_orphan_file(nullptr),
    m_log_collation_client_sm(nullptr)
{
  ink_zero(m_ip);
  ink_zero(m_ipstr);
}

LogHost::LogHost(const LogHost &rhs)
  : m_object_filename(ats_strdup(rhs.m_object_filename)),
    m_object_signature(rhs.m_object_signature),
    m_ip(rhs.m_ip),
    m_port(0),
    m_name(ats_strdup(rhs.m_name)),
    m_sock(nullptr),
    m_sock_fd(-1),
    m_connected(false),
    m_orphan_file(nullptr),
    m_log_collation_client_sm(nullptr)
{
  memcpy(m_ipstr, rhs.m_ipstr, sizeof(m_ipstr));
  m_orphan_file = make_orphan_logfile(this, m_object_filename);
}

LogHost::~LogHost()
{
  clear();
  ats_free(m_object_filename);
}

//
// There are 3 ways to establish a LogHost:
// - by "hostname:port" or IP:port", where IP is a string of the
//   form "xxx.xxx.xxx.xxx".
// - by specifying a hostname and a port (as separate arguments).
// - by specifying an ip and a port (as separate arguments).
//
bool
LogHost::set_name_port(const char *hostname, unsigned int pt)
{
  if (!hostname || hostname[0] == 0) {
    Note("Cannot establish LogHost with NULL hostname");
    return false;
  }

  clear(); // remove all previous state for this LogHost

  m_name = ats_strdup(hostname);
  m_port = pt;

  Debug("log-host", "LogHost established as %s:%u", this->name(), this->port());

  m_orphan_file = make_orphan_logfile(this, m_object_filename);
  return true;
}

bool
LogHost::set_ipstr_port(const char *ipstr, unsigned int pt)
{
  if (!ipstr || ipstr[0] == 0) {
    Note("Cannot establish LogHost with NULL ipstr");
    return false;
  }

  clear(); // remove all previous state for this LogHost

  if (0 != m_ip.load(ipstr)) {
    Note("Log host failed to parse IP address %s", ipstr);
  }

  m_port = pt;
  ink_strlcpy(m_ipstr, ipstr, sizeof(m_ipstr));
  m_name = ats_strdup(ipstr);

  Debug("log-host", "LogHost established as %s:%u", name(), pt);

  m_orphan_file = make_orphan_logfile(this, m_object_filename);
  return true;
}

bool
LogHost::set_name_or_ipstr(const char *name_or_ip)
{
  if (name_or_ip && name_or_ip[0] != '\0') {
    std::string_view addr, port;
    if (ats_ip_parse(std::string_view(name_or_ip), &addr, &port) == 0) {
      uint16_t p = port.empty() ? Log::config->collation_port : atoi(port.data());
      char *n    = const_cast<char *>(addr.data());
      // Force termination. We know we can do this because the address
      // string is followed by either a nul or a colon.
      n[addr.size()] = 0;
      if (AF_UNSPEC == ats_ip_check_characters(addr)) {
        return set_name_port(n, p);
      } else {
        return set_ipstr_port(n, p);
      }
    }
  }

  return false;
}

bool
LogHost::connected(bool ping)
{
  if (m_connected && m_sock && m_sock_fd >= 0) {
    if (m_sock->is_connected(m_sock_fd, ping)) {
      return true;
    }
  }
  return false;
}

bool
LogHost::connect()
{
  if (!m_ip.isValid()) {
    Note("Cannot connect to LogHost; host IP has not been established");
    return false;
  }

  if (connected(PING)) {
    return true;
  }

  IpEndpoint target;
  ip_port_text_buffer ipb;
  target.assign(m_ip, htons(m_port));

  if (is_debug_tag_set("log-host")) {
    Debug("log-host", "Connecting to LogHost %s", ats_ip_nptop(&target, ipb, sizeof ipb));
  }

  disconnect(); // make sure connection members are initialized

  if (m_sock == nullptr) {
    m_sock = new LogSock();
    ink_assert(m_sock != nullptr);
  }
  m_sock_fd = m_sock->connect(&target.sa);
  if (m_sock_fd < 0) {
    Note("Connection to LogHost %s failed", ats_ip_nptop(&target, ipb, sizeof ipb));
    return false;
  }
  m_connected = true;

  if (!authenticated()) {
    Note("Authentication to LogHost %s failed", ats_ip_nptop(&target, ipb, sizeof ipb));
    disconnect();
    return false;
  }

  return true;
}

void
LogHost::disconnect()
{
  if (m_sock && m_sock_fd >= 0) {
    m_sock->close(m_sock_fd);
    m_sock_fd = -1;
  }
  if (m_log_collation_client_sm) {
    delete m_log_collation_client_sm;
    m_log_collation_client_sm = nullptr;
  }
  m_connected = false;
}

//
// preprocess the given buffer data before sent to target host
// and try to delete it when its reference become zero.
//
bool
LogHost::preproc_and_try_delete(LogBuffer *&lb)
{
  if (lb == nullptr) {
    Note("Cannot write LogBuffer to LogHost %s; LogBuffer is NULL", name());
    return false;
  }

  LogBufferHeader *buffer_header = lb->header();
  if (buffer_header == nullptr) {
    Note("Cannot write LogBuffer to LogHost %s; LogBufferHeader is NULL", name());
    goto done;
  }

  if (buffer_header->entry_count == 0) {
    // no bytes to write
    goto done;
  }

  // create a new collation client if necessary
  if (m_log_collation_client_sm == nullptr) {
    m_log_collation_client_sm = new LogCollationClientSM(this);
    ink_assert(m_log_collation_client_sm != nullptr);
  }

  // send log_buffer
  if (m_log_collation_client_sm->send(lb) <= 0) {
    goto done;
  }

  return true;

done:
  LogBuffer::destroy(lb);
  return false;
}

//
// write the given buffer data to orphan file and
// try to delete it when its reference become zero.
//
void
LogHost::orphan_write_and_try_delete(LogBuffer *&lb)
{
  RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_num_lost_before_sent_to_network_stat,
                 lb->header()->entry_count);

  RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_bytes_lost_before_sent_to_network_stat,
                 lb->header()->byte_count);

  if (!Log::config->logging_space_exhausted) {
    Debug("log-host", "Sending LogBuffer to orphan file %s", m_orphan_file->get_name());
    m_orphan_file->preproc_and_try_delete(lb);
  } else {
    Debug("log-host", "logging space exhausted, failed to write orphan file, drop(%" PRIu32 ") bytes", lb->header()->byte_count);
  }
  LogBuffer::destroy(lb);
}

void
LogHost::display(FILE *fd)
{
  fprintf(fd, "LogHost: %s:%u, %s\n", name(), port(), (connected(NOPING)) ? "connected" : "not connected");

  LogHost *host = this;
  while (host->failover_link.next != nullptr) {
    fprintf(fd, "Failover: %s:%u, %s\n", host->name(), host->port(), (host->connected(NOPING)) ? "connected" : "not connected");
    host = host->failover_link.next;
  }
}

void
LogHost::clear()
{
  // close an established connection and clear the state of this host

  disconnect();

  ats_free(m_name);
  delete m_sock;
  m_orphan_file.clear();

  ink_zero(m_ip);
  m_port = 0;
  ink_zero(m_ipstr);
  m_name      = nullptr;
  m_sock      = nullptr;
  m_sock_fd   = -1;
  m_connected = false;
}

bool
LogHost::authenticated()
{
  if (!connected(NOPING)) {
    Note("Cannot authenticate LogHost %s; not connected", name());
    return false;
  }

  Debug("log-host", "Authenticating LogHost %s ...", name());
  char *auth_key        = Log::config->collation_secret;
  unsigned auth_key_len = (unsigned)::strlen(auth_key) + 1; // incl null
  int bytes             = m_sock->write(m_sock_fd, auth_key, auth_key_len);
  if ((unsigned)bytes != auth_key_len) {
    Debug("log-host", "... bad write on authenticate");
    return false;
  }

  Debug("log-host", "... authenticated");
  return true;
}

/*-------------------------------------------------------------------------
  LogHostList
  -------------------------------------------------------------------------*/

LogHostList::LogHostList() {}

LogHostList::~LogHostList()
{
  clear();
}

void
LogHostList::add(LogHost *object, bool copy)
{
  ink_assert(object != nullptr);
  if (copy) {
    m_host_list.enqueue(new LogHost(*object));
  } else {
    m_host_list.enqueue(object);
  }
}

unsigned
LogHostList::count()
{
  unsigned cnt = 0;
  for (LogHost *host = first(); host; host = next(host)) {
    cnt++;
  }
  return cnt;
}

void
LogHostList::clear()
{
  LogHost *host;
  while ((host = m_host_list.dequeue())) {
    delete host;
  }
}

int
LogHostList::preproc_and_try_delete(LogBuffer *lb)
{
  int success = false;
  unsigned nr_host, nr;
  bool need_orphan        = true;
  LogHost *available_host = nullptr;

  ink_release_assert(lb->m_references == 0);

  nr_host = nr = count();
  ink_atomic_increment(&lb->m_references, nr_host);

  for (LogHost *host = first(); host && nr; host = next(host)) {
    LogHost *lh    = host;
    available_host = lh;

    do {
      ink_atomic_increment(&lb->m_references, 1);
      success     = lh->preproc_and_try_delete(lb);
      need_orphan = need_orphan && (success == false);
    } while (lb && (success == false) && (lh = lh->failover_link.next));

    nr--;
  }

  if (lb != nullptr && need_orphan && available_host) {
    ink_atomic_increment(&lb->m_references, 1);
    available_host->orphan_write_and_try_delete(lb);
  }

  if (lb != nullptr) {
    LogBuffer::destroy(lb);
  }
  return 0;
}

void
LogHostList::display(FILE *fd)
{
  for (LogHost *host = first(); host; host = next(host)) {
    host->display(fd);
  }
}

bool
LogHostList::operator==(LogHostList &rhs)
{
  LogHost *host;
  for (host = first(); host; host = next(host)) {
    LogHost *rhs_host;
    for (rhs_host = rhs.first(); rhs_host; rhs_host = next(host)) {
      if ((host->port() == rhs_host->port() && host->ip_addr().isValid() && host->ip_addr() == rhs_host->ip_addr()) ||
          (host->name() && rhs_host->name() && (strcmp(host->name(), rhs_host->name()) == 0)) ||
          (*(host->ipstr()) && *(rhs_host->ipstr()) && (strcmp(host->ipstr(), rhs_host->ipstr()) == 0))) {
        break;
      }
    }
    if (rhs_host == nullptr) {
      return false;
    }
  }
  return true;
}
<|endoftext|>


<commit_before>/**
 * 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.
 */

#include <errno.h>
#include <signal.h>
#include <stdio.h> // For perror.
#include <string.h>

#include <map>
#include <set>

#include <process/clock.hpp>
#include <process/defer.hpp>
#include <process/dispatch.hpp>
#include <process/id.hpp>

#include <stout/check.hpp>
#include <stout/exit.hpp>
#include <stout/foreach.hpp>
#include <stout/lambda.hpp>
#include <stout/nothing.hpp>
#include <stout/option.hpp>
#include <stout/os.hpp>
#include <stout/uuid.hpp>

#include "common/type_utils.hpp"

#include "slave/flags.hpp"
#include "slave/process_isolator.hpp"
#include "slave/state.hpp"

using namespace process;

using std::map;
using std::set;
using std::string;

using process::defer;
using process::wait; // Necessary on some OS's to disambiguate.

namespace mesos {
namespace internal {
namespace slave {

using launcher::ExecutorLauncher;

using state::SlaveState;
using state::FrameworkState;
using state::ExecutorState;
using state::RunState;

ProcessIsolator::ProcessIsolator()
  : ProcessBase(ID::generate("process-isolator")),
    initialized(false) {}

void ProcessIsolator::initialize(
    const Flags& _flags,
    const Resources& _,
    bool _local,
    const PID<Slave>& _slave)
{
  flags = _flags;
  local = _local;
  slave = _slave;

  initialized = true;
}

void ProcessIsolator::launchExecutor(
    const SlaveID& slaveId,
    const FrameworkID& frameworkId,
    const FrameworkInfo& frameworkInfo,
    const ExecutorInfo& executorInfo,
    const UUID& uuid,
    const string& directory,
    const Resources& resources)
{
  CHECK(initialized) << "Cannot launch executors before initialization!";

  const ExecutorID& executorId = executorInfo.executor_id();

  LOG(INFO) << "Launching " << executorId
            << " (" << executorInfo.command().value() << ")"
            << " in " << directory
            << " with resources " << resources
            << "' for framework " << frameworkId;

  ProcessInfo* info = new ProcessInfo(frameworkId, executorId);

  infos[frameworkId][executorId] = info;

  // Use pipes to determine which child has successfully changed session.
  int pipes[2];
  if (pipe(pipes) < 0) {
    PLOG(FATAL) << "Failed to create a pipe";
  }

  // Set the FD_CLOEXEC flags on these pipes
  Try<Nothing> cloexec = os::cloexec(pipes[0]);
  CHECK_SOME(cloexec) << "Error setting FD_CLOEXEC on pipe[0]";

  cloexec = os::cloexec(pipes[1]);
  CHECK_SOME(cloexec) << "Error setting FD_CLOEXEC on pipe[1]";

  // Create the ExecutorLauncher instance before the fork for the
  // child process to use.
  ExecutorLauncher launcher(
      slaveId,
      frameworkId,
      executorInfo.executor_id(),
      uuid,
      executorInfo.command(),
      frameworkInfo.user(),
      directory,
      flags.work_dir,
      slave,
      flags.frameworks_home,
      flags.hadoop_home,
      !local,
      flags.switch_user,
      frameworkInfo.checkpoint());

  // We get the environment map for launching mesos-launcher before
  // the fork, because we have seen deadlock issues with ostringstream
  // in the forked process before it calls exec.
  map<string, string> env = launcher.getLauncherEnvironment();

  pid_t pid;
  if ((pid = fork()) == -1) {
    PLOG(FATAL) << "Failed to fork to launch new executor";
  }

  if (pid > 0) {
    os::close(pipes[1]);

    // Get the child's pid via the pipe.
    if (read(pipes[0], &pid, sizeof(pid)) == -1) {
      PLOG(FATAL) << "Failed to get child PID from pipe";
    }

    os::close(pipes[0]);

    // In parent process.
    LOG(INFO) << "Forked executor at " << pid;

    // Record the pid (should also be the pgid since we setsid below).
    infos[frameworkId][executorId]->pid = pid;

    reaper.monitor(pid)
      .onAny(defer(PID<ProcessIsolator>(this),
                   &ProcessIsolator::reaped,
                   pid,
                   lambda::_1));

    // Tell the slave this executor has started.
    dispatch(slave, &Slave::executorStarted, frameworkId, executorId, pid);
  } else {
    // In child process, we make cleanup easier by putting process
    // into it's own session. DO NOT USE GLOG!
    os::close(pipes[0]);

    // NOTE: We setsid() in a loop because setsid() might fail if another
    // process has the same process group id as the calling process.
    while ((pid = setsid()) == -1) {
      perror("Could not put executor in its own session");

      std::cout << "Forking another process and retrying ..." << std::endl;

      if ((pid = fork()) == -1) {
        perror("Failed to fork to launch executor");
        abort();
      }

      if (pid > 0) {
        // In parent process.
        // It is ok to suicide here, though process reaper signals the exit,
        // because the process isolator ignores unknown processes.
        exit(0);
      }
    }

    if (write(pipes[1], &pid, sizeof(pid)) != sizeof(pid)) {
      perror("Failed to write PID on pipe");
      abort();
    }

    os::close(pipes[1]);

    // Setup the environment for launcher.
    foreachpair (const string& key, const string& value, env) {
      os::setenv(key, value);
    }

    const char** args = (const char**) new char*[2];

    // Determine path for mesos-launcher.
    Try<string> realpath = os::realpath(
        path::join(flags.launcher_dir, "mesos-launcher"));

    if (realpath.isError()) {
      EXIT(1) << "Failed to determine the canonical path "
              << "for the mesos-launcher: " << realpath.error();
    }

    // Grab a copy of the path so that we can reliably use 'c_str()'.
    const string& path = realpath.get();

    args[0] = path.c_str();
    args[1] = NULL;

    // Execute the mesos-launcher!
    execvp(args[0], (char* const*) args);

    // If we get here, the execvp call failed.
    perror("Failed to execvp the mesos-launcher");
    abort();
  }
}

// NOTE: This function can be called by the isolator itself or by the
// slave if it doesn't hear about an executor exit after it sends a
// shutdown message.
void ProcessIsolator::killExecutor(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId)
{
  CHECK(initialized) << "Cannot kill executors before initialization!";

  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    LOG(ERROR) << "Asked to kill an unknown/killed executor! " << executorId;
    return;
  }

  const Option<pid_t>& pid = infos[frameworkId][executorId]->pid;

  if (pid.isSome()) {
    // TODO(vinod): Call killtree on the pid of the actual executor process
    // that is running the tasks (stored in the local storage by the
    // executor module).
    os::killtree(pid.get(), SIGKILL, true, true, &LOG(INFO));

    // Also kill all processes that belong to the process group of the executor.
    // This is valuable in situations where the top level executor process
    // exited and hence killtree is unable to kill any spawned orphans.
    // NOTE: This assumes that the process group id of the executor process is
    // same as its pid (which is expected to be the case with setsid()).
    // TODO(vinod): Also (recursively) kill processes belonging to the
    // same session, but have a different process group id.
    if (killpg(pid.get(), SIGKILL) == -1 && errno != ESRCH) {
      PLOG(WARNING) << "Failed to kill process group " << pid.get();
    }

    infos[frameworkId][executorId]->killed = true;
  }
}

void ProcessIsolator::resourcesChanged(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId,
    const Resources& resources)
{
  CHECK(initialized) << "Cannot do resourcesChanged before initialization!";

  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    LOG(INFO) << "Asked to update resources for an unknown/killed executor '"
              << executorId << "' of framework " << frameworkId;
    return;
  }

  ProcessInfo* info = CHECK_NOTNULL(infos[frameworkId][executorId]);

  info->resources = resources;

  // Do nothing; subclasses may override this.
}

Future<Nothing> ProcessIsolator::recover(
    const Option<SlaveState>& state)
{
  LOG(INFO) << "Recovering isolator";

  if (state.isNone()) {
    return Nothing();
  }

  foreachvalue (const FrameworkState& framework, state.get().frameworks) {
    foreachvalue (const ExecutorState& executor, framework.executors) {
      LOG(INFO) << "Recovering executor '" << executor.id
                << "' of framework " << framework.id;

      if (executor.info.isNone()) {
        LOG(WARNING) << "Skipping recovery of executor '" << executor.id
                     << "' of framework " << framework.id
                     << " because its info cannot be recovered";
        continue;
      }

      if (executor.latest.isNone()) {
        LOG(WARNING) << "Skipping recovery of executor '" << executor.id
                     << "' of framework " << framework.id
                     << " because its latest run cannot be recovered";
        continue;
      }

      // We are only interested in the latest run of the executor!
      const UUID& uuid = executor.latest.get();
      CHECK(executor.runs.contains(uuid));
      const RunState& run  = executor.runs.get(uuid).get();

      ProcessInfo* info =
        new ProcessInfo(framework.id, executor.id, run.forkedPid);

      infos[framework.id][executor.id] = info;

      // Add the pid to the reaper to monitor exit status.
      if (run.forkedPid.isSome()) {
        reaper.monitor(run.forkedPid.get())
          .onAny(defer(PID<ProcessIsolator>(this),
                       &ProcessIsolator::reaped,
                       run.forkedPid.get(),
                       lambda::_1));
      }
    }
  }

  return Nothing();
}

Future<ResourceStatistics> ProcessIsolator::usage(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId)
{
  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    return Future<ResourceStatistics>::failed("Unknown/killed executor");
  }

  ProcessInfo* info = infos[frameworkId][executorId];
  CHECK_NOTNULL(info);

  ResourceStatistics result;

  result.set_timestamp(Clock::now().secs());

  // Set the resource allocations.
  const Option<Bytes>& mem = info->resources.mem();
  if (mem.isSome()) {
    result.set_mem_limit_bytes(mem.get().bytes());
  }

  const Option<double>& cpus = info->resources.cpus();
  if (cpus.isSome()) {
    result.set_cpus_limit(cpus.get());
  }

  CHECK_SOME(info->pid);

  Result<os::Process> process = os::process(info->pid.get());

  if (!process.isSome()) {
    return Future<ResourceStatistics>::failed(
        process.isError() ? process.error() : "Process does not exist");
  }

  result.set_timestamp(Clock::now().secs());

  if (process.get().rss.isSome()) {
    result.set_mem_rss_bytes(process.get().rss.get().bytes());
  }

  // We only show utime and stime when both are available, otherwise
  // we're exposing a partial view of the CPU times.
  if (process.get().utime.isSome() && process.get().stime.isSome()) {
    result.set_cpus_user_time_secs(process.get().utime.get().secs());
    result.set_cpus_system_time_secs(process.get().stime.get().secs());
  }

  // Now aggregate all descendant process usage statistics.
  const Try<set<pid_t> >& children = os::children(info->pid.get(), true);

  if (children.isError()) {
    return Future<ResourceStatistics>::failed(
        "Failed to get children of " + stringify(info->pid.get()) + ": " +
        children.error());
  }

  // Aggregate the usage of all child processes.
  foreach (pid_t child, children.get()) {
    process = os::process(child);

    // Skip processes that disappear.
    if (process.isNone()) {
      continue;
    }

    if (process.isError()) {
      LOG(WARNING) << "Failed to get status of descendant process " << child
                   << " of parent " << info->pid.get() << ": "
                   << process.error();
      continue;
    }

    if (process.get().rss.isSome()) {
      result.set_mem_rss_bytes(
          result.mem_rss_bytes() + process.get().rss.get().bytes());
    }

    // We only show utime and stime when both are available, otherwise
    // we're exposing a partial view of the CPU times.
    if (process.get().utime.isSome() && process.get().stime.isSome()) {
      result.set_cpus_user_time_secs(
          result.cpus_user_time_secs() + process.get().utime.get().secs());
      result.set_cpus_system_time_secs(
          result.cpus_system_time_secs() + process.get().stime.get().secs());
    }
  }

  return result;
}

void ProcessIsolator::reaped(pid_t pid, const Future<Option<int> >& status)
{
  foreachkey (const FrameworkID& frameworkId, infos) {
    foreachkey (const ExecutorID& executorId, infos[frameworkId]) {
      ProcessInfo* info = infos[frameworkId][executorId];

      if (info->pid.isSome() && info->pid.get() == pid) {
        if (!status.isReady()) {
          LOG(ERROR) << "Failed to get the status for executor '" << executorId
                     << "' of framework " << frameworkId << ": "
                     << (status.isFailed() ? status.failure() : "discarded");
          return;
        }

        LOG(INFO) << "Telling slave of terminated executor '" << executorId
                  << "' of framework " << frameworkId;

        dispatch(slave,
                 &Slave::executorTerminated,
                 frameworkId,
                 executorId,
                 status.get(),
                 false,
                 "Executor terminated");

        if (!info->killed) {
          // Try and cleanup after the executor.
          killExecutor(frameworkId, executorId);
        }

        if (infos[frameworkId].size() == 1) {
          infos.erase(frameworkId);
        } else {
          infos[frameworkId].erase(executorId);
        }
        delete info;

        return;
      }
    }
  }
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<commit_msg>Fixed a log line to show the path of launcher in process isolator.<commit_after>/**
 * 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.
 */

#include <errno.h>
#include <signal.h>
#include <stdio.h> // For perror.
#include <string.h>

#include <map>
#include <set>

#include <process/clock.hpp>
#include <process/defer.hpp>
#include <process/dispatch.hpp>
#include <process/id.hpp>

#include <stout/check.hpp>
#include <stout/exit.hpp>
#include <stout/foreach.hpp>
#include <stout/lambda.hpp>
#include <stout/nothing.hpp>
#include <stout/option.hpp>
#include <stout/os.hpp>
#include <stout/uuid.hpp>

#include "common/type_utils.hpp"

#include "slave/flags.hpp"
#include "slave/process_isolator.hpp"
#include "slave/state.hpp"

using namespace process;

using std::map;
using std::set;
using std::string;

using process::defer;
using process::wait; // Necessary on some OS's to disambiguate.

namespace mesos {
namespace internal {
namespace slave {

using launcher::ExecutorLauncher;

using state::SlaveState;
using state::FrameworkState;
using state::ExecutorState;
using state::RunState;

ProcessIsolator::ProcessIsolator()
  : ProcessBase(ID::generate("process-isolator")),
    initialized(false) {}

void ProcessIsolator::initialize(
    const Flags& _flags,
    const Resources& _,
    bool _local,
    const PID<Slave>& _slave)
{
  flags = _flags;
  local = _local;
  slave = _slave;

  initialized = true;
}

void ProcessIsolator::launchExecutor(
    const SlaveID& slaveId,
    const FrameworkID& frameworkId,
    const FrameworkInfo& frameworkInfo,
    const ExecutorInfo& executorInfo,
    const UUID& uuid,
    const string& directory,
    const Resources& resources)
{
  CHECK(initialized) << "Cannot launch executors before initialization!";

  const ExecutorID& executorId = executorInfo.executor_id();

  LOG(INFO) << "Launching " << executorId
            << " (" << executorInfo.command().value() << ")"
            << " in " << directory
            << " with resources " << resources
            << "' for framework " << frameworkId;

  ProcessInfo* info = new ProcessInfo(frameworkId, executorId);

  infos[frameworkId][executorId] = info;

  // Use pipes to determine which child has successfully changed session.
  int pipes[2];
  if (pipe(pipes) < 0) {
    PLOG(FATAL) << "Failed to create a pipe";
  }

  // Set the FD_CLOEXEC flags on these pipes
  Try<Nothing> cloexec = os::cloexec(pipes[0]);
  CHECK_SOME(cloexec) << "Error setting FD_CLOEXEC on pipe[0]";

  cloexec = os::cloexec(pipes[1]);
  CHECK_SOME(cloexec) << "Error setting FD_CLOEXEC on pipe[1]";

  // Create the ExecutorLauncher instance before the fork for the
  // child process to use.
  ExecutorLauncher launcher(
      slaveId,
      frameworkId,
      executorInfo.executor_id(),
      uuid,
      executorInfo.command(),
      frameworkInfo.user(),
      directory,
      flags.work_dir,
      slave,
      flags.frameworks_home,
      flags.hadoop_home,
      !local,
      flags.switch_user,
      frameworkInfo.checkpoint());

  // We get the environment map for launching mesos-launcher before
  // the fork, because we have seen deadlock issues with ostringstream
  // in the forked process before it calls exec.
  map<string, string> env = launcher.getLauncherEnvironment();

  pid_t pid;
  if ((pid = fork()) == -1) {
    PLOG(FATAL) << "Failed to fork to launch new executor";
  }

  if (pid > 0) {
    os::close(pipes[1]);

    // Get the child's pid via the pipe.
    if (read(pipes[0], &pid, sizeof(pid)) == -1) {
      PLOG(FATAL) << "Failed to get child PID from pipe";
    }

    os::close(pipes[0]);

    // In parent process.
    LOG(INFO) << "Forked executor at " << pid;

    // Record the pid (should also be the pgid since we setsid below).
    infos[frameworkId][executorId]->pid = pid;

    reaper.monitor(pid)
      .onAny(defer(PID<ProcessIsolator>(this),
                   &ProcessIsolator::reaped,
                   pid,
                   lambda::_1));

    // Tell the slave this executor has started.
    dispatch(slave, &Slave::executorStarted, frameworkId, executorId, pid);
  } else {
    // In child process, we make cleanup easier by putting process
    // into it's own session. DO NOT USE GLOG!
    os::close(pipes[0]);

    // NOTE: We setsid() in a loop because setsid() might fail if another
    // process has the same process group id as the calling process.
    while ((pid = setsid()) == -1) {
      perror("Could not put executor in its own session");

      std::cout << "Forking another process and retrying ..." << std::endl;

      if ((pid = fork()) == -1) {
        perror("Failed to fork to launch executor");
        abort();
      }

      if (pid > 0) {
        // In parent process.
        // It is ok to suicide here, though process reaper signals the exit,
        // because the process isolator ignores unknown processes.
        exit(0);
      }
    }

    if (write(pipes[1], &pid, sizeof(pid)) != sizeof(pid)) {
      perror("Failed to write PID on pipe");
      abort();
    }

    os::close(pipes[1]);

    // Setup the environment for launcher.
    foreachpair (const string& key, const string& value, env) {
      os::setenv(key, value);
    }

    const char** args = (const char**) new char*[2];

    // Determine path for mesos-launcher.
    Try<string> realpath = os::realpath(
        path::join(flags.launcher_dir, "mesos-launcher"));

    if (realpath.isError()) {
      EXIT(1) << "Failed to determine the canonical path "
              << "for the mesos-launcher '"
              << path::join(flags.launcher_dir, "mesos-launcher")
              << "': " << realpath.error();
    }

    // Grab a copy of the path so that we can reliably use 'c_str()'.
    const string& path = realpath.get();

    args[0] = path.c_str();
    args[1] = NULL;

    // Execute the mesos-launcher!
    execvp(args[0], (char* const*) args);

    // If we get here, the execvp call failed.
    perror("Failed to execvp the mesos-launcher");
    abort();
  }
}

// NOTE: This function can be called by the isolator itself or by the
// slave if it doesn't hear about an executor exit after it sends a
// shutdown message.
void ProcessIsolator::killExecutor(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId)
{
  CHECK(initialized) << "Cannot kill executors before initialization!";

  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    LOG(ERROR) << "Asked to kill an unknown/killed executor! " << executorId;
    return;
  }

  const Option<pid_t>& pid = infos[frameworkId][executorId]->pid;

  if (pid.isSome()) {
    // TODO(vinod): Call killtree on the pid of the actual executor process
    // that is running the tasks (stored in the local storage by the
    // executor module).
    os::killtree(pid.get(), SIGKILL, true, true, &LOG(INFO));

    // Also kill all processes that belong to the process group of the executor.
    // This is valuable in situations where the top level executor process
    // exited and hence killtree is unable to kill any spawned orphans.
    // NOTE: This assumes that the process group id of the executor process is
    // same as its pid (which is expected to be the case with setsid()).
    // TODO(vinod): Also (recursively) kill processes belonging to the
    // same session, but have a different process group id.
    if (killpg(pid.get(), SIGKILL) == -1 && errno != ESRCH) {
      PLOG(WARNING) << "Failed to kill process group " << pid.get();
    }

    infos[frameworkId][executorId]->killed = true;
  }
}

void ProcessIsolator::resourcesChanged(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId,
    const Resources& resources)
{
  CHECK(initialized) << "Cannot do resourcesChanged before initialization!";

  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    LOG(INFO) << "Asked to update resources for an unknown/killed executor '"
              << executorId << "' of framework " << frameworkId;
    return;
  }

  ProcessInfo* info = CHECK_NOTNULL(infos[frameworkId][executorId]);

  info->resources = resources;

  // Do nothing; subclasses may override this.
}

Future<Nothing> ProcessIsolator::recover(
    const Option<SlaveState>& state)
{
  LOG(INFO) << "Recovering isolator";

  if (state.isNone()) {
    return Nothing();
  }

  foreachvalue (const FrameworkState& framework, state.get().frameworks) {
    foreachvalue (const ExecutorState& executor, framework.executors) {
      LOG(INFO) << "Recovering executor '" << executor.id
                << "' of framework " << framework.id;

      if (executor.info.isNone()) {
        LOG(WARNING) << "Skipping recovery of executor '" << executor.id
                     << "' of framework " << framework.id
                     << " because its info cannot be recovered";
        continue;
      }

      if (executor.latest.isNone()) {
        LOG(WARNING) << "Skipping recovery of executor '" << executor.id
                     << "' of framework " << framework.id
                     << " because its latest run cannot be recovered";
        continue;
      }

      // We are only interested in the latest run of the executor!
      const UUID& uuid = executor.latest.get();
      CHECK(executor.runs.contains(uuid));
      const RunState& run  = executor.runs.get(uuid).get();

      ProcessInfo* info =
        new ProcessInfo(framework.id, executor.id, run.forkedPid);

      infos[framework.id][executor.id] = info;

      // Add the pid to the reaper to monitor exit status.
      if (run.forkedPid.isSome()) {
        reaper.monitor(run.forkedPid.get())
          .onAny(defer(PID<ProcessIsolator>(this),
                       &ProcessIsolator::reaped,
                       run.forkedPid.get(),
                       lambda::_1));
      }
    }
  }

  return Nothing();
}

Future<ResourceStatistics> ProcessIsolator::usage(
    const FrameworkID& frameworkId,
    const ExecutorID& executorId)
{
  if (!infos.contains(frameworkId) ||
      !infos[frameworkId].contains(executorId) ||
      infos[frameworkId][executorId]->killed) {
    return Future<ResourceStatistics>::failed("Unknown/killed executor");
  }

  ProcessInfo* info = infos[frameworkId][executorId];
  CHECK_NOTNULL(info);

  ResourceStatistics result;

  result.set_timestamp(Clock::now().secs());

  // Set the resource allocations.
  const Option<Bytes>& mem = info->resources.mem();
  if (mem.isSome()) {
    result.set_mem_limit_bytes(mem.get().bytes());
  }

  const Option<double>& cpus = info->resources.cpus();
  if (cpus.isSome()) {
    result.set_cpus_limit(cpus.get());
  }

  CHECK_SOME(info->pid);

  Result<os::Process> process = os::process(info->pid.get());

  if (!process.isSome()) {
    return Future<ResourceStatistics>::failed(
        process.isError() ? process.error() : "Process does not exist");
  }

  result.set_timestamp(Clock::now().secs());

  if (process.get().rss.isSome()) {
    result.set_mem_rss_bytes(process.get().rss.get().bytes());
  }

  // We only show utime and stime when both are available, otherwise
  // we're exposing a partial view of the CPU times.
  if (process.get().utime.isSome() && process.get().stime.isSome()) {
    result.set_cpus_user_time_secs(process.get().utime.get().secs());
    result.set_cpus_system_time_secs(process.get().stime.get().secs());
  }

  // Now aggregate all descendant process usage statistics.
  const Try<set<pid_t> >& children = os::children(info->pid.get(), true);

  if (children.isError()) {
    return Future<ResourceStatistics>::failed(
        "Failed to get children of " + stringify(info->pid.get()) + ": " +
        children.error());
  }

  // Aggregate the usage of all child processes.
  foreach (pid_t child, children.get()) {
    process = os::process(child);

    // Skip processes that disappear.
    if (process.isNone()) {
      continue;
    }

    if (process.isError()) {
      LOG(WARNING) << "Failed to get status of descendant process " << child
                   << " of parent " << info->pid.get() << ": "
                   << process.error();
      continue;
    }

    if (process.get().rss.isSome()) {
      result.set_mem_rss_bytes(
          result.mem_rss_bytes() + process.get().rss.get().bytes());
    }

    // We only show utime and stime when both are available, otherwise
    // we're exposing a partial view of the CPU times.
    if (process.get().utime.isSome() && process.get().stime.isSome()) {
      result.set_cpus_user_time_secs(
          result.cpus_user_time_secs() + process.get().utime.get().secs());
      result.set_cpus_system_time_secs(
          result.cpus_system_time_secs() + process.get().stime.get().secs());
    }
  }

  return result;
}

void ProcessIsolator::reaped(pid_t pid, const Future<Option<int> >& status)
{
  foreachkey (const FrameworkID& frameworkId, infos) {
    foreachkey (const ExecutorID& executorId, infos[frameworkId]) {
      ProcessInfo* info = infos[frameworkId][executorId];

      if (info->pid.isSome() && info->pid.get() == pid) {
        if (!status.isReady()) {
          LOG(ERROR) << "Failed to get the status for executor '" << executorId
                     << "' of framework " << frameworkId << ": "
                     << (status.isFailed() ? status.failure() : "discarded");
          return;
        }

        LOG(INFO) << "Telling slave of terminated executor '" << executorId
                  << "' of framework " << frameworkId;

        dispatch(slave,
                 &Slave::executorTerminated,
                 frameworkId,
                 executorId,
                 status.get(),
                 false,
                 "Executor terminated");

        if (!info->killed) {
          // Try and cleanup after the executor.
          killExecutor(frameworkId, executorId);
        }

        if (infos[frameworkId].size() == 1) {
          infos.erase(frameworkId);
        } else {
          infos[frameworkId].erase(executorId);
        }
        delete info;

        return;
      }
    }
  }
}

} // namespace slave {
} // namespace internal {
} // namespace mesos {
<|endoftext|>


<commit_before>//=======================================================================
// Copyright (c) 2014-2016 Baptiste Wicht
// Distributed under the terms of the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#pragma once

namespace etl {

namespace impl {

namespace reduc {

/*!
 * \brief Pad the input matrix in the output matrix for convolution as multiplication
 * \param in The input matrix
 * \param out The output matrix
 */
template <typename F1, typename F2>
void complex_pad_3d(const F1& in, F2& out) {
    for (std::size_t outer = 0; outer < etl::dim<0>(in); ++outer) {
        auto* direct = out(outer).memory_start();
        for (std::size_t i = 0; i < etl::dim<1>(in); ++i) {
            for (std::size_t j = 0; j < etl::dim<2>(in); ++j) {
                direct[i * out.template dim<2>() + j] = in(outer, i, j);
            }
        }
    }
}

/*!
 * \brief Pad the input matrix in the output matrix for convolution as multiplication
 * \param in The input matrix
 * \param out The output matrix
 */
template <typename F1, typename F2>
void complex_pad_2d(const F1& in, F2& out) {
    auto* direct = out.memory_start();
    for (std::size_t i = 0; i < etl::dim<0>(in); ++i) {
        for (std::size_t j = 0; j < etl::dim<1>(in); ++j) {
            direct[i * out.template dim<1>() + j] = in(i, j);
        }
    }
}

/*!
 * \brief Standard implementation of a 2D 'valid' convolution C = I * K, with multiple kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void fft_conv2_valid_multi(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K = etl::dim<0>(kernels);

    const std::size_t i1 = etl::dim<0>(input);
    const std::size_t i2 = etl::dim<1>(input);

    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);

    const std::size_t v1 = i1 - k1 + 1;
    const std::size_t v2 = i2 - k2 + 1;
    const std::size_t t1 = i1 + k1 - 1;
    const std::size_t t2 = i2 + k2 - 1;
    const std::size_t b1 = (t1 - v1) / 2;
    const std::size_t b2 = (t2 - v2) / 2;

    etl::dyn_matrix<std::complex<value_t<I>>> input_padded(t1, t2);
    etl::dyn_matrix<std::complex<value_t<I>>, 3> kernels_padded(K, t1, t2);
    etl::dyn_matrix<std::complex<value_t<I>>, 3> tmp_result(K, t1, t2);

    complex_pad_2d(input, input_padded);
    complex_pad_3d(kernels, kernels_padded);

    input_padded.fft2_inplace();
    kernels_padded.fft2_many_inplace();

    for (std::size_t k = 0; k < K; ++k) {
        tmp_result(k) = input_padded >> kernels_padded(k);
    }

    tmp_result.ifft2_many_inplace();

    for (std::size_t k = 0; k < K; ++k) {
        for (std::size_t i = 0; i < v1; ++i) {
            for (std::size_t j = 0; j < v2; ++j) {
                conv(k, i, j) = tmp_result(k, i + b1, j + b2).real();
            }
        }
    }
}

/*!
 * \brief BLAS implementation of a 2D 'valid' convolution C = I * K, with multiple kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void blas_conv2_valid_multi(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K  = etl::dim<0>(kernels);
    const std::size_t v1 = etl::dim<0>(input);
    const std::size_t v2 = etl::dim<1>(input);
    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);
    const std::size_t f1 = etl::dim<1>(conv);
    const std::size_t f2 = etl::dim<2>(conv);

    auto prepared_k = force_temporary(kernels);

    for (std::size_t i = 0; i < K; ++i) {
        prepared_k(i).fflip_inplace();
    }

    etl::dyn_matrix<value_t<I>, 2> input_col(k1 * k2, (v1 - k1 + 1) * (v2 - k2 + 1));
    im2col_direct_tr(input_col, input, k1, k2);

    *mul(
        etl::reshape(prepared_k, K, k1 * k2),
        input_col,
        etl::reshape(conv, K, f1 * f2));
}

/*!
 * \brief Standard implementation of a 2D 'valid' convolution C = I * K, with multiple flipped kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void fft_conv2_valid_multi_flipped(const I& input, const K_T& kernels, C&& conv) {
    auto kernels_f = etl::force_temporary(kernels);

    for (std::size_t i = 0; i < etl::dim<0>(kernels_f); ++i) {
        kernels_f(i).fflip_inplace();
    }

    fft_conv2_valid_multi(input, kernels_f, conv);
}

/*!
 * \brief BLAS implementation of a 2D 'valid' convolution C = I * K, with multiple flipped kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void blas_conv2_valid_multi_flipped(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K  = etl::dim<0>(kernels);
    const std::size_t v1 = etl::dim<0>(input);
    const std::size_t v2 = etl::dim<1>(input);
    const std::size_t f1 = etl::dim<1>(conv);
    const std::size_t f2 = etl::dim<2>(conv);
    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);

    etl::dyn_matrix<value_t<I>, 2> input_col(k1 * k2, (v1 - k1 + 1) * (v2 - k2 + 1));
    im2col_direct_tr(input_col, input, k1, k2);

    *mul(
        etl::reshape(kernels, K, k1 * k2),
        input_col,
        etl::reshape(conv, K, f1 * f2));
}

} //end of namespace reduc
} //end of namespace impl
} //end of namespace etl
<commit_msg>Direct multiplication<commit_after>//=======================================================================
// Copyright (c) 2014-2016 Baptiste Wicht
// Distributed under the terms of the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#pragma once

namespace etl {

namespace impl {

namespace reduc {

/*!
 * \brief Pad the input matrix in the output matrix for convolution as multiplication
 * \param in The input matrix
 * \param out The output matrix
 */
template <typename F1, typename F2>
void complex_pad_3d(const F1& in, F2& out) {
    for (std::size_t outer = 0; outer < etl::dim<0>(in); ++outer) {
        auto* direct = out(outer).memory_start();
        for (std::size_t i = 0; i < etl::dim<1>(in); ++i) {
            for (std::size_t j = 0; j < etl::dim<2>(in); ++j) {
                direct[i * out.template dim<2>() + j] = in(outer, i, j);
            }
        }
    }
}

/*!
 * \brief Pad the input matrix in the output matrix for convolution as multiplication
 * \param in The input matrix
 * \param out The output matrix
 */
template <typename F1, typename F2>
void complex_pad_2d(const F1& in, F2& out) {
    auto* direct = out.memory_start();
    for (std::size_t i = 0; i < etl::dim<0>(in); ++i) {
        for (std::size_t j = 0; j < etl::dim<1>(in); ++j) {
            direct[i * out.template dim<1>() + j] = in(i, j);
        }
    }
}

/*!
 * \brief Standard implementation of a 2D 'valid' convolution C = I * K, with multiple kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void fft_conv2_valid_multi(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K = etl::dim<0>(kernels);

    const std::size_t i1 = etl::dim<0>(input);
    const std::size_t i2 = etl::dim<1>(input);

    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);

    const std::size_t v1 = i1 - k1 + 1;
    const std::size_t v2 = i2 - k2 + 1;
    const std::size_t t1 = i1 + k1 - 1;
    const std::size_t t2 = i2 + k2 - 1;
    const std::size_t b1 = (t1 - v1) / 2;
    const std::size_t b2 = (t2 - v2) / 2;

    etl::dyn_matrix<std::complex<value_t<I>>> input_padded(t1, t2);
    etl::dyn_matrix<std::complex<value_t<I>>, 3> kernels_padded(K, t1, t2);
    etl::dyn_matrix<std::complex<value_t<I>>, 3> tmp_result(K, t1, t2);

    complex_pad_2d(input, input_padded);
    complex_pad_3d(kernels, kernels_padded);

    input_padded.fft2_inplace();
    kernels_padded.fft2_many_inplace();

    for (std::size_t k = 0; k < K; ++k) {
        tmp_result(k) = input_padded >> kernels_padded(k);
    }

    tmp_result.ifft2_many_inplace();

    for (std::size_t k = 0; k < K; ++k) {
        for (std::size_t i = 0; i < v1; ++i) {
            for (std::size_t j = 0; j < v2; ++j) {
                conv(k, i, j) = tmp_result(k, i + b1, j + b2).real();
            }
        }
    }
}

/*!
 * \brief BLAS implementation of a 2D 'valid' convolution C = I * K, with multiple kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void blas_conv2_valid_multi(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K  = etl::dim<0>(kernels);
    const std::size_t v1 = etl::dim<0>(input);
    const std::size_t v2 = etl::dim<1>(input);
    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);
    const std::size_t f1 = etl::dim<1>(conv);
    const std::size_t f2 = etl::dim<2>(conv);

    auto prepared_k = force_temporary(kernels);

    for (std::size_t i = 0; i < K; ++i) {
        prepared_k(i).fflip_inplace();
    }

    etl::dyn_matrix<value_t<I>, 2> input_col(k1 * k2, (v1 - k1 + 1) * (v2 - k2 + 1));
    im2col_direct_tr(input_col, input, k1, k2);

    etl::reshape(conv, K, f1 * f2) = mul(etl::reshape(prepared_k, K, k1 * k2), input_col);
}

/*!
 * \brief Standard implementation of a 2D 'valid' convolution C = I * K, with multiple flipped kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void fft_conv2_valid_multi_flipped(const I& input, const K_T& kernels, C&& conv) {
    auto kernels_f = etl::force_temporary(kernels);

    for (std::size_t i = 0; i < etl::dim<0>(kernels_f); ++i) {
        kernels_f(i).fflip_inplace();
    }

    fft_conv2_valid_multi(input, kernels_f, conv);
}

/*!
 * \brief BLAS implementation of a 2D 'valid' convolution C = I * K, with multiple flipped kernels
 * \param input The input matrix
 * \param kernel The kernel matrix
 * \param conv The output matrix
 */
template <typename I, typename K_T, typename C>
void blas_conv2_valid_multi_flipped(const I& input, const K_T& kernels, C&& conv) {
    const std::size_t K  = etl::dim<0>(kernels);
    const std::size_t v1 = etl::dim<0>(input);
    const std::size_t v2 = etl::dim<1>(input);
    const std::size_t f1 = etl::dim<1>(conv);
    const std::size_t f2 = etl::dim<2>(conv);
    const std::size_t k1 = etl::dim<1>(kernels);
    const std::size_t k2 = etl::dim<2>(kernels);

    etl::dyn_matrix<value_t<I>, 2> input_col(k1 * k2, (v1 - k1 + 1) * (v2 - k2 + 1));
    im2col_direct_tr(input_col, input, k1, k2);

    etl::reshape(conv, K, f1 * f2) = mul(etl::reshape(kernels, K, k1 * k2), input_col);
}

} //end of namespace reduc
} //end of namespace impl
} //end of namespace etl
<|endoftext|>


<commit_before>//
// Copyright (c) 2012 Kim Walisch, <kim.walisch@gmail.com>.
// All rights reserved.
//
// This file is part of primesieve.
// Homepage: http://primesieve.googlecode.com
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above
//     copyright notice, this list of conditions and the following
//     disclaimer in the documentation and/or other materials provided
//     with the distribution.
//   * Neither the name of the author nor the names of its
//     contributors may be used to endorse or promote products derived
//     from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

#include "ParallelPrimeSieve.h"
#include "PrimeSieve.h"
#include "imath.h"
#include "config.h"

#ifdef _OPENMP
  #include <omp.h>
  #include "openmp_RAII.h"
#endif

#include <stdint.h>
#include <cstdlib>
#include <cassert>
#include <algorithm>

using namespace soe;

ParallelPrimeSieve::ParallelPrimeSieve() :
  shm_(NULL),
  numThreads_(IDEAL_NUM_THREADS)
{ }

/// API for the primesieve GUI application
void ParallelPrimeSieve::init(SharedMemory& shm) {
  setStart(shm.start);
  setStop(shm.stop);
  setSieveSize(shm.sieveSize);
  setFlags(shm.flags);
  setNumThreads(shm.threads);
  shm_ = &shm;
}

/// Get the number of logical CPU cores
int ParallelPrimeSieve::getMaxThreads() {
#ifdef _OPENMP
  return omp_get_max_threads();
#else
  return 1;
#endif
}

/// Get the current set number of threads for sieving
int ParallelPrimeSieve::getNumThreads() const {
  return (numThreads_ == IDEAL_NUM_THREADS) ? idealNumThreads() : numThreads_;
}

/// Set the number of threads for sieving
void ParallelPrimeSieve::setNumThreads(int threads) {
  numThreads_ = getInBetween(1, threads, getMaxThreads());
}

int ParallelPrimeSieve::idealNumThreads() const {
  // use 1 thread to generate primes in arithmetic order
  if (isGenerate()) return 1;
  // each thread sieves at least an interval of size sqrt(x) / 5
  // but not smaller than MIN_THREAD_INTERVAL
  uint64_t threshold = std::max(config::MIN_THREAD_INTERVAL, isqrt(stop_) / 5);
  uint64_t threads = getInterval() / threshold;
  threads = getInBetween<uint64_t>(1, threads, getMaxThreads());
  return static_cast<int>(threads);
}

/// Get an interval size that ensures a good load balance
uint64_t ParallelPrimeSieve::getThreadInterval(int threads) const {
  assert(threads > 0);
  uint64_t unbalanced = getInterval() / threads;
  uint64_t balanced = isqrt(stop_) * 1000;
  uint64_t fastest = std::min(balanced, unbalanced);
  uint64_t threadInterval = getInBetween(config::MIN_THREAD_INTERVAL, fastest, config::MAX_THREAD_INTERVAL);
  uint64_t chunks = getInterval() / threadInterval;
  if (chunks < threads * 5u)
    threadInterval = std::max(config::MIN_THREAD_INTERVAL, unbalanced);
  // align to modulo 30 to prevent prime k-tuplet gaps
  threadInterval += 30 - threadInterval % 30;
  return threadInterval;
}

#ifdef _OPENMP

/// Used to synchronize threads for prime number generation

void ParallelPrimeSieve::setLock() {
  omp_set_lock(&lock_);
}

void ParallelPrimeSieve::unsetLock() {
  omp_unset_lock(&lock_);
}

/// Calculate the sieving status (in percent).
/// @param processed  Sum of recently processed segments.
///
bool ParallelPrimeSieve::updateStatus(uint64_t processed, bool waitForLock) {
  OmpGuard lock(lock_, waitForLock);
  if (lock.isSet()) {
    PrimeSieve::updateStatus(processed, false);
    if (shm_ != NULL)
      shm_->status = getStatus();
  }
  return lock.isSet();
}

bool ParallelPrimeSieve::tooMany(int threads) const {
  return (threads > 1 && getInterval() / threads < config::MIN_THREAD_INTERVAL);
}

/// Sieve the primes and prime k-tuplets within [start, stop]
/// in parallel using OpenMP (version 3.0 or later).
///
void ParallelPrimeSieve::sieve() {
  if (start_ > stop_)
    throw primesieve_error("start must be <= stop");

  int threads = getNumThreads();
  if (tooMany(threads)) threads = idealNumThreads();
  OmpInitGuard init(lock_);

  if (threads == 1)
    PrimeSieve::sieve();
  else {
    double t1 = omp_get_wtime();
    reset();
    uint64_t count0 = 0, count1 = 0, count2 = 0, count3 = 0, count4 = 0, count5 = 0, count6 = 0;
    uint64_t align = start_ + 32 - start_ % 30;
    uint64_t threadInterval = getThreadInterval(threads);
    // The sieve interval [start_, stop_] is subdivided into chunks of
    // size 'threadInterval' that are sieved in parallel using
    // multiple threads. This scales well as each thread sieves using
    // its own private memory without need of synchronization.
    #pragma omp parallel for schedule(dynamic) num_threads(threads) \
        reduction(+: count0, count1, count2, count3, count4, count5, count6)
    for (uint64_t n = align; n < stop_; n += threadInterval) {
      uint64_t threadStart = (n == align) ? start_ : n;
      uint64_t threadStop = std::min(n + threadInterval, stop_);
      PrimeSieve ps(*this, omp_get_thread_num());
      ps.sieve(threadStart, threadStop);
      count0 += ps.getCounts(0);
      count1 += ps.getCounts(1);
      count2 += ps.getCounts(2);
      count3 += ps.getCounts(3);
      count4 += ps.getCounts(4);
      count5 += ps.getCounts(5);
      count6 += ps.getCounts(6);
    }
    counts_[0] = count0;
    counts_[1] = count1;
    counts_[2] = count2;
    counts_[3] = count3;
    counts_[4] = count4;
    counts_[5] = count5;
    counts_[6] = count6;
    seconds_ = omp_get_wtime() - t1;
  }

  // communicate the sieving results to the
  // primesieve GUI application
  if (shm_ != NULL) {
    std::copy(counts_.begin(), counts_.end(), shm_->counts);
    shm_->seconds = seconds_;
  }
}

#endif
<commit_msg>updated<commit_after>//
// Copyright (c) 2012 Kim Walisch, <kim.walisch@gmail.com>.
// All rights reserved.
//
// This file is part of primesieve.
// Homepage: http://primesieve.googlecode.com
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above
//     copyright notice, this list of conditions and the following
//     disclaimer in the documentation and/or other materials provided
//     with the distribution.
//   * Neither the name of the author nor the names of its
//     contributors may be used to endorse or promote products derived
//     from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

#include "ParallelPrimeSieve.h"
#include "PrimeSieve.h"
#include "imath.h"
#include "config.h"

#ifdef _OPENMP
  #include <omp.h>
  #include "openmp_RAII.h"
#endif

#include <stdint.h>
#include <cstdlib>
#include <cassert>
#include <algorithm>

using namespace soe;

ParallelPrimeSieve::ParallelPrimeSieve() :
  shm_(NULL),
  numThreads_(IDEAL_NUM_THREADS)
{ }

/// API for the primesieve GUI application
void ParallelPrimeSieve::init(SharedMemory& shm) {
  setStart(shm.start);
  setStop(shm.stop);
  setSieveSize(shm.sieveSize);
  setFlags(shm.flags);
  setNumThreads(shm.threads);
  shm_ = &shm;
}

/// Get the number of CPU cores
int ParallelPrimeSieve::getMaxThreads() {
#ifdef _OPENMP
  return omp_get_max_threads();
#else
  return 1;
#endif
}

/// Get the current set number of threads for sieving
int ParallelPrimeSieve::getNumThreads() const {
  return (numThreads_ == IDEAL_NUM_THREADS) ? idealNumThreads() : numThreads_;
}

/// Set the number of threads for sieving
void ParallelPrimeSieve::setNumThreads(int threads) {
  numThreads_ = getInBetween(1, threads, getMaxThreads());
}

int ParallelPrimeSieve::idealNumThreads() const {
  // use 1 thread to generate primes in arithmetic order
  if (isGenerate()) return 1;
  // each thread sieves at least an interval of size sqrt(x) / 5
  // but not smaller than MIN_THREAD_INTERVAL
  uint64_t threshold = std::max(config::MIN_THREAD_INTERVAL, isqrt(stop_) / 5);
  uint64_t threads = getInterval() / threshold;
  threads = getInBetween<uint64_t>(1, threads, getMaxThreads());
  return static_cast<int>(threads);
}

/// Get an interval size that ensures a good load balance
uint64_t ParallelPrimeSieve::getThreadInterval(int threads) const {
  assert(threads > 0);
  uint64_t unbalanced = getInterval() / threads;
  uint64_t balanced = isqrt(stop_) * 1000;
  uint64_t fastest = std::min(balanced, unbalanced);
  uint64_t threadInterval = getInBetween(config::MIN_THREAD_INTERVAL, fastest, config::MAX_THREAD_INTERVAL);
  uint64_t chunks = getInterval() / threadInterval;
  if (chunks < threads * 5u)
    threadInterval = std::max(config::MIN_THREAD_INTERVAL, unbalanced);
  // align to modulo 30 to prevent prime k-tuplet gaps
  threadInterval += 30 - threadInterval % 30;
  return threadInterval;
}

#ifdef _OPENMP

/// Used to synchronize threads for prime number generation

void ParallelPrimeSieve::setLock() {
  omp_set_lock(&lock_);
}

void ParallelPrimeSieve::unsetLock() {
  omp_unset_lock(&lock_);
}

/// Calculate the sieving status (in percent).
/// @param processed  Sum of recently processed segments.
///
bool ParallelPrimeSieve::updateStatus(uint64_t processed, bool waitForLock) {
  OmpGuard lock(lock_, waitForLock);
  if (lock.isSet()) {
    PrimeSieve::updateStatus(processed, false);
    if (shm_ != NULL)
      shm_->status = getStatus();
  }
  return lock.isSet();
}

bool ParallelPrimeSieve::tooMany(int threads) const {
  return (threads > 1 && getInterval() / threads < config::MIN_THREAD_INTERVAL);
}

/// Sieve the primes and prime k-tuplets within [start, stop]
/// in parallel using OpenMP (version 3.0 or later).
///
void ParallelPrimeSieve::sieve() {
  if (start_ > stop_)
    throw primesieve_error("start must be <= stop");

  int threads = getNumThreads();
  if (tooMany(threads)) threads = idealNumThreads();
  OmpInitGuard init(lock_);

  if (threads == 1)
    PrimeSieve::sieve();
  else {
    double t1 = omp_get_wtime();
    reset();
    uint64_t count0 = 0, count1 = 0, count2 = 0, count3 = 0, count4 = 0, count5 = 0, count6 = 0;
    uint64_t align = start_ + 32 - start_ % 30;
    uint64_t threadInterval = getThreadInterval(threads);
    // The sieve interval [start_, stop_] is subdivided into chunks of
    // size 'threadInterval' that are sieved in parallel using
    // multiple threads. This scales well as each thread sieves using
    // its own private memory without need of synchronization.
    #pragma omp parallel for schedule(dynamic) num_threads(threads) \
        reduction(+: count0, count1, count2, count3, count4, count5, count6)
    for (uint64_t n = align; n < stop_; n += threadInterval) {
      uint64_t threadStart = (n == align) ? start_ : n;
      uint64_t threadStop = std::min(n + threadInterval, stop_);
      PrimeSieve ps(*this, omp_get_thread_num());
      ps.sieve(threadStart, threadStop);
      count0 += ps.getCounts(0);
      count1 += ps.getCounts(1);
      count2 += ps.getCounts(2);
      count3 += ps.getCounts(3);
      count4 += ps.getCounts(4);
      count5 += ps.getCounts(5);
      count6 += ps.getCounts(6);
    }
    counts_[0] = count0;
    counts_[1] = count1;
    counts_[2] = count2;
    counts_[3] = count3;
    counts_[4] = count4;
    counts_[5] = count5;
    counts_[6] = count6;
    seconds_ = omp_get_wtime() - t1;
  }

  // communicate the sieving results to the
  // primesieve GUI application
  if (shm_ != NULL) {
    std::copy(counts_.begin(), counts_.end(), shm_->counts);
    shm_->seconds = seconds_;
  }
}

#endif
<|endoftext|>