<commit_before>/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2019 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <vector>

#include "caf/behavior.hpp"
#include "caf/config.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/typed_actor_util.hpp"
#include "caf/error.hpp"
#include "caf/message_id.hpp"

namespace caf {
namespace detail {

template <class F, class T>
struct fan_in_responses_helper {
  std::vector<T> results;
  std::shared_ptr<size_t> pending;
  F f;

  void operator()(T& x) {
    if (*pending > 0) {
      results.emplace_back(std::move(x));
      if (--*pending == 0)
        f(std::move(results));
    }
  }

  template <class Fun>
  fan_in_responses_helper(size_t pending, Fun&& f)
    : pending(std::make_shared<size_t>(pending)), f(std::forward<Fun>(f)) {
    results.reserve(pending);
  }

  // TODO: return 'auto' from this function when switching to C++17
  std::function<void(T&)> wrap() {
    return [this](T& x) { (*this)(x); };
  }
};

template <class F, class... Ts>
struct fan_in_responses_tuple_helper {
  using value_type = std::tuple<Ts...>;
  std::vector<value_type> results;
  std::shared_ptr<size_t> pending;
  F f;

  void operator()(Ts&... xs) {
    if (*pending > 0) {
      results.emplace_back(std::move(xs)...);
      if (--*pending == 0)
        f(std::move(results));
    }
  }

  template <class Fun>
  fan_in_responses_tuple_helper(size_t pending, Fun&& f)
    : pending(std::make_shared<size_t>(pending)), f(std::forward<Fun>(f)) {
    results.reserve(pending);
  }

  // TODO: return 'auto' from this function when switching to C++17
  std::function<void(Ts&...)> wrap() {
    return [this](Ts&... xs) { (*this)(xs...); };
  }
};

template <class F, class = typename detail::get_callable_trait<F>::arg_types>
struct select_fan_in_responses_helper;

template <class F, class... Ts>
struct select_fan_in_responses_helper<
  F, detail::type_list<std::vector<std::tuple<Ts...>>>> {
  using type = fan_in_responses_tuple_helper<F, Ts...>;
};

template <class F, class T>
struct select_fan_in_responses_helper<F, detail::type_list<std::vector<T>>> {
  using type = fan_in_responses_helper<F, T>;
};

template <class F>
using fan_in_responses_helper_t =
  typename select_fan_in_responses_helper<F>::type;

// TODO: Replace with a lambda when switching to C++17 (move g into lambda).
template <class G>
class fan_in_responses_error_handler {
public:
  template <class Fun>
  fan_in_responses_error_handler(Fun&& fun, std::shared_ptr<size_t> pending)
    : handler(std::forward<Fun>(fun)), pending(std::move(pending)) {
    // nop
  }

  void operator()(error& err) {
    if (*pending > 0) {
      *pending = 0;
      handler(err);
    }
  }

private:
  G handler;
  std::shared_ptr<size_t> pending;
};

} // namespace detail
} // namespace caf

namespace caf {
namespace policy {

/// Enables a `response_handle` to fan-in multiple responses into a single
/// result (a `vector` of Individual values) for the client.
/// @relates mixin::requester
/// @relates response_handle
template <class ResponseType>
class fan_in_responses {
public:
  fan_in_responses(std::vector<message_id> ids) : ids_(std::move(ids)) {
    CAF_ASSERT(ids_.size()
               <= static_cast<size_t>(std::numeric_limits<int>::max()));
  }

  template <class Self, class F, class OnError>
  void await(Self* self, F&& f, OnError&& g) {
    auto bhvr = make_behavior(std::forward<F>(f), std::forward<OnError>(g));
    for (auto id : ids_)
      self->add_awaited_response_handler(id, bhvr);
  }

  template <class Self, class F, class OnError>
  void then(Self* self, F&& f, OnError&& g) {
    auto bhvr = make_behavior(std::forward<F>(f), std::forward<OnError>(g));
    for (auto id : ids_)
      self->add_multiplexed_response_handler(id, bhvr);
  }

  template <class Self, class F, class OnError>
  void receive(Self* self, F&& f, OnError&& g) {
    using helper_type = detail::fan_in_responses_helper_t<detail::decay_t<F>>;
    helper_type helper{ids_.size(), std::forward<F>(f)};
    detail::type_checker<ResponseType, helper_type>::check();
    auto error_handler = [&](error& err) {
      if (*helper.pending > 0) {
        *helper.pending = 0;
        helper.results.clear();
        g(err);
      }
    };
    auto wrapped_helper = helper.wrap();
    for (auto id : ids_) {
      typename Self::accept_one_cond rc;
      self->varargs_receive(rc, id, wrapped_helper, error_handler);
    }
  }

private:
  template <class F, class OnError>
  behavior make_behavior(F&& f, OnError&& g) {
    using namespace detail;
    using helper_type = fan_in_responses_helper_t<decay_t<F>>;
    using error_handler_type = fan_in_responses_error_handler<decay_t<OnError>>;
    helper_type helper{ids_.size(), std::move(f)};
    type_checker<ResponseType, helper_type>::check();
    error_handler_type err_helper{std::forward<OnError>(g), helper.pending};
    return {
      std::move(helper),
      std::move(err_helper),
    };
  }

  std::vector<message_id> ids_;
};

} // namespace policy
} // namespace caf
<commit_msg>Make member functions const, add type aliases<commit_after>/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2019 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <vector>

#include "caf/behavior.hpp"
#include "caf/config.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/detail/typed_actor_util.hpp"
#include "caf/error.hpp"
#include "caf/message_id.hpp"

namespace caf {
namespace detail {

template <class F, class T>
struct fan_in_responses_helper {
  std::vector<T> results;
  std::shared_ptr<size_t> pending;
  F f;

  void operator()(T& x) {
    if (*pending > 0) {
      results.emplace_back(std::move(x));
      if (--*pending == 0)
        f(std::move(results));
    }
  }

  template <class Fun>
  fan_in_responses_helper(size_t pending, Fun&& f)
    : pending(std::make_shared<size_t>(pending)), f(std::forward<Fun>(f)) {
    results.reserve(pending);
  }

  // TODO: return 'auto' from this function when switching to C++17
  std::function<void(T&)> wrap() {
    return [this](T& x) { (*this)(x); };
  }
};

template <class F, class... Ts>
struct fan_in_responses_tuple_helper {
  using value_type = std::tuple<Ts...>;
  std::vector<value_type> results;
  std::shared_ptr<size_t> pending;
  F f;

  void operator()(Ts&... xs) {
    if (*pending > 0) {
      results.emplace_back(std::move(xs)...);
      if (--*pending == 0)
        f(std::move(results));
    }
  }

  template <class Fun>
  fan_in_responses_tuple_helper(size_t pending, Fun&& f)
    : pending(std::make_shared<size_t>(pending)), f(std::forward<Fun>(f)) {
    results.reserve(pending);
  }

  // TODO: return 'auto' from this function when switching to C++17
  std::function<void(Ts&...)> wrap() {
    return [this](Ts&... xs) { (*this)(xs...); };
  }
};

template <class F, class = typename detail::get_callable_trait<F>::arg_types>
struct select_fan_in_responses_helper;

template <class F, class... Ts>
struct select_fan_in_responses_helper<
  F, detail::type_list<std::vector<std::tuple<Ts...>>>> {
  using type = fan_in_responses_tuple_helper<F, Ts...>;
};

template <class F, class T>
struct select_fan_in_responses_helper<F, detail::type_list<std::vector<T>>> {
  using type = fan_in_responses_helper<F, T>;
};

template <class F>
using fan_in_responses_helper_t =
  typename select_fan_in_responses_helper<F>::type;

// TODO: Replace with a lambda when switching to C++17 (move g into lambda).
template <class G>
class fan_in_responses_error_handler {
public:
  template <class Fun>
  fan_in_responses_error_handler(Fun&& fun, std::shared_ptr<size_t> pending)
    : handler(std::forward<Fun>(fun)), pending(std::move(pending)) {
    // nop
  }

  void operator()(error& err) {
    if (*pending > 0) {
      *pending = 0;
      handler(err);
    }
  }

private:
  G handler;
  std::shared_ptr<size_t> pending;
};

} // namespace detail
} // namespace caf

namespace caf {
namespace policy {

/// Enables a `response_handle` to fan-in multiple responses into a single
/// result (a `vector` of individual values) for the client.
/// @relates mixin::requester
/// @relates response_handle
template <class ResponseType>
class fan_in_responses {
public:
  static constexpr bool is_trivial = false;

  using response_type = ResponseType;

  using message_id_list = std::vector<message_id>;

  explicit fan_in_responses(message_id_list ids) : ids_(std::move(ids)) {
    CAF_ASSERT(ids_.size()
               <= static_cast<size_t>(std::numeric_limits<int>::max()));
  }

  fan_in_responses(fan_in_responses&&) noexcept = default;

  fan_in_responses& operator=(fan_in_responses&&) noexcept = default;

  template <class Self, class F, class OnError>
  void await(Self* self, F&& f, OnError&& g) const {
    auto bhvr = make_behavior(std::forward<F>(f), std::forward<OnError>(g));
    for (auto id : ids_)
      self->add_awaited_response_handler(id, bhvr);
  }

  template <class Self, class F, class OnError>
  void then(Self* self, F&& f, OnError&& g) const {
    auto bhvr = make_behavior(std::forward<F>(f), std::forward<OnError>(g));
    for (auto id : ids_)
      self->add_multiplexed_response_handler(id, bhvr);
  }

  template <class Self, class F, class G>
  void receive(Self* self, F&& f, G&& g) const {
    using helper_type = detail::fan_in_responses_helper_t<detail::decay_t<F>>;
    helper_type helper{ids_.size(), std::forward<F>(f)};
    detail::type_checker<ResponseType, helper_type>::check();
    auto error_handler = [&](error& err) {
      if (*helper.pending > 0) {
        *helper.pending = 0;
        helper.results.clear();
        g(err);
      }
    };
    auto wrapped_helper = helper.wrap();
    for (auto id : ids_) {
      typename Self::accept_one_cond rc;
      self->varargs_receive(rc, id, wrapped_helper, error_handler);
    }
  }

  const message_id_list& ids() const noexcept {
    return ids_;
  }

private:
  template <class F, class OnError>
  behavior make_behavior(F&& f, OnError&& g) const {
    using namespace detail;
    using helper_type = fan_in_responses_helper_t<decay_t<F>>;
    using error_handler_type = fan_in_responses_error_handler<decay_t<OnError>>;
    helper_type helper{ids_.size(), std::move(f)};
    type_checker<ResponseType, helper_type>::check();
    error_handler_type err_helper{std::forward<OnError>(g), helper.pending};
    return {
      std::move(helper),
      std::move(err_helper),
    };
  }

  message_id_list ids_;
};

} // namespace policy
} // namespace caf
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#include "logjoin/SessionJoin.h"
#include "logjoin/common.h"

using namespace stx;

namespace cm {

void SessionJoin::process(RefPtr<TrackedSessionContext> ctx) {

  /* load builtin events into structured format */
  std::vector<TrackedQuery> queries;
  std::vector<TrackedItemVisit> page_views;
  std::vector<TrackedCartItem> cart_items;

  for (const auto& ev : ctx->tracked_session.events) {
    if (ev.evtype == "_search_query") {
      processSearchQueryEvent(ev, &queries);
      continue;
    }

    if (ev.evtype == "_pageview") {
      processPageViewEvent(ev, &page_views);
      continue;
    }

    if (ev.evtype == "_cart_items") {
      processCartItemsEvent(ev, &cart_items);
      continue;
    }
  }

  /* update queries (mark items as clicked) */
  for (auto& cur_query : queries) {

    /* search for matching item visits */
    for (auto& cur_visit : page_views) {
      auto cutoff = cur_query.time.unixMicros() +
          kMaxQueryClickDelaySeconds * kMicrosPerSecond;

      if (cur_visit.time < cur_query.time ||
          cur_visit.time.unixMicros() > cutoff) {
        continue;
      }

      for (auto& qitem : cur_query.items) {
        if (cur_visit.item == qitem.item) {
          qitem.clicked = true;
          qitem.seen = true;
          break;
        }
      }
    }
  }

  /* calculate global gmv */
  uint32_t num_cart_items = 0;
  uint32_t num_order_items = 0;
  uint32_t gmv_eurcents = 0;
  uint32_t cart_value_eurcents = 0;
  HashMap<String, uint64_t> cart_eurcents_per_item;
  HashMap<String, uint64_t> gmv_eurcents_per_item;
  for (const auto& ci : cart_items) {
    auto currency = currencyFromString(ci.currency);
    auto eur = cconv()->convert(Money(ci.price_cents, currency), CURRENCY_EUR);
    auto eurcents = eur.cents;
    eurcents *= ci.quantity;
    cart_eurcents_per_item.emplace(ci.item.docID().docid, eurcents);

    ++num_cart_items;
    cart_value_eurcents += eurcents;
    if (ci.checkout_step == 1) {
      gmv_eurcents_per_item.emplace(ci.item.docID().docid, eurcents);
      ++num_order_items;
      gmv_eurcents += eurcents;
    }
  }

  /* calculate gmv and ctrs per query */
  for (auto& q : queries) {
    auto slrid = extractAttr(q.attrs, "slrid");

    for (auto& i : q.items) {
      // DAWANDA HACK
      if (i.position >= 1 && i.position <= 4 && slrid.isEmpty()) {
        ++q.nads;
        q.nadclicks += i.clicked;
      }
      // EOF DAWANDA HACK

      ++q.nitems;

      if (i.clicked) {
        ++q.nclicks;

        {
          auto ci = cart_eurcents_per_item.find(i.item.docID().docid);
          if (ci != cart_eurcents_per_item.end()) {
            ++q.num_cart_items;
            q.cart_value_eurcents += ci->second;
          }
        }

        {
          auto ci = gmv_eurcents_per_item.find(i.item.docID().docid);
          if (ci != gmv_eurcents_per_item.end()) {
            ++q.num_order_items;
            q.gmv_eurcents += ci->second;
          }
        }
      }
    }
  }

  for (const auto& q : queries) {
    auto qobj = ctx->joined_session.add_search_queries();

    qobj->set_time(q.time.unixMicros() / kMicrosPerSecond);
    qobj->set_language((ProtoLanguage) cm::extractLanguage(q.attrs));

    auto qstr = cm::extractQueryString(q.attrs);
    if (!qstr.isEmpty()) {
      qobj->set_query_string(qstr.get());
    }

    auto slrid = cm::extractAttr(q.attrs, "slrid");
    if (!slrid.isEmpty()) {
      try {
        uint32_t sid = std::stoul(slrid.get());
        qobj->set_shop_id(sid);
      } catch (...) {}
    }

    qobj->set_num_result_items(q.nitems);
    qobj->set_num_result_items_clicked(q.nclicks);
    qobj->set_num_ad_impressions(q.nads);
    qobj->set_num_ad_clicks(q.nadclicks);
    qobj->set_num_cart_items(q.num_cart_items);
    qobj->set_cart_value_eurcents(q.cart_value_eurcents);
    qobj->set_num_order_items(q.num_order_items);
    qobj->set_gmv_eurcents(q.gmv_eurcents);

    auto pg_str = cm::extractAttr(q.attrs, "pg");
    if (!pg_str.isEmpty()) {
      try {
        auto val = std::stoull(pg_str.get());
        qobj->set_page(val);
      } catch (...) {}
    }

    auto abgrp = cm::extractABTestGroup(q.attrs);
    if (!abgrp.isEmpty()) {
      qobj->set_ab_test_group(abgrp.get());
    }

    auto qcat1 = cm::extractAttr(q.attrs, "q_cat1");
    if (!qcat1.isEmpty()) {
      try {
        auto val = std::stoull(qcat1.get());
        qobj->set_category1(val);
      } catch (...) {}
    }

    auto qcat2 = cm::extractAttr(q.attrs, "q_cat2");
    if (!qcat2.isEmpty()) {
      try {
        auto val = std::stoull(qcat2.get());
        qobj->set_category2(val);
      } catch (...) {}
    }

    auto qcat3 = cm::extractAttr(q.attrs, "q_cat3");
    if (!qcat3.isEmpty()) {
      try {
        auto val = std::stoull(qcat3.get());
        qobj->set_category3(val);
      } catch (...) {}
    }

    qobj->set_device_type((ProtoDeviceType) extractDeviceType(q.attrs));
    qobj->set_page_type((ProtoPageType) extractPageType(q.attrs));

    String query_type = pageTypeToString((PageType) qobj->page_type());
    auto qtype_attr = cm::extractAttr(q.attrs, "qt");
    if (!qtype_attr.isEmpty()) {
      query_type = qtype_attr.get();
    }

    qobj->set_query_type(query_type);

    for (const auto& item : q.items) {
      auto item_obj = qobj->add_result_items();

      item_obj->set_position(item.position);
      item_obj->set_item_id(item.item.docID().docid);
      item_obj->set_clicked(item.clicked);
      item_obj->set_seen(item.seen);
    }
  }
}

void SessionJoin::processSearchQueryEvent(
    const TrackedEvent& event,
    Vector<TrackedQuery>* queries) {
  TrackedQuery query;
  query.time = event.time;
  query.eid = event.evid;

  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);
  query.fromParams(logline);

  for (auto& q : *queries) {
    if (q.eid == query.eid) {
      q.merge(query);
      return;
    }
  }

  queries->emplace_back(query);
}

void SessionJoin::processPageViewEvent(
    const TrackedEvent& event,
    Vector<TrackedItemVisit>* page_views) {
  TrackedItemVisit visit;
  visit.time = event.time;
  visit.eid = event.evid;

  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);
  visit.fromParams(logline);

  for (auto& v : *page_views) {
    if (v.eid == visit.eid) {
      v.merge(visit);
      return;
    }
  }

  page_views->emplace_back(visit);
}

void SessionJoin::processCartItemsEvent(
    const TrackedEvent& event,
    Vector<TrackedCartItem>* cart_items) {
  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);

  auto new_cart_items = TrackedCartItem::fromParams(logline);
  for (auto& ci : new_cart_items) {
    ci.time = event.time;
  }

  for (const auto& cart_item : new_cart_items) {
    bool merged = false;

    for (auto& c : *cart_items) {
      if (c.item == cart_item.item) {
        c.merge(cart_item);
        merged = true;
        break;
      }
    }

    if (!merged) {
      cart_items->emplace_back(cart_item);
    }
  }
}

} // namespace cm

<commit_msg>session_tracking.md stub<commit_after>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#include "logjoin/SessionJoin.h"
#include "logjoin/common.h"

using namespace stx;

namespace cm {

void SessionJoin::process(RefPtr<TrackedSessionContext> ctx) {

  /* load builtin events into structured format */
  std::vector<TrackedQuery> queries;
  std::vector<TrackedItemVisit> page_views;
  std::vector<TrackedCartItem> cart_items;

  for (const auto& ev : ctx->tracked_session.events) {
    if (ev.evtype == "_search_query") {
      processSearchQueryEvent(ev, &queries);
      continue;
    }

    if (ev.evtype == "_pageview") {
      processPageViewEvent(ev, &page_views);
      continue;
    }

    if (ev.evtype == "_cart_items") {
      processCartItemsEvent(ev, &cart_items);
      continue;
    }
  }

  /* update queries (mark items as clicked) */
  for (auto& cur_query : queries) {

    /* search for matching item visits */
    for (auto& cur_visit : page_views) {
      auto cutoff = cur_query.time.unixMicros() +
          kMaxQueryClickDelaySeconds * kMicrosPerSecond;

      if (cur_visit.time < cur_query.time ||
          cur_visit.time.unixMicros() > cutoff) {
        continue;
      }

      for (auto& qitem : cur_query.items) {
        if (cur_visit.item == qitem.item) {
          qitem.clicked = true;
          qitem.seen = true;
          break;
        }
      }
    }
  }

  /* calculate global gmv */
  uint32_t num_cart_items = 0;
  uint32_t num_order_items = 0;
  uint32_t gmv_eurcents = 0;
  uint32_t cart_value_eurcents = 0;
  HashMap<String, uint64_t> cart_eurcents_per_item;
  HashMap<String, uint64_t> gmv_eurcents_per_item;
  for (const auto& ci : cart_items) {
    auto currency = currencyFromString(ci.currency);
    auto eur = cconv()->convert(Money(ci.price_cents, currency), CURRENCY_EUR);
    auto eurcents = eur.cents;
    eurcents *= ci.quantity;
    cart_eurcents_per_item.emplace(ci.item.docID().docid, eurcents);

    ++num_cart_items;
    cart_value_eurcents += eurcents;
    if (ci.checkout_step == 1) {
      gmv_eurcents_per_item.emplace(ci.item.docID().docid, eurcents);
      ++num_order_items;
      gmv_eurcents += eurcents;
    }
  }

  /* calculate gmv and ctrs per query */
  for (auto& q : queries) {
    auto slrid = extractAttr(q.attrs, "slrid");

    for (auto& i : q.items) {
      // DAWANDA HACK
      if (i.position >= 1 && i.position <= 4 && slrid.isEmpty()) {
        ++q.nads;
        q.nadclicks += i.clicked;
      }
      // EOF DAWANDA HACK

      ++q.nitems;

      if (i.clicked) {
        ++q.nclicks;

        {
          auto ci = cart_eurcents_per_item.find(i.item.docID().docid);
          if (ci != cart_eurcents_per_item.end()) {
            ++q.num_cart_items;
            q.cart_value_eurcents += ci->second;
          }
        }

        {
          auto ci = gmv_eurcents_per_item.find(i.item.docID().docid);
          if (ci != gmv_eurcents_per_item.end()) {
            ++q.num_order_items;
            q.gmv_eurcents += ci->second;
          }
        }
      }
    }
  }

  for (const auto& q : queries) {
    auto qobj = ctx->joined_session.add_search_queries();

    qobj->set_time(q.time.unixMicros() / kMicrosPerSecond);
    qobj->set_language((ProtoLanguage) cm::extractLanguage(q.attrs));

    auto qstr = cm::extractQueryString(q.attrs);
    if (!qstr.isEmpty()) {
      qobj->set_query_string(qstr.get());
    }

    auto slrid = cm::extractAttr(q.attrs, "slrid");
    if (!slrid.isEmpty()) {
      try {
        uint32_t sid = std::stoul(slrid.get());
        qobj->set_shop_id(sid);
      } catch (...) {}
    }

    qobj->set_num_result_items(q.nitems);
    qobj->set_num_result_items_clicked(q.nclicks);
    qobj->set_num_ad_impressions(q.nads);
    qobj->set_num_ad_clicks(q.nadclicks);
    qobj->set_num_cart_items(q.num_cart_items);
    qobj->set_cart_value_eurcents(q.cart_value_eurcents);
    qobj->set_num_order_items(q.num_order_items);
    qobj->set_gmv_eurcents(q.gmv_eurcents);

    auto pg_str = cm::extractAttr(q.attrs, "pg");
    if (!pg_str.isEmpty()) {
      try {
        auto val = std::stoull(pg_str.get());
        qobj->set_page(val);
      } catch (...) {}
    }

    auto abgrp = cm::extractABTestGroup(q.attrs);
    if (!abgrp.isEmpty()) {
      qobj->set_ab_test_group(abgrp.get());
    }

    auto qcat1 = cm::extractAttr(q.attrs, "q_cat1");
    if (!qcat1.isEmpty()) {
      try {
        auto val = std::stoull(qcat1.get());
        qobj->set_category1(val);
      } catch (...) {}
    }

    auto qcat2 = cm::extractAttr(q.attrs, "q_cat2");
    if (!qcat2.isEmpty()) {
      try {
        auto val = std::stoull(qcat2.get());
        qobj->set_category2(val);
      } catch (...) {}
    }

    auto qcat3 = cm::extractAttr(q.attrs, "q_cat3");
    if (!qcat3.isEmpty()) {
      try {
        auto val = std::stoull(qcat3.get());
        qobj->set_category3(val);
      } catch (...) {}
    }

    qobj->set_device_type((ProtoDeviceType) extractDeviceType(q.attrs));
    qobj->set_page_type((ProtoPageType) extractPageType(q.attrs));

    String query_type = pageTypeToString((PageType) qobj->page_type());
    auto qtype_attr = cm::extractAttr(q.attrs, "qt");
    if (!qtype_attr.isEmpty()) {
      query_type = qtype_attr.get();
    }

    qobj->set_query_type(query_type);

    for (const auto& item : q.items) {
      auto item_obj = qobj->add_result_items();

      item_obj->set_position(item.position);
      item_obj->set_item_id(item.item.docID().docid);
      item_obj->set_clicked(item.clicked);
      item_obj->set_seen(item.seen);
    }
  }

  //if (!session.referrer_url.isEmpty()) {
  //  //obj.addChild(session_schema->fieldId("referrer_url"), session.referrer_url.get());
  //}

  //if (!session.referrer_campaign.isEmpty()) {
  //  obj.addChild(session_schema->fieldId("referrer_campaign"), session.referrer_campaign.get());
  //}

  //if (!session.referrer_name.isEmpty()) {
  //  obj.addChild(session_schema->fieldId("referrer_name"), session.referrer_name.get());
  //}

  //if (!session.customer_session_id.isEmpty()) {
  //  obj.addChild(session_schema->fieldId("customer_session_id"), session.customer_session_id.get());
  //}

  //obj.addChild(session_schema->fieldId("num_cart_items"), session.num_cart_items);
  //obj.addChild(session_schema->fieldId("cart_value_eurcents"), session.cart_value_eurcents);
  //obj.addChild(session_schema->fieldId("num_order_items"), session.num_order_items);
  //obj.addChild(session_schema->fieldId("gmv_eurcents"), session.gmv_eurcents);
  //obj.addChild(session_schema->fieldId("customer"), session.customer_key);

}

void SessionJoin::processSearchQueryEvent(
    const TrackedEvent& event,
    Vector<TrackedQuery>* queries) {
  TrackedQuery query;
  query.time = event.time;
  query.eid = event.evid;

  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);
  query.fromParams(logline);

  for (auto& q : *queries) {
    if (q.eid == query.eid) {
      q.merge(query);
      return;
    }
  }

  queries->emplace_back(query);
}

void SessionJoin::processPageViewEvent(
    const TrackedEvent& event,
    Vector<TrackedItemVisit>* page_views) {
  TrackedItemVisit visit;
  visit.time = event.time;
  visit.eid = event.evid;

  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);
  visit.fromParams(logline);

  for (auto& v : *page_views) {
    if (v.eid == visit.eid) {
      v.merge(visit);
      return;
    }
  }

  page_views->emplace_back(visit);
}

void SessionJoin::processCartItemsEvent(
    const TrackedEvent& event,
    Vector<TrackedCartItem>* cart_items) {
  URI::ParamList logline;
  URI::parseQueryString(event.data, &logline);

  auto new_cart_items = TrackedCartItem::fromParams(logline);
  for (auto& ci : new_cart_items) {
    ci.time = event.time;
  }

  for (const auto& cart_item : new_cart_items) {
    bool merged = false;

    for (auto& c : *cart_items) {
      if (c.item == cart_item.item) {
        c.merge(cart_item);
        merged = true;
        break;
      }
    }

    if (!merged) {
      cart_items->emplace_back(cart_item);
    }
  }
}

} // namespace cm

<|endoftext|>


<commit_before>#include <bts/blockchain/balance_operations.hpp>
#include <bts/blockchain/chain_interface.hpp>
#include <bts/blockchain/exceptions.hpp>

namespace bts { namespace blockchain {

   asset   balance_record::calculate_rewards( fc::time_point_sec now, share_type amount, share_type rewards_pool, share_type share_supply )const
   {
      auto elapsed_time = (now - deposit_date);
      if(  elapsed_time > fc::seconds( BTS_BLOCKCHAIN_MIN_INTEREST_PERIOD_SEC ) )
      {
         if( rewards_pool > 0 && share_supply > 0 )
         {
            fc::uint128 amount_withdrawn( amount );
            amount_withdrawn *= BTS_BLOCKCHAIN_MAX_SHARES;

            fc::uint128 current_supply( share_supply );
            current_supply *= BTS_BLOCKCHAIN_MAX_SHARES;

            fc::uint128 fee_fund( rewards_pool );

            auto rewards = (amount_withdrawn * fee_fund) / current_supply;

            /**
             *  If less than 1 year, the 80% of rewards are scalled linerly with time and 20% scaled by time^2,
             *  this should produce a yield curve that is "80% simple interest" and 20% simulating compound
             *  interest.
             */
            if( elapsed_time < fc::seconds( BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC ) )
            {
               auto original_rewards = rewards;
               // discount the rewards by 80%
               rewards *= 8;
               rewards /= 10;

               auto delta_rewards = original_rewards - rewards;

               // rewards == amount withdrawn / total usd  *  fee fund * fraction_of_year
               rewards *= elapsed_time.to_seconds();
               rewards /= (BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC);

               delta_rewards *= elapsed_time.to_seconds();
               delta_rewards /= (BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC);

               rewards += delta_rewards;
            }

            auto rewards_amount = rewards.to_uint64();

            if( rewards_amount > 0 && rewards_amount < rewards_pool )
            {
               return asset( rewards_amount, condition.asset_id );
            }
         }
      }
      return asset( 0, condition.asset_id );
   }

   balance_id_type  deposit_operation::balance_id()const
   {
      return condition.get_address();
   }

   deposit_operation::deposit_operation( const address& owner,
                                         const asset& amnt,
                                         slate_id_type slate_id )
   {
      FC_ASSERT( amnt.amount > 0 );
      amount = amnt.amount;
      condition = withdraw_condition( withdraw_with_signature( owner ),
                                      amnt.asset_id, slate_id );
   }

   void define_delegate_slate_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
      auto slate_id = this->slate.id();

      if( this->slate.supported_delegates.size() > BTS_BLOCKCHAIN_MAX_SLATE_SIZE )
         FC_CAPTURE_AND_THROW( too_may_delegates_in_slate, (slate.supported_delegates.size()) );

      auto current_slate = eval_state._current_state->get_delegate_slate( slate_id );
      if( NOT current_slate )
      {
         for( auto delegate_id : this->slate.supported_delegates )
         {
            eval_state.verify_delegate_id( abs(delegate_id) );
         }
         eval_state._current_state->store_delegate_slate( slate_id, slate );
      }
   } FC_CAPTURE_AND_RETHROW( (eval_state) ) }

   /**
    *  TODO: Document Rules for Deposits
    *
    */
   void deposit_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
       if( this->amount <= 0 )
          FC_CAPTURE_AND_THROW( negative_deposit, (amount) );

       auto deposit_balance_id = this->balance_id();

       auto cur_record = eval_state._current_state->get_balance_record( deposit_balance_id );
       if( !cur_record )
       {
          cur_record = balance_record( this->condition );
       }
       cur_record->last_update   = eval_state._current_state->now();
       if( cur_record->balance == 0 )
       {
          cur_record->deposit_date  = eval_state._current_state->now();
       }
       else
       {
#ifndef WIN32
#warning [HARDFORK] Interest needs testing before merging into BTSX
#endif
          fc::uint128 old_sec_since_epoch( cur_record->deposit_date.sec_since_epoch());
          fc::uint128 new_sec_since_epoch( eval_state._current_state->now().sec_since_epoch());

          fc::uint128 avg = old_sec_since_epoch * cur_record->balance + new_sec_since_epoch*this->amount;
          avg /= (cur_record->balance + this->amount);

          cur_record->deposit_date  =  time_point_sec( avg.to_integer() );
       }
       cur_record->balance       += this->amount;

       eval_state.sub_balance( deposit_balance_id, asset(this->amount, cur_record->condition.asset_id) );

       if( cur_record->condition.asset_id == 0 && cur_record->condition.delegate_slate_id )
          eval_state.adjust_vote( cur_record->condition.delegate_slate_id, this->amount );

       eval_state._current_state->store_balance_record( *cur_record );
   } FC_CAPTURE_AND_RETHROW( (*this) ) }

   /**
    *  TODO: Document rules for Withdraws
    */
   void withdraw_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
       if( this->amount <= 0 )
          FC_CAPTURE_AND_THROW( negative_deposit, (amount) );

      obalance_record current_balance_record = eval_state._current_state->get_balance_record( this->balance_id );

      if( !current_balance_record )
         FC_CAPTURE_AND_THROW( unknown_balance_record, (balance_id) );

      if( this->amount > current_balance_record->balance )
         FC_CAPTURE_AND_THROW( insufficient_funds,
                               (current_balance_record)
                               (amount)
                               (current_balance_record->balance - amount) );

      switch( (withdraw_condition_types)current_balance_record->condition.type )
      {
         case withdraw_signature_type:
         {
            auto owner = current_balance_record->condition.as<withdraw_with_signature>().owner;
            if( claim_input_data.size() )
            {
               auto pts_sig = fc::raw::unpack<withdraw_with_pts>( claim_input_data );
               fc::sha256::encoder enc;
               fc::raw::pack( enc, eval_state._current_state->get_property( chain_property_enum::chain_id ).as<fc::ripemd160>() );
               fc::raw::pack( enc, pts_sig.new_key );
               auto digest = enc.result();
               fc::ecc::public_key test_key( pts_sig.pts_signature, digest );
               if( address(test_key) == owner ||
                   address(pts_address(test_key,false,56) ) == owner ||
                   address(pts_address(test_key,true,56) ) == owner ||
                   address(pts_address(test_key,false,0) ) == owner ||
                   address(pts_address(test_key,true,0) ) == owner )
               {
                  if( !eval_state.check_signature( pts_sig.new_key) )
                     FC_CAPTURE_AND_THROW( missing_signature, (pts_sig) );
                  break;
               }
               else
               {
                  FC_CAPTURE_AND_THROW( missing_signature, (owner)(pts_sig) );
               }
            }
            else
            {
               if( !eval_state.check_signature( owner ) )
                  FC_CAPTURE_AND_THROW( missing_signature, (owner) );
            }
            break;
         }

         case withdraw_multi_sig_type:
         {
            auto multi_sig = current_balance_record->condition.as<withdraw_with_multi_sig>();
            uint32_t valid_signatures = 0;
            for( auto sig : multi_sig.owners )
               valid_signatures += eval_state.check_signature( sig );
            if( valid_signatures < multi_sig.required )
               FC_CAPTURE_AND_THROW( missing_signature, (valid_signatures)(multi_sig) );
            break;
         }

         case withdraw_password_type:
         {
            auto password_condition = current_balance_record->condition.as<withdraw_with_password>();
            try {
               if( password_condition.timeout < eval_state._current_state->now() )
               {
                  if( !eval_state.check_signature( password_condition.payor ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (password_condition.payor) );
               }
               else
               {
                  if( !eval_state.check_signature( password_condition.payee ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (password_condition.payee) );
                  if( claim_input_data.size() < sizeof( fc::ripemd160 ) )
                     FC_CAPTURE_AND_THROW( invalid_claim_password, (claim_input_data) );

                  auto input_password_hash = fc::ripemd160::hash( claim_input_data.data(),
                                                                  claim_input_data.size() );

                  if( password_condition.password_hash != input_password_hash )
                     FC_CAPTURE_AND_THROW( invalid_claim_password, (input_password_hash) );
               }
            } FC_CAPTURE_AND_RETHROW( (password_condition ) )
            break;
         }

         case withdraw_option_type:
         {
            auto option = current_balance_record->condition.as<withdraw_option>();
            try {
               if( eval_state._current_state->now() > option.date )
               {
                  if( !eval_state.check_signature( option.optionor ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (option.optionor) );
               }
               else // the option hasn't expired
               {
                  if( !eval_state.check_signature( option.optionee ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (option.optionee) );

                  auto pay_amount = asset( this->amount, current_balance_record->condition.asset_id ) * option.strike_price;
                  eval_state.add_required_deposit( option.optionee, pay_amount );
               }
            } FC_CAPTURE_AND_RETHROW( (option) )
            break;
         }
         case withdraw_null_type:
            FC_CAPTURE_AND_THROW( invalid_withdraw_condition, (current_balance_record->condition) );
            break;
      //   default:
      }
      // update delegate vote on withdrawn account..

      current_balance_record->balance -= this->amount;
      current_balance_record->last_update = eval_state._current_state->now();

      if( current_balance_record->condition.asset_id == 0 && current_balance_record->condition.delegate_slate_id )
         eval_state.adjust_vote( current_balance_record->condition.delegate_slate_id, -this->amount );

#ifndef WIN32
#warning [HARDFORK] Interest needs testing before merging into BTSX
#endif
      auto asset_rec = eval_state._current_state->get_asset_record( current_balance_record->condition.asset_id );
      FC_ASSERT( asset_rec.valid() );
      if( asset_rec->is_market_issued() )
      {
         auto rewards = current_balance_record->calculate_rewards( eval_state._current_state->now(),
                                                                   current_balance_record->balance,
                                                                   asset_rec->collected_fees,
                                                                   asset_rec->current_share_supply );
         if( rewards.amount > 0 )
         {
            asset_rec->collected_fees       -= rewards.amount;
            current_balance_record->balance += rewards.amount;
            current_balance_record->deposit_date = eval_state._current_state->now();
            eval_state.rewards[current_balance_record->condition.asset_id] += rewards.amount;
            eval_state._current_state->store_asset_record( *asset_rec );
         }
      }

      eval_state._current_state->store_balance_record( *current_balance_record );
      eval_state.add_balance( asset(this->amount, current_balance_record->condition.asset_id) );
   } FC_CAPTURE_AND_RETHROW( (*this) ) }

} } // bts::blockchain
<commit_msg>squaring second half<commit_after>#include <bts/blockchain/balance_operations.hpp>
#include <bts/blockchain/chain_interface.hpp>
#include <bts/blockchain/exceptions.hpp>

namespace bts { namespace blockchain {

   asset   balance_record::calculate_rewards( fc::time_point_sec now, share_type amount, share_type rewards_pool, share_type share_supply )const
   {
      auto elapsed_time = (now - deposit_date);
      if(  elapsed_time > fc::seconds( BTS_BLOCKCHAIN_MIN_INTEREST_PERIOD_SEC ) )
      {
         if( rewards_pool > 0 && share_supply > 0 )
         {
            fc::uint128 amount_withdrawn( amount );
            amount_withdrawn *= BTS_BLOCKCHAIN_MAX_SHARES;

            fc::uint128 current_supply( share_supply );
            current_supply *= BTS_BLOCKCHAIN_MAX_SHARES;

            fc::uint128 fee_fund( rewards_pool );

            auto rewards = (amount_withdrawn * fee_fund) / current_supply;

            /**
             *  If less than 1 year, the 80% of rewards are scalled linerly with time and 20% scaled by time^2,
             *  this should produce a yield curve that is "80% simple interest" and 20% simulating compound
             *  interest.
             */
            if( elapsed_time < fc::seconds( BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC ) )
            {
               auto original_rewards = rewards;
               // discount the rewards by 80%
               rewards *= 8;
               rewards /= 10;

               auto delta_rewards = original_rewards - rewards;

               // rewards == amount withdrawn / total usd  *  fee fund * fraction_of_year
               rewards *= elapsed_time.to_seconds();
               rewards /= (BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC);

               delta_rewards *= elapsed_time.to_seconds();
               delta_rewards /= (BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC);

               delta_rewards *= elapsed_time.to_seconds();
               delta_rewards /= (BTS_BLOCKCHAIN_BLOCKS_PER_YEAR * BTS_BLOCKCHAIN_BLOCK_INTERVAL_SEC);

               rewards += delta_rewards;
            }

            auto rewards_amount = rewards.to_uint64();

            if( rewards_amount > 0 && rewards_amount < rewards_pool )
            {
               return asset( rewards_amount, condition.asset_id );
            }
         }
      }
      return asset( 0, condition.asset_id );
   }

   balance_id_type  deposit_operation::balance_id()const
   {
      return condition.get_address();
   }

   deposit_operation::deposit_operation( const address& owner,
                                         const asset& amnt,
                                         slate_id_type slate_id )
   {
      FC_ASSERT( amnt.amount > 0 );
      amount = amnt.amount;
      condition = withdraw_condition( withdraw_with_signature( owner ),
                                      amnt.asset_id, slate_id );
   }

   void define_delegate_slate_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
      auto slate_id = this->slate.id();

      if( this->slate.supported_delegates.size() > BTS_BLOCKCHAIN_MAX_SLATE_SIZE )
         FC_CAPTURE_AND_THROW( too_may_delegates_in_slate, (slate.supported_delegates.size()) );

      auto current_slate = eval_state._current_state->get_delegate_slate( slate_id );
      if( NOT current_slate )
      {
         for( auto delegate_id : this->slate.supported_delegates )
         {
            eval_state.verify_delegate_id( abs(delegate_id) );
         }
         eval_state._current_state->store_delegate_slate( slate_id, slate );
      }
   } FC_CAPTURE_AND_RETHROW( (eval_state) ) }

   /**
    *  TODO: Document Rules for Deposits
    *
    */
   void deposit_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
       if( this->amount <= 0 )
          FC_CAPTURE_AND_THROW( negative_deposit, (amount) );

       auto deposit_balance_id = this->balance_id();

       auto cur_record = eval_state._current_state->get_balance_record( deposit_balance_id );
       if( !cur_record )
       {
          cur_record = balance_record( this->condition );
       }
       cur_record->last_update   = eval_state._current_state->now();
       if( cur_record->balance == 0 )
       {
          cur_record->deposit_date  = eval_state._current_state->now();
       }
       else
       {
#ifndef WIN32
#warning [HARDFORK] Interest needs testing before merging into BTSX
#endif
          fc::uint128 old_sec_since_epoch( cur_record->deposit_date.sec_since_epoch());
          fc::uint128 new_sec_since_epoch( eval_state._current_state->now().sec_since_epoch());

          fc::uint128 avg = old_sec_since_epoch * cur_record->balance + new_sec_since_epoch*this->amount;
          avg /= (cur_record->balance + this->amount);

          cur_record->deposit_date  =  time_point_sec( avg.to_integer() );
       }
       cur_record->balance       += this->amount;

       eval_state.sub_balance( deposit_balance_id, asset(this->amount, cur_record->condition.asset_id) );

       if( cur_record->condition.asset_id == 0 && cur_record->condition.delegate_slate_id )
          eval_state.adjust_vote( cur_record->condition.delegate_slate_id, this->amount );

       eval_state._current_state->store_balance_record( *cur_record );
   } FC_CAPTURE_AND_RETHROW( (*this) ) }

   /**
    *  TODO: Document rules for Withdraws
    */
   void withdraw_operation::evaluate( transaction_evaluation_state& eval_state )
   { try {
       if( this->amount <= 0 )
          FC_CAPTURE_AND_THROW( negative_deposit, (amount) );

      obalance_record current_balance_record = eval_state._current_state->get_balance_record( this->balance_id );

      if( !current_balance_record )
         FC_CAPTURE_AND_THROW( unknown_balance_record, (balance_id) );

      if( this->amount > current_balance_record->balance )
         FC_CAPTURE_AND_THROW( insufficient_funds,
                               (current_balance_record)
                               (amount)
                               (current_balance_record->balance - amount) );

      switch( (withdraw_condition_types)current_balance_record->condition.type )
      {
         case withdraw_signature_type:
         {
            auto owner = current_balance_record->condition.as<withdraw_with_signature>().owner;
            if( claim_input_data.size() )
            {
               auto pts_sig = fc::raw::unpack<withdraw_with_pts>( claim_input_data );
               fc::sha256::encoder enc;
               fc::raw::pack( enc, eval_state._current_state->get_property( chain_property_enum::chain_id ).as<fc::ripemd160>() );
               fc::raw::pack( enc, pts_sig.new_key );
               auto digest = enc.result();
               fc::ecc::public_key test_key( pts_sig.pts_signature, digest );
               if( address(test_key) == owner ||
                   address(pts_address(test_key,false,56) ) == owner ||
                   address(pts_address(test_key,true,56) ) == owner ||
                   address(pts_address(test_key,false,0) ) == owner ||
                   address(pts_address(test_key,true,0) ) == owner )
               {
                  if( !eval_state.check_signature( pts_sig.new_key) )
                     FC_CAPTURE_AND_THROW( missing_signature, (pts_sig) );
                  break;
               }
               else
               {
                  FC_CAPTURE_AND_THROW( missing_signature, (owner)(pts_sig) );
               }
            }
            else
            {
               if( !eval_state.check_signature( owner ) )
                  FC_CAPTURE_AND_THROW( missing_signature, (owner) );
            }
            break;
         }

         case withdraw_multi_sig_type:
         {
            auto multi_sig = current_balance_record->condition.as<withdraw_with_multi_sig>();
            uint32_t valid_signatures = 0;
            for( auto sig : multi_sig.owners )
               valid_signatures += eval_state.check_signature( sig );
            if( valid_signatures < multi_sig.required )
               FC_CAPTURE_AND_THROW( missing_signature, (valid_signatures)(multi_sig) );
            break;
         }

         case withdraw_password_type:
         {
            auto password_condition = current_balance_record->condition.as<withdraw_with_password>();
            try {
               if( password_condition.timeout < eval_state._current_state->now() )
               {
                  if( !eval_state.check_signature( password_condition.payor ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (password_condition.payor) );
               }
               else
               {
                  if( !eval_state.check_signature( password_condition.payee ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (password_condition.payee) );
                  if( claim_input_data.size() < sizeof( fc::ripemd160 ) )
                     FC_CAPTURE_AND_THROW( invalid_claim_password, (claim_input_data) );

                  auto input_password_hash = fc::ripemd160::hash( claim_input_data.data(),
                                                                  claim_input_data.size() );

                  if( password_condition.password_hash != input_password_hash )
                     FC_CAPTURE_AND_THROW( invalid_claim_password, (input_password_hash) );
               }
            } FC_CAPTURE_AND_RETHROW( (password_condition ) )
            break;
         }

         case withdraw_option_type:
         {
            auto option = current_balance_record->condition.as<withdraw_option>();
            try {
               if( eval_state._current_state->now() > option.date )
               {
                  if( !eval_state.check_signature( option.optionor ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (option.optionor) );
               }
               else // the option hasn't expired
               {
                  if( !eval_state.check_signature( option.optionee ) )
                     FC_CAPTURE_AND_THROW( missing_signature, (option.optionee) );

                  auto pay_amount = asset( this->amount, current_balance_record->condition.asset_id ) * option.strike_price;
                  eval_state.add_required_deposit( option.optionee, pay_amount );
               }
            } FC_CAPTURE_AND_RETHROW( (option) )
            break;
         }
         case withdraw_null_type:
            FC_CAPTURE_AND_THROW( invalid_withdraw_condition, (current_balance_record->condition) );
            break;
      //   default:
      }
      // update delegate vote on withdrawn account..

      current_balance_record->balance -= this->amount;
      current_balance_record->last_update = eval_state._current_state->now();

      if( current_balance_record->condition.asset_id == 0 && current_balance_record->condition.delegate_slate_id )
         eval_state.adjust_vote( current_balance_record->condition.delegate_slate_id, -this->amount );

#ifndef WIN32
#warning [HARDFORK] Interest needs testing before merging into BTSX
#endif
      auto asset_rec = eval_state._current_state->get_asset_record( current_balance_record->condition.asset_id );
      FC_ASSERT( asset_rec.valid() );
      if( asset_rec->is_market_issued() )
      {
         auto rewards = current_balance_record->calculate_rewards( eval_state._current_state->now(),
                                                                   current_balance_record->balance,
                                                                   asset_rec->collected_fees,
                                                                   asset_rec->current_share_supply );
         if( rewards.amount > 0 )
         {
            asset_rec->collected_fees       -= rewards.amount;
            current_balance_record->balance += rewards.amount;
            current_balance_record->deposit_date = eval_state._current_state->now();
            eval_state.rewards[current_balance_record->condition.asset_id] += rewards.amount;
            eval_state._current_state->store_asset_record( *asset_rec );
         }
      }

      eval_state._current_state->store_balance_record( *current_balance_record );
      eval_state.add_balance( asset(this->amount, current_balance_record->condition.asset_id) );
   } FC_CAPTURE_AND_RETHROW( (*this) ) }

} } // bts::blockchain
<|endoftext|>


<commit_before>#include <yuni/yuni.h>
#include "details/pass/build-ast-to-ir/scope.h"
#include "details/utils/check-for-valid-identifier-name.h"
#include "libnany-config.h"

using namespace Yuni;

namespace Nany
{
namespace IR
{
namespace Producer
{

	inline bool Scope::visitASTExprIdentifier(const AST::Node& node, LVID& localvar)
	{
		// value fetching
		emitDebugpos(node);
		uint32_t rid = sequence().emitStackalloc(nextvar(), nyt_any);
		sequence().emitIdentify(rid, node.text, localvar);
		localvar = rid;

		if (not node.children.empty())
			return visitASTExprContinuation(node, localvar);
		return true;
	}

	bool Scope::visitASTExprIdOperator(const AST::Node& node, LVID& localvar)
	{
		if (unlikely((node.children.size() != 1) or (node.children[0]->rule != AST::rgFunctionKindOpname)))
			return unexpectedNode(node, "[expr/operator]");

		// value fetching
		emitDebugpos(node);
		uint32_t rid = sequence().emitStackalloc(nextvar(), nyt_any);
		ShortString64 idname;
		idname << '^' << node.children[0]->text;
		sequence().emitIdentify(rid, acquireString(idname), localvar);
		localvar = rid;
		return true;
	}

	inline bool Scope::visitASTExprRegister(const AST::Node& node, LVID& localvar)
	{
		assert(not node.text.empty());
		localvar = node.text.to<uint32_t>();
		assert(localvar != 0);
		if (node.children.empty())
			return true;
		return visitASTExprContinuation(node, localvar);
	}

	bool Scope::visitASTExprContinuation(const AST::Node& node, LVID& localvar, bool allowScope)
	{
		bool success = true;
		for (auto& childptr: node.children)
		{
			auto& child = *childptr;
			switch (child.rule)
			{
				case AST::rgIdentifier: success &= visitASTExprIdentifier(child, localvar); break;
				case AST::rgExprValue:
				case AST::rgExprGroup:  success &= visitASTExpr(child, localvar); break;
				case AST::rgCall:       success &= visitASTExprCall(&child, localvar, &node); break;
				case AST::rgNumber:     success &= visitASTExprNumber(child, localvar); break;
				case AST::rgNew:        success &= visitASTExprNew(child, localvar); break;
				case AST::rgExprSubDot:
				case AST::rgTypeSubDot: success &= visitASTExprSubDot(child, localvar); break;

				// strings
				case AST::rgString:     success &= visitASTExprString(child, localvar); break;
				// when directly called from an expr, this rule is generated by the compiler itself
				case AST::rgStringLiteral: success &= visitASTExprStringLiteral(child, localvar); break;

				// special stuff
				case AST::rgTypeof:     success &= visitASTExprTypeof(child, localvar); break;
				case AST::rgIntrinsic:  success &= visitASTExprIntrinsic(child, localvar); break;

				case AST::rgIf:         success &= visitASTExprIfExpr(child, localvar); break;
				case AST::rgWhile:      success &= visitASTExprWhile(child); break;

				case AST::rgExprTemplate:
				case AST::rgExprTypeTemplate: success &= visitASTExprTemplate(child, localvar); break;

				case AST::rgIn:         success &= visitASTExprIn(child, localvar); break;
				case AST::rgFunction:   success &= visitASTExprClosure(child, localvar); break;

				case AST::rgAttributes: success &= visitASTAttributes(child); break;

				case AST::rgFunctionKindOperator: success &= visitASTExprIdOperator(child, localvar); break;

				// special for internal AST manipulation
				case AST::rgRegister:   success &= visitASTExprRegister(child, localvar); break;

				// scope may appear in expr (when expr are actually statements)
				case AST::rgScope: {
					if (allowScope) {
						success &= visitASTExprScope(child);
						break;
					}
				}
				// [[fallthru]]
				default:
					success = unexpectedNode(child, "[expr/continuation]");
			}
		}
		return success;
	}

	void Scope::emitExprAttributes(uint32_t& localvar)
	{
		assert(!!pAttributes);
		auto& attrs = *pAttributes;

		// allow to push a synthetic object (type)
		if (unlikely(attrs.flags(Attributes::Flag::pushSynthetic)))
		{
			// do not report errors
			attrs.flags -= Attributes::Flag::pushSynthetic;
			if (debugmode)
				sequence().emitComment(String("#[__nanyc_synthetic: %") << localvar << ']');
			sequence().emitPragmaSynthetic(localvar, false);
		}
	}

	bool Scope::visitASTExpr(const AST::Node& orignode, LVID& localvar, bool allowScope)
	{
		assert(not orignode.children.empty());

		// reset the value of the localvar, result of the expr
		localvar = 0;

		// expr
		// |   expr-value
		const AST::Node* nodeptr = &orignode;
		const AST::Node* attrnode = nullptr;
		if (orignode.rule == AST::rgExpr)
		{
			switch (orignode.children.size())
			{
				case 1:
				{
					// do not take into consideration this node
					// (it will generate useless scopes)
					auto& firstchild = *(orignode.children[0]);
					if (firstchild.rule == AST::rgExprValue)
						nodeptr = &firstchild;
					break;
				}
				case 2:
				{
					auto& firstchild = *(orignode.children[0]);
					if (firstchild.rule == AST::rgAttributes)
					{
						auto& sndchild = *(orignode.children[1]);
						if (sndchild.rule == AST::rgExprValue)
						{
							nodeptr = &sndchild;
							attrnode = &firstchild; // do not forget to visit this node
						}
					}
					break;
				}
			}
		}

		auto& node = *nodeptr;
		assert(node.rule == AST::rgExpr or node.rule == AST::rgExprValue
			or node.rule == AST::rgExprGroup or node.rule == AST::rgTypeDecl);
		assert(not node.children.empty());

		// always creating a new scope for a expr
		IR::Producer::Scope scope{*this};

		if (unlikely(attrnode))
			scope.visitASTAttributes(*attrnode);

		bool r = scope.visitASTExprContinuation(node, localvar, allowScope);
		if (r and localvar != 0 and localvar != (uint32_t) -1)
		{
			scope.emitTmplParametersIfAny();
			scope.emitDebugpos(node);
			scope.sequence().emitEnsureTypeResolved(localvar);

			if (unlikely(!!scope.pAttributes))
				scope.emitExprAttributes(localvar);
		}
		return r;
	}

} // namespace Producer
} // namespace IR
} // namespace Nany
<commit_msg>ast: removed useless string acquisition<commit_after>#include <yuni/yuni.h>
#include "details/pass/build-ast-to-ir/scope.h"
#include "details/utils/check-for-valid-identifier-name.h"
#include "libnany-config.h"

using namespace Yuni;

namespace Nany
{
namespace IR
{
namespace Producer
{

	inline bool Scope::visitASTExprIdentifier(const AST::Node& node, LVID& localvar)
	{
		// value fetching
		emitDebugpos(node);
		uint32_t rid = sequence().emitStackalloc(nextvar(), nyt_any);
		sequence().emitIdentify(rid, node.text, localvar);
		localvar = rid;

		if (not node.children.empty())
			return visitASTExprContinuation(node, localvar);
		return true;
	}

	bool Scope::visitASTExprIdOperator(const AST::Node& node, LVID& localvar)
	{
		if (unlikely((node.children.size() != 1) or (node.children[0]->rule != AST::rgFunctionKindOpname)))
			return unexpectedNode(node, "[expr/operator]");

		// value fetching
		emitDebugpos(node);
		uint32_t rid = sequence().emitStackalloc(nextvar(), nyt_any);
		ShortString64 idname;
		idname << '^' << node.children[0]->text;
		sequence().emitIdentify(rid, idname, localvar);
		localvar = rid;
		return true;
	}

	inline bool Scope::visitASTExprRegister(const AST::Node& node, LVID& localvar)
	{
		assert(not node.text.empty());
		localvar = node.text.to<uint32_t>();
		assert(localvar != 0);
		if (node.children.empty())
			return true;
		return visitASTExprContinuation(node, localvar);
	}

	bool Scope::visitASTExprContinuation(const AST::Node& node, LVID& localvar, bool allowScope)
	{
		bool success = true;
		for (auto& childptr: node.children)
		{
			auto& child = *childptr;
			switch (child.rule)
			{
				case AST::rgIdentifier: success &= visitASTExprIdentifier(child, localvar); break;
				case AST::rgExprValue:
				case AST::rgExprGroup:  success &= visitASTExpr(child, localvar); break;
				case AST::rgCall:       success &= visitASTExprCall(&child, localvar, &node); break;
				case AST::rgNumber:     success &= visitASTExprNumber(child, localvar); break;
				case AST::rgNew:        success &= visitASTExprNew(child, localvar); break;
				case AST::rgExprSubDot:
				case AST::rgTypeSubDot: success &= visitASTExprSubDot(child, localvar); break;

				// strings
				case AST::rgString:     success &= visitASTExprString(child, localvar); break;
				// when directly called from an expr, this rule is generated by the compiler itself
				case AST::rgStringLiteral: success &= visitASTExprStringLiteral(child, localvar); break;

				// special stuff
				case AST::rgTypeof:     success &= visitASTExprTypeof(child, localvar); break;
				case AST::rgIntrinsic:  success &= visitASTExprIntrinsic(child, localvar); break;

				case AST::rgIf:         success &= visitASTExprIfExpr(child, localvar); break;
				case AST::rgWhile:      success &= visitASTExprWhile(child); break;

				case AST::rgExprTemplate:
				case AST::rgExprTypeTemplate: success &= visitASTExprTemplate(child, localvar); break;

				case AST::rgIn:         success &= visitASTExprIn(child, localvar); break;
				case AST::rgFunction:   success &= visitASTExprClosure(child, localvar); break;

				case AST::rgAttributes: success &= visitASTAttributes(child); break;

				case AST::rgFunctionKindOperator: success &= visitASTExprIdOperator(child, localvar); break;

				// special for internal AST manipulation
				case AST::rgRegister:   success &= visitASTExprRegister(child, localvar); break;

				// scope may appear in expr (when expr are actually statements)
				case AST::rgScope: {
					if (allowScope) {
						success &= visitASTExprScope(child);
						break;
					}
				}
				// [[fallthru]]
				default:
					success = unexpectedNode(child, "[expr/continuation]");
			}
		}
		return success;
	}

	void Scope::emitExprAttributes(uint32_t& localvar)
	{
		assert(!!pAttributes);
		auto& attrs = *pAttributes;

		// allow to push a synthetic object (type)
		if (unlikely(attrs.flags(Attributes::Flag::pushSynthetic)))
		{
			// do not report errors
			attrs.flags -= Attributes::Flag::pushSynthetic;
			if (debugmode)
				sequence().emitComment(String("#[__nanyc_synthetic: %") << localvar << ']');
			sequence().emitPragmaSynthetic(localvar, false);
		}
	}

	bool Scope::visitASTExpr(const AST::Node& orignode, LVID& localvar, bool allowScope)
	{
		assert(not orignode.children.empty());

		// reset the value of the localvar, result of the expr
		localvar = 0;

		// expr
		// |   expr-value
		const AST::Node* nodeptr = &orignode;
		const AST::Node* attrnode = nullptr;
		if (orignode.rule == AST::rgExpr)
		{
			switch (orignode.children.size())
			{
				case 1:
				{
					// do not take into consideration this node
					// (it will generate useless scopes)
					auto& firstchild = *(orignode.children[0]);
					if (firstchild.rule == AST::rgExprValue)
						nodeptr = &firstchild;
					break;
				}
				case 2:
				{
					auto& firstchild = *(orignode.children[0]);
					if (firstchild.rule == AST::rgAttributes)
					{
						auto& sndchild = *(orignode.children[1]);
						if (sndchild.rule == AST::rgExprValue)
						{
							nodeptr = &sndchild;
							attrnode = &firstchild; // do not forget to visit this node
						}
					}
					break;
				}
			}
		}

		auto& node = *nodeptr;
		assert(node.rule == AST::rgExpr or node.rule == AST::rgExprValue
			or node.rule == AST::rgExprGroup or node.rule == AST::rgTypeDecl);
		assert(not node.children.empty());

		// always creating a new scope for a expr
		IR::Producer::Scope scope{*this};

		if (unlikely(attrnode))
			scope.visitASTAttributes(*attrnode);

		bool r = scope.visitASTExprContinuation(node, localvar, allowScope);
		if (r and localvar != 0 and localvar != (uint32_t) -1)
		{
			scope.emitTmplParametersIfAny();
			scope.emitDebugpos(node);
			scope.sequence().emitEnsureTypeResolved(localvar);

			if (unlikely(!!scope.pAttributes))
				scope.emitExprAttributes(localvar);
		}
		return r;
	}

} // namespace Producer
} // namespace IR
} // namespace Nany
<|endoftext|>


<commit_before>// Copyright (c) 2014, Robert Escriva
// All rights reserved.
//
// 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 this project 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.

#define __STDC_LIMIT_MACROS

// STL
#include <algorithm>

// e
#include "e/garbage_collector.h"

using e::garbage_collector;

class garbage_collector::thread_state_node
{
    public:
        thread_state_node()
            : next(NULL), quiescent_timestamp(1), offline_timestamp(0) {}
        ~thread_state_node() throw () {}

    public:
        thread_state_node* next;
        uint64_t quiescent_timestamp;
        uint64_t offline_timestamp;

    private:
        thread_state_node(const thread_state_node&);
        thread_state_node& operator = (const thread_state_node&);
};

class garbage_collector::garbage
{
    public:
        garbage()
            : next(NULL), timestamp(0), ptr(NULL), func(NULL) {}
        garbage(uint64_t t, void* p, void (*f)(void*))
            : next(NULL), timestamp(t), ptr(p), func(f) {}

    public:
        garbage* next;
        uint64_t timestamp;
        void* ptr;
        void (*func)(void* ptr);

    private:
        garbage(const garbage&);
        garbage& operator = (const garbage&);
};

garbage_collector :: garbage_collector()
    : m_timestamp()
    , m_offline_transitions()
    , m_registered(NULL)
    , m_garbage()
    , m_protect_registration()
{
    po6::threads::mutex::hold hold(&m_protect_registration);
    e::atomic::store_64_nobarrier(&m_timestamp, 2);
    e::atomic::store_64_nobarrier(&m_offline_transitions, 0);
    e::atomic::store_ptr_nobarrier(&m_garbage, static_cast<garbage*>(NULL));
}

garbage_collector :: ~garbage_collector() throw ()
{
    po6::threads::mutex::hold hold(&m_protect_registration);

    while (m_registered)
    {
        thread_state_node* tmp = m_registered;
        m_registered = tmp->next;
        delete tmp;
    }

    garbage* gc = e::atomic::load_ptr_acquire(&m_garbage);

    while (gc)
    {
        garbage* tmp = gc;
        gc = e::atomic::load_ptr_acquire(&tmp->next);
        tmp->func(tmp->ptr);
        delete tmp;
    }
}

void
garbage_collector :: register_thread(thread_state* ts)
{
    assert(ts->m_tsn == NULL);
    // create the new thread state node and save a ref to it
    thread_state_node* tsn = new thread_state_node();
    ts->m_tsn = tsn;
    po6::threads::mutex::hold hold(&m_protect_registration);
    e::atomic::store_ptr_nobarrier(&tsn->next, m_registered);
    e::atomic::store_ptr_release(&m_registered, tsn);
    uint64_t timestamp = read_timestamp();
    e::atomic::store_64_release(&tsn->quiescent_timestamp, timestamp);
}

void
garbage_collector :: deregister_thread(thread_state* ts)
{
    assert(ts->m_tsn != NULL);
    po6::threads::mutex::hold hold(&m_protect_registration);
    thread_state_node** ptr = &m_registered;
    thread_state_node* node = e::atomic::load_ptr_acquire(ptr);

    while (node && node != ts->m_tsn)
    {
        ptr  = &node->next;
        node = e::atomic::load_ptr_acquire(ptr);
    }

    // if these asserts fail, it means that ts is corrupt
    assert(node == ts->m_tsn);
    assert(*ptr == node);

    // unlink the node from the chain
    e::atomic::store_ptr_release(ptr, node->next);

    // now destroy the unlinked tsn
    collect(node, garbage_collector::free_ptr<thread_state_node>);
}

void
garbage_collector :: quiescent_state(thread_state* ts)
{
    // no need to memory barrier within this funciton when storking
    // tsn->quiescent_timestamp because it's strictly increasing, and seeing a
    // lower value only delays garbage collection, but cannot hurt safety.
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;

    // read the timestamp here
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);

    // Find the largest timestamp that is less than each thread state's
    // quiescent_timestamp.  For every other thread, use the value that they
    // advertise in their thread_state_node.  For this thread, use the value we
    // just read above in the counter.
    uint64_t transitions = load_64_acquire(&m_offline_transitions);
    uint64_t min_timestamp = timestamp;
    uint64_t min_offline = timestamp;
    thread_state_node* node = load_ptr_acquire(&m_registered);

    while (node)
    {
        if (node != tsn)
        {
            uint64_t qst = load_64_acquire(&node->quiescent_timestamp);
            uint64_t oft = load_64_acquire(&node->offline_timestamp);

            if (qst > oft)
            {
                min_timestamp = std::min(qst, min_timestamp);
            }
            else
            {
                min_offline = std::min(oft, min_offline);
            }
        }

        node = load_ptr_acquire(&node->next);
    }

    if (min_offline < min_timestamp)
    {
        read_timestamp();
        uint64_t second_transitions = load_64_acquire(&m_offline_transitions);

        if (transitions < second_transitions)
        {
            min_timestamp = min_offline;
        }
    }

    // At this point, min_timestamp is the smallest quiescent_timestamp across
    // all threads.  We know this because every thread falls into one of two
    // cases.  The first case is pretty simple:  we visited it in the above
    // loop, and observed its timestamp directly.
    //
    // The second case is considerably more complex and involves the state of a
    // thread that registered but did not get seen by this loop.  Because
    // read_timestamp is a full barrier operation, and draws from a sequential
    // counter, we know that one of two things happened.  Either the "register"
    // timestamp comes before the timestamp found in this function, or it comes
    // after.  If it comes before, we know that it would be seen by the loop
    // because the release-store on the timestamp in register, and acquire-load
    // on the timestamp here would ensure that the value stored to m_registered
    // became visible to us.  This would mean that it falls into the first
    // paragraph's case.
    //
    // In the other subcase, the "register" timestamp comes after timestamp
    // found in this function.  In this case, the minimum timestamp is the one
    // we selected above and we're all set.
    //
    // In all cases, the minimum timestamp is the smallest of every thread's
    // quiescent timestamp.
    garbage* gc = load_ptr_nobarrier(&m_garbage);

    if (compare_and_swap_ptr_fullbarrier(&m_garbage, gc, static_cast<garbage*>(NULL)) != gc)
    {
        gc = NULL;
    }

    // expose our timestamp to the world
    store_64_release(&tsn->quiescent_timestamp, timestamp);

    while (gc)
    {
        garbage* next = load_ptr_acquire(&gc->next);

        if (gc->timestamp < min_timestamp)
        {
            gc->func(gc->ptr);
            delete gc;
        }
        else
        {
            enqueue(&m_garbage, gc);
        }

        gc = next;
    }
}

void
garbage_collector :: offline(thread_state* ts)
{
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);
    assert(tsn->offline_timestamp < timestamp);
    store_64_release(&tsn->offline_timestamp, timestamp);
    store_64_release(&tsn->quiescent_timestamp, timestamp);
    read_timestamp();
}

void
garbage_collector :: online(thread_state* ts)
{
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);
    assert(tsn->offline_timestamp < timestamp);
    store_64_release(&tsn->quiescent_timestamp, timestamp);

    while (compare_and_swap_64_nobarrier(&m_offline_transitions, e::atomic::load_64_nobarrier(&m_offline_transitions), timestamp) < timestamp)
        ;

    read_timestamp();
}

void
garbage_collector :: collect(void* ptr, void(*func)(void* ptr))
{
    garbage* g(new garbage(UINT64_MAX, ptr, func));
    uint64_t timestamp = read_timestamp();
    e::atomic::store_64_release(&g->timestamp, timestamp);
    enqueue(&m_garbage, g);
}

uint64_t
garbage_collector :: read_timestamp()
{
    return e::atomic::increment_64_fullbarrier(&m_timestamp, 1);
}

void
garbage_collector :: enqueue(garbage* volatile* list, garbage* g)
{
    garbage* expect = e::atomic::load_ptr_acquire(list);
    garbage* witness = expect;
    e::atomic::store_ptr_release(&g->next, expect);

    while ((witness = e::atomic::compare_and_swap_ptr_fullbarrier(list, expect, g)) != expect)
    {
        expect = witness;
        e::atomic::store_ptr_release(&g->next, expect);
    }
}
<commit_msg>Improve garbage collection performance<commit_after>// Copyright (c) 2014, Robert Escriva
// All rights reserved.
//
// 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 this project 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.

#define __STDC_LIMIT_MACROS

// STL
#include <algorithm>
#include <vector>

// e
#include "e/garbage_collector.h"

using e::garbage_collector;

class garbage_collector::thread_state_node
{
    public:
        static bool heap_cmp(const std::pair<uint64_t, garbage*>& lhs,
                             const std::pair<uint64_t, garbage*>& rhs)
        {
            // not an accident; STL heap maximizes, so we need our "less than"
            // operator to be inverted.
            return lhs.first > rhs.first;
        }

    public:
        thread_state_node()
            : next(NULL), quiescent_timestamp(1), offline_timestamp(0) {}
        ~thread_state_node() throw () {}

    public:
        thread_state_node* next;
        uint64_t quiescent_timestamp;
        uint64_t offline_timestamp;
        std::vector<std::pair<uint64_t, garbage*> > heap;

    private:
        thread_state_node(const thread_state_node&);
        thread_state_node& operator = (const thread_state_node&);
};

class garbage_collector::garbage
{
    public:
        garbage()
            : next(NULL), timestamp(0), ptr(NULL), func(NULL) {}
        garbage(uint64_t t, void* p, void (*f)(void*))
            : next(NULL), timestamp(t), ptr(p), func(f) {}

    public:
        garbage* next;
        uint64_t timestamp;
        void* ptr;
        void (*func)(void* ptr);

    private:
        garbage(const garbage&);
        garbage& operator = (const garbage&);
};

garbage_collector :: garbage_collector()
    : m_timestamp()
    , m_offline_transitions()
    , m_registered(NULL)
    , m_garbage()
    , m_protect_registration()
{
    po6::threads::mutex::hold hold(&m_protect_registration);
    e::atomic::store_64_nobarrier(&m_timestamp, 2);
    e::atomic::store_64_nobarrier(&m_offline_transitions, 0);
    e::atomic::store_ptr_nobarrier(&m_garbage, static_cast<garbage*>(NULL));
}

garbage_collector :: ~garbage_collector() throw ()
{
    po6::threads::mutex::hold hold(&m_protect_registration);

    while (m_registered)
    {
        thread_state_node* tmp = m_registered;
        m_registered = tmp->next;
        delete tmp;
    }

    garbage* gc = e::atomic::load_ptr_acquire(&m_garbage);

    while (gc)
    {
        garbage* tmp = gc;
        gc = e::atomic::load_ptr_acquire(&tmp->next);
        tmp->func(tmp->ptr);
        delete tmp;
    }
}

void
garbage_collector :: register_thread(thread_state* ts)
{
    assert(ts->m_tsn == NULL);
    // create the new thread state node and save a ref to it
    thread_state_node* tsn = new thread_state_node();
    ts->m_tsn = tsn;
    po6::threads::mutex::hold hold(&m_protect_registration);
    e::atomic::store_ptr_nobarrier(&tsn->next, m_registered);
    e::atomic::store_ptr_release(&m_registered, tsn);
    uint64_t timestamp = read_timestamp();
    e::atomic::store_64_release(&tsn->quiescent_timestamp, timestamp);
}

void
garbage_collector :: deregister_thread(thread_state* ts)
{
    assert(ts->m_tsn != NULL);
    po6::threads::mutex::hold hold(&m_protect_registration);
    thread_state_node** ptr = &m_registered;
    thread_state_node* node = e::atomic::load_ptr_acquire(ptr);

    while (node && node != ts->m_tsn)
    {
        ptr  = &node->next;
        node = e::atomic::load_ptr_acquire(ptr);
    }

    // if these asserts fail, it means that ts is corrupt
    assert(node == ts->m_tsn);
    assert(*ptr == node);

    // unlink the node from the chain
    e::atomic::store_ptr_release(ptr, node->next);

    for (size_t i = 0; i < node->heap.size(); ++i)
    {
        enqueue(&m_garbage, node->heap[i].second);
    }

    // now destroy the unlinked tsn
    collect(node, garbage_collector::free_ptr<thread_state_node>);
}

void
garbage_collector :: quiescent_state(thread_state* ts)
{
    // no need to memory barrier within this funciton when storking
    // tsn->quiescent_timestamp because it's strictly increasing, and seeing a
    // lower value only delays garbage collection, but cannot hurt safety.
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;

    // read the timestamp here
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);

    // Find the largest timestamp that is less than each thread state's
    // quiescent_timestamp.  For every other thread, use the value that they
    // advertise in their thread_state_node.  For this thread, use the value we
    // just read above in the counter.
    uint64_t transitions = load_64_acquire(&m_offline_transitions);
    uint64_t min_timestamp = timestamp;
    uint64_t min_offline = timestamp;
    thread_state_node* node = load_ptr_acquire(&m_registered);

    while (node)
    {
        if (node != tsn)
        {
            uint64_t qst = load_64_acquire(&node->quiescent_timestamp);
            uint64_t oft = load_64_acquire(&node->offline_timestamp);

            if (qst > oft)
            {
                min_timestamp = std::min(qst, min_timestamp);
            }
            else
            {
                min_offline = std::min(oft, min_offline);
            }
        }

        node = load_ptr_acquire(&node->next);
    }

    if (min_offline < min_timestamp)
    {
        read_timestamp();
        uint64_t second_transitions = load_64_acquire(&m_offline_transitions);

        if (transitions < second_transitions)
        {
            min_timestamp = min_offline;
        }
    }

    // At this point, min_timestamp is the smallest quiescent_timestamp across
    // all threads.  We know this because every thread falls into one of two
    // cases.  The first case is pretty simple:  we visited it in the above
    // loop, and observed its timestamp directly.
    //
    // The second case is considerably more complex and involves the state of a
    // thread that registered but did not get seen by this loop.  Because
    // read_timestamp is a full barrier operation, and draws from a sequential
    // counter, we know that one of two things happened.  Either the "register"
    // timestamp comes before the timestamp found in this function, or it comes
    // after.  If it comes before, we know that it would be seen by the loop
    // because the release-store on the timestamp in register, and acquire-load
    // on the timestamp here would ensure that the value stored to m_registered
    // became visible to us.  This would mean that it falls into the first
    // paragraph's case.
    //
    // In the other subcase, the "register" timestamp comes after timestamp
    // found in this function.  In this case, the minimum timestamp is the one
    // we selected above and we're all set.
    //
    // In all cases, the minimum timestamp is the smallest of every thread's
    // quiescent timestamp.
    garbage* gc = load_ptr_nobarrier(&m_garbage);

    if (compare_and_swap_ptr_fullbarrier(&m_garbage, gc, static_cast<garbage*>(NULL)) != gc)
    {
        gc = NULL;
    }

    // expose our timestamp to the world
    store_64_release(&tsn->quiescent_timestamp, timestamp);

    // purge from the heap all items less than min_timestamp
    while (!tsn->heap.empty() && tsn->heap[0].first < min_timestamp)
    {
        garbage* g = tsn->heap[0].second;
        g->func(g->ptr);
        delete g;
        std::pop_heap(tsn->heap.begin(), tsn->heap.end(), thread_state_node::heap_cmp);
        tsn->heap.pop_back();
    }

    while (gc)
    {
        garbage* next = load_ptr_acquire(&gc->next);

        if (gc->timestamp < min_timestamp)
        {
            gc->func(gc->ptr);
            delete gc;
        }
        else
        {
            tsn->heap.push_back(std::make_pair(gc->timestamp, gc));
            std::push_heap(tsn->heap.begin(), tsn->heap.end());
        }

        gc = next;
    }
}

void
garbage_collector :: offline(thread_state* ts)
{
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);
    assert(tsn->offline_timestamp < timestamp);
    store_64_release(&tsn->offline_timestamp, timestamp);
    store_64_release(&tsn->quiescent_timestamp, timestamp);
    read_timestamp();
}

void
garbage_collector :: online(thread_state* ts)
{
    using namespace e::atomic;
    thread_state_node* const tsn = ts->m_tsn;
    uint64_t timestamp = read_timestamp();
    assert(tsn->quiescent_timestamp < timestamp);
    assert(tsn->offline_timestamp < timestamp);
    store_64_release(&tsn->quiescent_timestamp, timestamp);

    while (compare_and_swap_64_nobarrier(&m_offline_transitions, e::atomic::load_64_nobarrier(&m_offline_transitions), timestamp) < timestamp)
        ;

    read_timestamp();
}

void
garbage_collector :: collect(void* ptr, void(*func)(void* ptr))
{
    garbage* g(new garbage(UINT64_MAX, ptr, func));
    uint64_t timestamp = read_timestamp();
    e::atomic::store_64_release(&g->timestamp, timestamp);
    enqueue(&m_garbage, g);
}

uint64_t
garbage_collector :: read_timestamp()
{
    return e::atomic::increment_64_fullbarrier(&m_timestamp, 1);
}

void
garbage_collector :: enqueue(garbage* volatile* list, garbage* g)
{
    garbage* expect = e::atomic::load_ptr_acquire(list);
    garbage* witness = expect;
    e::atomic::store_ptr_release(&g->next, expect);

    while ((witness = e::atomic::compare_and_swap_ptr_fullbarrier(list, expect, g)) != expect)
    {
        expect = witness;
        e::atomic::store_ptr_release(&g->next, expect);
    }
}
<|endoftext|>