<commit_before>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include <fc/filesystem.hpp>
#include <fc/optional.hpp>
#include <fc/string.hpp>
#include <fc/variant.hpp>
#include <fc/variant_object.hpp>

#include <fc/io/iostream.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/json.hpp>
#include <fc/rpc/json_connection.hpp>

#include <bts/blockchain/genesis_config.hpp>
#include <bts/client/client.hpp>
#include <bts/net/node.hpp>
#include <bts/rpc/rpc_server.hpp>

namespace bts { namespace tscript {

class client_context
{
public:
    client_context();
    virtual ~client_context();

    void configure_client_from_args();
    void start();

    std::vector<std::string> args;
    bts::client::client_ptr client;
    fc::future<void> client_done;
    std::map<std::string, std::string> template_context;
};

typedef std::shared_ptr<client_context> client_context_ptr;

class context
{
public:
    context();
    virtual ~context();

    void set_genesis(const fc::path& genesis_json_filename);

    void create_client(const std::string& name);
    fc::path get_data_dir_for_client(const std::string& name) const;

    void get_clients_by_variant(const fc::variant& spec, std::vector<client_context_ptr>& result, int depth=0);

    fc::path basedir;
    std::vector<client_context_ptr> v_clients;
    std::map<std::string, client_context_ptr> m_name_client;
    std::map<std::string, std::vector<std::string>> m_name_client_group;
    std::map<std::string, std::string> template_context;
    fc::path genesis_json_filename;
    bts::blockchain::genesis_block_config genesis_config;
    bts::net::simulated_network_ptr sim_network;
};

typedef std::shared_ptr<context> context_ptr;

class interpreter
{
public:
    interpreter();
    virtual ~interpreter();

    void run_single_command(const fc::variants& cmd);
    void run_command_list(const fc::variants& cmd_list);
    void run_file(const fc::path& path);

    void cmd_clients(const fc::variants& args);
    void cmd_x(const fc::variants& cmd);

    context_ptr ctx;
};

client_context::client_context()
{
    return;
}

client_context::~client_context()
{
    return;
}

inline void copy_str_to_buffer_with_0(std::vector<char> &buf, const std::string& s)
{
    std::copy(s.c_str(), s.c_str()+s.length(), std::back_inserter(buf));
    buf.push_back('\0');
    return;
}

void client_context::configure_client_from_args()
{
    std::vector<char> buf;
    std::vector<size_t> arg_offset;

    std::string arg_0 = "bitshares_client";
    arg_offset.push_back(0);
    copy_str_to_buffer_with_0(buf, arg_0);

    int argc = (int) this->args.size() + 1;

    for(int i=1;i<argc;i++)
    {
        arg_offset.push_back(buf.size());
        copy_str_to_buffer_with_0(buf, this->args[i-1]);
    }

    std::vector<char*> v_argv;
    v_argv.push_back(buf.data());
    for(int i=1;i<argc;i++)
        v_argv.push_back(v_argv[0] + arg_offset[i]);

    std::cout << "client invocation:";
    for(int i=0;i<argc;i++)
    {
        std::cout << v_argv.data()[i] << " ";
    }
    std::cout << "\n";

    this->client->configure_from_command_line(argc, v_argv.data());

    return;
}

void client_context::start()
{
    this->client_done = this->client->start();
    return;
}

context::context()
{
    this->sim_network = std::make_shared<bts::net::simulated_network>("tscript");
    this->m_name_client_group["all"] = std::vector<std::string>();
    this->basedir = "tmp";
    return;
}

context::~context()
{
    return;
}

void context::set_genesis(const fc::path& genesis_json_filename)
{
    this->genesis_json_filename = genesis_json_filename;
    this->genesis_config = fc::json::from_file(genesis_json_filename).as<bts::blockchain::genesis_block_config>();
    this->template_context["genesis.timestamp"] = this->genesis_config.timestamp.to_iso_string();
    return;
}

void context::create_client(const std::string& name)
{
    client_context_ptr cc = std::make_shared<client_context>();
    cc->args.push_back("--disable-default-peers");
    cc->args.push_back("--log-commands");
    cc->args.push_back("--ulog=0");
    cc->args.push_back("--min-delegate-connection-count=0");
    cc->args.push_back("--upnp=false");
    cc->args.push_back("--genesis-config=" + this->genesis_json_filename.string());
    cc->args.push_back("--data-dir=" + this->get_data_dir_for_client(name).string());

    cc->client = std::make_shared<bts::client::client>("tscript", this->sim_network);
    cc->configure_client_from_args();
    cc->client->set_client_debug_name(name);
    cc->template_context["client.name"] = name;

    this->m_name_client[name] = cc;
    this->m_name_client_group["all"].push_back(name);

    return;
}

fc::path context::get_data_dir_for_client(const std::string& name) const
{
    return this->basedir / "client" / name;
}

void context::get_clients_by_variant(const fc::variant& spec, std::vector<client_context_ptr>& result, int depth)
{
    FC_ASSERT(depth < 20);

    if( spec.is_array() )
    {
        const fc::variants& v = spec.get_array();
        for( const fc::variant& e : v )
            this->get_clients_by_variant(e, result, depth+1);
        return;
    }

    if( spec.is_string() )
    {
        std::string target_name = spec.get_string();
        std::cout << "in spec.is_string() : target=" << target_name << "\n";
        auto p = this->m_name_client_group.find(target_name);
        if( p != this->m_name_client_group.end() )
        {
            std::cout << "   found group\n";
            for( const fc::string& e : p->second )
            {
                auto q = this->m_name_client.find(e);
                if( q != this->m_name_client.end() )
                {
                    result.push_back(q->second);
                    std::cout << "   group member " << e << " found\n";
                }
                else
                    FC_ASSERT(false, "couldn't find named client when expanding group definition");
            }
            return;
        }
        auto q = this->m_name_client.find(target_name);
        if( q != this->m_name_client.end() )
        {
            std::cout << "   found singleton\n";
            result.push_back(q->second);
            return;
        }
        FC_ASSERT(false, "couldn't find named client");
    }

    FC_ASSERT(false, "expected: client-spec");
}

interpreter::interpreter()
{
    this->ctx = std::make_shared<context>();
    return;
}

interpreter::~interpreter()
{
    return;
}

void interpreter::run_single_command(const fc::variants& cmd)
{
    // parse the command
    std::string action = cmd[0].get_string();

    if( action == "x")
    {
        this->cmd_x(cmd);
    }
    else if( action == "!clients" )
    {
        this->cmd_clients( cmd );
    }
    else if( action == "!defgroup" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else if( action == "!include" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else if( action == "!pragma" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else
    {
        FC_ASSERT(false, "unknown command");
    }
    return;
}

void interpreter::run_command_list(const fc::variants& cmd_list)
{
    for(auto cmd : cmd_list)
        this->run_single_command(cmd.as<fc::variants>());
    return;
}

void interpreter::run_file(const fc::path& path)
{
    fc::variants v = fc::json::from_file<fc::variants>(path);
    this->run_command_list(v);
    return;
}

void interpreter::cmd_clients(const fc::variants& cmd)
{
    FC_ASSERT(cmd.size() >= 2);

    fc::variants args = cmd[1].get_array();

    // create clients
    for( const fc::variant& e : args )
    {
        std::string cname = e.as<std::string>();
        std::cout << "creating client " << cname << "\n";
        this->ctx->create_client(cname);
    }
    return;
}

void interpreter::cmd_x(const fc::variants& cmd)
{
    FC_ASSERT( cmd.size() >= 3 );

    std::cout << "in cmd_x\n";

    std::vector<bts::tscript::client_context_ptr> targets;

    this->ctx->get_clients_by_variant( cmd[1], targets );

    std::cout << "targets found: " << targets.size() << "\n";

    for( bts::tscript::client_context_ptr& t : targets )
    {
        std::cout << "   " << t->client->debug_get_client_name() << ":\n";

        const std::string& method_name = cmd[2].get_string();

        std::cout << "      " << method_name << "\n";

        // create context for command by combining template context dictionaries
        fc::mutable_variant_object effective_ctx;
        for( auto& e : this->ctx->template_context )
            effective_ctx[e.first] = e.second;
        for( auto& e : t->template_context )
            effective_ctx[e.first] = e.second;
        fc::variant_object v_effective_ctx = effective_ctx;

        // substitute into parameters
        fc::variants args;
        if( cmd.size() >= 4 )
            args = cmd[3].get_array();

        for( size_t i=0,n=args.size(); i<n; i++ )
        {
            if( args[i].is_string() )
            {
                args[i] = fc::format_string(args[i].get_string(), v_effective_ctx);
            }
            std::cout << "      " << args[i].as_string() << "\n";
        }

        fc::optional<fc::variant> result;
        fc::optional<fc::exception> exc;

        try
        {
            result = t->client->get_rpc_server()->direct_invoke_method(method_name, args);
            std::cout << "      result:" << fc::json::to_string(result) << "\n";
        }
        catch( const fc::exception& e )
        {
            exc = e;
            std::cout << "      " << e.to_detail_string() << "\n";
        }

        std::string cmp_op = "nop";
        fc::variant cmp_op_arg;
        if( cmd.size() >= 5 )
        {
            cmp_op = cmd[4].get_string();
            if( cmd.size() >= 6 )
            {
                cmp_op_arg = cmd[5];
            }
        }

        bool expected_exception = false;

        if( cmp_op == "nop" )
            ;
        else if( cmp_op == "eq" )
        {
            FC_ASSERT(result.valid());
            std::cout << "*result : " << fc::json::to_string(*result) << "\n";
            std::cout << "cmp_op_arg : " << fc::json::to_string(cmp_op_arg) << "\n";
            FC_ASSERT(((*result) == cmp_op_arg).as_bool());
        }
        else if( cmp_op == "ex" )
        {
            expected_exception = true;
        }
        else if( cmp_op == "ss" )
        {
            FC_ASSERT(result.valid());
            FC_ASSERT(cmp_op_arg.is_object());
            FC_ASSERT(result->is_object());

            fc::variant_object vo_result = result->get_object();
            for( auto& kv : cmp_op_arg.get_object() )
            {
                auto it = vo_result.find(kv.key());
                FC_ASSERT( it != vo_result.end() );
                FC_ASSERT( (it->value() == kv.value()).as_bool() );
            }
        }

        if( (!expected_exception) && (!result.valid()) )
            FC_ASSERT(false, "terminating due to unexpected exception");
    }
    std::cout << "\n";

    return;
}

void run_single_tscript(
    const fc::path& genesis_json_filename,
    const fc::path& tscript_filename
    )
{
    FC_ASSERT( fc::is_regular_file(tscript_filename) );

    // delete client directories
    fc::remove_all( "tmp/client" );

    bts::tscript::interpreter interp;
    interp.ctx->set_genesis(genesis_json_filename);
    interp.run_file(tscript_filename);
    return;
}

inline bool startswith(
    const std::string& a,
    const std::string& prefix
    )
{
    size_t m = prefix.length(), n = a.length();
    return (m <= n) && (0 == a.compare( 0, m, prefix ));
}

inline bool endswith(
    const std::string& a,
    const std::string& suffix
    )
{
    size_t m = suffix.length(), n = a.length();
    return (m <= n) && (0 == a.compare( n-m, m, suffix ));
}

void run_tscripts(
    const fc::path& genesis_json_filename,
    const fc::path& target_filename
    )
{
    if( !fc::is_directory(target_filename) )
    {
        run_single_tscript( genesis_json_filename, target_filename );
        return;
    }

    for( fc::recursive_directory_iterator it(target_filename), it_end;
         it != it_end; ++it )
    {
        fc::path filename = (*it);
        if( !fc::is_regular_file(filename) )
            continue;
        if( !endswith(filename.string(), ".tscript") )
            continue;
        run_single_tscript(genesis_json_filename, filename);
    }
    return;
}

} }

int main(int argc, char **argv, char **envp)
{
    for( int i=1; i<argc; i++ )
        bts::tscript::run_tscripts( "tmp/genesis.json", argv[i] );
    return 0;
}
<commit_msg>Display argument using fc::json::to_string() instead of as_string()<commit_after>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include <fc/filesystem.hpp>
#include <fc/optional.hpp>
#include <fc/string.hpp>
#include <fc/variant.hpp>
#include <fc/variant_object.hpp>

#include <fc/io/iostream.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/json.hpp>
#include <fc/rpc/json_connection.hpp>

#include <bts/blockchain/genesis_config.hpp>
#include <bts/client/client.hpp>
#include <bts/net/node.hpp>
#include <bts/rpc/rpc_server.hpp>

namespace bts { namespace tscript {

class client_context
{
public:
    client_context();
    virtual ~client_context();

    void configure_client_from_args();
    void start();

    std::vector<std::string> args;
    bts::client::client_ptr client;
    fc::future<void> client_done;
    std::map<std::string, std::string> template_context;
};

typedef std::shared_ptr<client_context> client_context_ptr;

class context
{
public:
    context();
    virtual ~context();

    void set_genesis(const fc::path& genesis_json_filename);

    void create_client(const std::string& name);
    fc::path get_data_dir_for_client(const std::string& name) const;

    void get_clients_by_variant(const fc::variant& spec, std::vector<client_context_ptr>& result, int depth=0);

    fc::path basedir;
    std::vector<client_context_ptr> v_clients;
    std::map<std::string, client_context_ptr> m_name_client;
    std::map<std::string, std::vector<std::string>> m_name_client_group;
    std::map<std::string, std::string> template_context;
    fc::path genesis_json_filename;
    bts::blockchain::genesis_block_config genesis_config;
    bts::net::simulated_network_ptr sim_network;
};

typedef std::shared_ptr<context> context_ptr;

class interpreter
{
public:
    interpreter();
    virtual ~interpreter();

    void run_single_command(const fc::variants& cmd);
    void run_command_list(const fc::variants& cmd_list);
    void run_file(const fc::path& path);

    void cmd_clients(const fc::variants& args);
    void cmd_x(const fc::variants& cmd);

    context_ptr ctx;
};

client_context::client_context()
{
    return;
}

client_context::~client_context()
{
    return;
}

inline void copy_str_to_buffer_with_0(std::vector<char> &buf, const std::string& s)
{
    std::copy(s.c_str(), s.c_str()+s.length(), std::back_inserter(buf));
    buf.push_back('\0');
    return;
}

void client_context::configure_client_from_args()
{
    std::vector<char> buf;
    std::vector<size_t> arg_offset;

    std::string arg_0 = "bitshares_client";
    arg_offset.push_back(0);
    copy_str_to_buffer_with_0(buf, arg_0);

    int argc = (int) this->args.size() + 1;

    for(int i=1;i<argc;i++)
    {
        arg_offset.push_back(buf.size());
        copy_str_to_buffer_with_0(buf, this->args[i-1]);
    }

    std::vector<char*> v_argv;
    v_argv.push_back(buf.data());
    for(int i=1;i<argc;i++)
        v_argv.push_back(v_argv[0] + arg_offset[i]);

    std::cout << "client invocation:";
    for(int i=0;i<argc;i++)
    {
        std::cout << v_argv.data()[i] << " ";
    }
    std::cout << "\n";

    this->client->configure_from_command_line(argc, v_argv.data());

    return;
}

void client_context::start()
{
    this->client_done = this->client->start();
    return;
}

context::context()
{
    this->sim_network = std::make_shared<bts::net::simulated_network>("tscript");
    this->m_name_client_group["all"] = std::vector<std::string>();
    this->basedir = "tmp";
    return;
}

context::~context()
{
    return;
}

void context::set_genesis(const fc::path& genesis_json_filename)
{
    this->genesis_json_filename = genesis_json_filename;
    this->genesis_config = fc::json::from_file(genesis_json_filename).as<bts::blockchain::genesis_block_config>();
    this->template_context["genesis.timestamp"] = this->genesis_config.timestamp.to_iso_string();
    return;
}

void context::create_client(const std::string& name)
{
    client_context_ptr cc = std::make_shared<client_context>();
    cc->args.push_back("--disable-default-peers");
    cc->args.push_back("--log-commands");
    cc->args.push_back("--ulog=0");
    cc->args.push_back("--min-delegate-connection-count=0");
    cc->args.push_back("--upnp=false");
    cc->args.push_back("--genesis-config=" + this->genesis_json_filename.string());
    cc->args.push_back("--data-dir=" + this->get_data_dir_for_client(name).string());

    cc->client = std::make_shared<bts::client::client>("tscript", this->sim_network);
    cc->configure_client_from_args();
    cc->client->set_client_debug_name(name);
    cc->template_context["client.name"] = name;

    this->m_name_client[name] = cc;
    this->m_name_client_group["all"].push_back(name);

    return;
}

fc::path context::get_data_dir_for_client(const std::string& name) const
{
    return this->basedir / "client" / name;
}

void context::get_clients_by_variant(const fc::variant& spec, std::vector<client_context_ptr>& result, int depth)
{
    FC_ASSERT(depth < 20);

    if( spec.is_array() )
    {
        const fc::variants& v = spec.get_array();
        for( const fc::variant& e : v )
            this->get_clients_by_variant(e, result, depth+1);
        return;
    }

    if( spec.is_string() )
    {
        std::string target_name = spec.get_string();
        std::cout << "in spec.is_string() : target=" << target_name << "\n";
        auto p = this->m_name_client_group.find(target_name);
        if( p != this->m_name_client_group.end() )
        {
            std::cout << "   found group\n";
            for( const fc::string& e : p->second )
            {
                auto q = this->m_name_client.find(e);
                if( q != this->m_name_client.end() )
                {
                    result.push_back(q->second);
                    std::cout << "   group member " << e << " found\n";
                }
                else
                    FC_ASSERT(false, "couldn't find named client when expanding group definition");
            }
            return;
        }
        auto q = this->m_name_client.find(target_name);
        if( q != this->m_name_client.end() )
        {
            std::cout << "   found singleton\n";
            result.push_back(q->second);
            return;
        }
        FC_ASSERT(false, "couldn't find named client");
    }

    FC_ASSERT(false, "expected: client-spec");
}

interpreter::interpreter()
{
    this->ctx = std::make_shared<context>();
    return;
}

interpreter::~interpreter()
{
    return;
}

void interpreter::run_single_command(const fc::variants& cmd)
{
    // parse the command
    std::string action = cmd[0].get_string();

    if( action == "x")
    {
        this->cmd_x(cmd);
    }
    else if( action == "!clients" )
    {
        this->cmd_clients( cmd );
    }
    else if( action == "!defgroup" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else if( action == "!include" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else if( action == "!pragma" )
    {
        FC_ASSERT(false, "unimplemented command");
    }
    else
    {
        FC_ASSERT(false, "unknown command");
    }
    return;
}

void interpreter::run_command_list(const fc::variants& cmd_list)
{
    for(auto cmd : cmd_list)
        this->run_single_command(cmd.as<fc::variants>());
    return;
}

void interpreter::run_file(const fc::path& path)
{
    fc::variants v = fc::json::from_file<fc::variants>(path);
    this->run_command_list(v);
    return;
}

void interpreter::cmd_clients(const fc::variants& cmd)
{
    FC_ASSERT(cmd.size() >= 2);

    fc::variants args = cmd[1].get_array();

    // create clients
    for( const fc::variant& e : args )
    {
        std::string cname = e.as<std::string>();
        std::cout << "creating client " << cname << "\n";
        this->ctx->create_client(cname);
    }
    return;
}

void interpreter::cmd_x(const fc::variants& cmd)
{
    FC_ASSERT( cmd.size() >= 3 );

    std::cout << "in cmd_x\n";

    std::vector<bts::tscript::client_context_ptr> targets;

    this->ctx->get_clients_by_variant( cmd[1], targets );

    std::cout << "targets found: " << targets.size() << "\n";

    for( bts::tscript::client_context_ptr& t : targets )
    {
        std::cout << "   " << t->client->debug_get_client_name() << ":\n";

        const std::string& method_name = cmd[2].get_string();

        std::cout << "      " << method_name << "\n";

        // create context for command by combining template context dictionaries
        fc::mutable_variant_object effective_ctx;
        for( auto& e : this->ctx->template_context )
            effective_ctx[e.first] = e.second;
        for( auto& e : t->template_context )
            effective_ctx[e.first] = e.second;
        fc::variant_object v_effective_ctx = effective_ctx;

        // substitute into parameters
        fc::variants args;
        if( cmd.size() >= 4 )
            args = cmd[3].get_array();

        for( size_t i=0,n=args.size(); i<n; i++ )
        {
            if( args[i].is_string() )
            {
                args[i] = fc::format_string(args[i].get_string(), v_effective_ctx);
            }
            std::cout << "      " << fc::json::to_string(args[i]) << "\n";
        }

        fc::optional<fc::variant> result;
        fc::optional<fc::exception> exc;

        try
        {
            result = t->client->get_rpc_server()->direct_invoke_method(method_name, args);
            std::cout << "      result:" << fc::json::to_string(result) << "\n";
        }
        catch( const fc::exception& e )
        {
            exc = e;
            std::cout << "      " << e.to_detail_string() << "\n";
        }

        std::string cmp_op = "nop";
        fc::variant cmp_op_arg;
        if( cmd.size() >= 5 )
        {
            cmp_op = cmd[4].get_string();
            if( cmd.size() >= 6 )
            {
                cmp_op_arg = cmd[5];
            }
        }

        bool expected_exception = false;

        if( cmp_op == "nop" )
            ;
        else if( cmp_op == "eq" )
        {
            FC_ASSERT(result.valid());
            std::cout << "*result : " << fc::json::to_string(*result) << "\n";
            std::cout << "cmp_op_arg : " << fc::json::to_string(cmp_op_arg) << "\n";
            FC_ASSERT(((*result) == cmp_op_arg).as_bool());
        }
        else if( cmp_op == "ex" )
        {
            expected_exception = true;
        }
        else if( cmp_op == "ss" )
        {
            FC_ASSERT(result.valid());
            FC_ASSERT(cmp_op_arg.is_object());
            FC_ASSERT(result->is_object());

            fc::variant_object vo_result = result->get_object();
            for( auto& kv : cmp_op_arg.get_object() )
            {
                auto it = vo_result.find(kv.key());
                FC_ASSERT( it != vo_result.end() );
                FC_ASSERT( (it->value() == kv.value()).as_bool() );
            }
        }

        if( (!expected_exception) && (!result.valid()) )
            FC_ASSERT(false, "terminating due to unexpected exception");
    }
    std::cout << "\n";

    return;
}

void run_single_tscript(
    const fc::path& genesis_json_filename,
    const fc::path& tscript_filename
    )
{
    FC_ASSERT( fc::is_regular_file(tscript_filename) );

    // delete client directories
    fc::remove_all( "tmp/client" );

    bts::tscript::interpreter interp;
    interp.ctx->set_genesis(genesis_json_filename);
    interp.run_file(tscript_filename);
    return;
}

inline bool startswith(
    const std::string& a,
    const std::string& prefix
    )
{
    size_t m = prefix.length(), n = a.length();
    return (m <= n) && (0 == a.compare( 0, m, prefix ));
}

inline bool endswith(
    const std::string& a,
    const std::string& suffix
    )
{
    size_t m = suffix.length(), n = a.length();
    return (m <= n) && (0 == a.compare( n-m, m, suffix ));
}

void run_tscripts(
    const fc::path& genesis_json_filename,
    const fc::path& target_filename
    )
{
    if( !fc::is_directory(target_filename) )
    {
        run_single_tscript( genesis_json_filename, target_filename );
        return;
    }

    for( fc::recursive_directory_iterator it(target_filename), it_end;
         it != it_end; ++it )
    {
        fc::path filename = (*it);
        if( !fc::is_regular_file(filename) )
            continue;
        if( !endswith(filename.string(), ".tscript") )
            continue;
        run_single_tscript(genesis_json_filename, filename);
    }
    return;
}

} }

int main(int argc, char **argv, char **envp)
{
    for( int i=1; i<argc; i++ )
        bts::tscript::run_tscripts( "tmp/genesis.json", argv[i] );
    return 0;
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *    Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
 */

#include <open62541/types.h>
#include <open62541/types_generated_handling.h>

/* Decode a message, then encode, decode, encode.
 * The two encodings must be bit-equal. */
extern "C" int
LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
    UA_ByteString buf;
    buf.data = (UA_Byte*)data;
    buf.length = size;

    UA_Variant value;
    UA_Variant_init(&value);

    UA_StatusCode retval = UA_decodeJson(&buf, &value, &UA_TYPES[UA_TYPES_VARIANT], NULL);
    if(retval != UA_STATUSCODE_GOOD)
        return 0;

    size_t jsonSize = UA_calcSizeJson(&value, &UA_TYPES[UA_TYPES_VARIANT], NULL);

    UA_ByteString buf2 = UA_BYTESTRING_NULL;
    retval = UA_ByteString_allocBuffer(&buf2, jsonSize);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Variant_clear(&value);
        return 0;
    }

    retval = UA_encodeJson(&value, &UA_TYPES[UA_TYPES_VARIANT], &buf2, NULL);
    UA_assert(retval == UA_STATUSCODE_GOOD);

    UA_Variant value2;
    UA_Variant_init(&value2);
    retval = UA_decodeJson(&buf2, &value2, &UA_TYPES[UA_TYPES_VARIANT], NULL);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Variant_clear(&value);
        UA_ByteString_clear(&buf2);
        return 0;
    }

    UA_assert(UA_order(&value, &value2, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ);
    UA_Variant_clear(&value);

    UA_ByteString buf3 = UA_BYTESTRING_NULL;
    retval = UA_ByteString_allocBuffer(&buf3, jsonSize);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Variant_clear(&value2);
        UA_ByteString_clear(&buf2);
        return 0;
    }

    retval = UA_encodeJson(&value2, &UA_TYPES[UA_TYPES_VARIANT], &buf3, NULL);
    UA_assert(retval == UA_STATUSCODE_GOOD);

    UA_assert(buf2.length == buf3.length);
    UA_assert(memcmp(buf2.data, buf3.data, buf2.length) == 0);

    UA_Variant_clear(&value2);
    UA_ByteString_clear(&buf2);
    UA_ByteString_clear(&buf3);
    return 0;
}
<commit_msg>feat(tests): Decoding of JSON (we encoded) must succeed in fuzzing<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *    Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
 */

#include <open62541/types.h>
#include <open62541/types_generated_handling.h>

/* Decode a message, then encode, decode, encode.
 * The two encodings must be bit-equal. */
extern "C" int
LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
    UA_ByteString buf;
    buf.data = (UA_Byte*)data;
    buf.length = size;

    UA_Variant value;
    UA_Variant_init(&value);

    UA_StatusCode retval = UA_decodeJson(&buf, &value, &UA_TYPES[UA_TYPES_VARIANT], NULL);
    if(retval != UA_STATUSCODE_GOOD)
        return 0;

    size_t jsonSize = UA_calcSizeJson(&value, &UA_TYPES[UA_TYPES_VARIANT], NULL);

    UA_ByteString buf2 = UA_BYTESTRING_NULL;
    retval = UA_ByteString_allocBuffer(&buf2, jsonSize);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Variant_clear(&value);
        return 0;
    }

    retval = UA_encodeJson(&value, &UA_TYPES[UA_TYPES_VARIANT], &buf2, NULL);
    UA_assert(retval == UA_STATUSCODE_GOOD);

    UA_Variant value2;
    UA_Variant_init(&value2);
    retval = UA_decodeJson(&buf2, &value2, &UA_TYPES[UA_TYPES_VARIANT], NULL);
    UA_assert(retval == UA_STATUSCODE_GOOD);
    UA_assert(UA_order(&value, &value2, &UA_TYPES[UA_TYPES_VARIANT]) == UA_ORDER_EQ);
    UA_Variant_clear(&value);

    UA_ByteString buf3 = UA_BYTESTRING_NULL;
    retval = UA_ByteString_allocBuffer(&buf3, jsonSize);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Variant_clear(&value2);
        UA_ByteString_clear(&buf2);
        return 0;
    }

    retval = UA_encodeJson(&value2, &UA_TYPES[UA_TYPES_VARIANT], &buf3, NULL);
    UA_assert(retval == UA_STATUSCODE_GOOD);

    UA_assert(buf2.length == buf3.length);
    UA_assert(memcmp(buf2.data, buf3.data, buf2.length) == 0);

    UA_Variant_clear(&value2);
    UA_ByteString_clear(&buf2);
    UA_ByteString_clear(&buf3);
    return 0;
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <chrono>
#include <unordered_map>
#include <boost/intrusive/unordered_set.hpp>

#include <seastar/core/timer.hh>

#include "utils/exceptions.hh"

namespace bi = boost::intrusive;

namespace utils {
// Simple variant of the "LoadingCache" used for permissions in origin.

typedef lowres_clock loading_cache_clock_type;

template<typename _Tp, typename _Key, typename _Hash, typename _EqualPred>
class timestamped_val : public bi::unordered_set_base_hook<bi::store_hash<true>> {
public:
    typedef _Key key_type;
    typedef _Tp value_type;

private:
    std::experimental::optional<_Tp> _opt_value;
    loading_cache_clock_type::time_point _loaded;
    loading_cache_clock_type::time_point _last_read;
    _Key _key;

public:
    struct key_eq {
       bool operator()(const _Key& k, const timestamped_val& c) const {
           return _EqualPred()(k, c.key());
       }

       bool operator()(const timestamped_val& c, const _Key& k) const {
           return _EqualPred()(c.key(), k);
       }
    };

    timestamped_val(const _Key& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _key(key) {}

    timestamped_val(_Key&& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _key(std::move(key)) {}

    timestamped_val(const timestamped_val&) = default;
    timestamped_val(timestamped_val&&) = default;

    // Make sure copy/move-assignments don't go through the template below
    timestamped_val& operator=(const timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&&) = default;

    template <typename U>
    timestamped_val& operator=(U&& new_val) {
        _opt_value = std::forward<U>(new_val);
        _loaded = loading_cache_clock_type::now();
        return *this;
    }

    const _Tp& value() {
        _last_read = loading_cache_clock_type::now();
        return _opt_value.value();
    }

    explicit operator bool() const noexcept {
        return bool(_opt_value);
    }

    loading_cache_clock_type::time_point last_read() const noexcept {
        return _last_read;
    }

    loading_cache_clock_type::time_point loaded() const noexcept {
        return _loaded;
    }

    const _Key& key() const {
        return _key;
    }

    friend bool operator==(const timestamped_val& a, const timestamped_val& b){
        return _EqualPred()(a.key(), b.key());
    }

    friend std::size_t hash_value(const timestamped_val& v) {
        return _Hash()(v.key());
    }
};

class shared_mutex {
private:
    lw_shared_ptr<semaphore> _mutex_ptr;

public:
    shared_mutex() : _mutex_ptr(make_lw_shared<semaphore>(1)) {}
    semaphore& get() const noexcept {
        return *_mutex_ptr;
    }
};

template<typename _Key,
         typename _Tp,
         typename _Hash = std::hash<_Key>,
         typename _Pred = std::equal_to<_Key>,
         typename _Alloc = std::allocator<timestamped_val<_Tp, _Key, _Hash, _Pred>>,
         typename SharedMutexMapAlloc = std::allocator<std::pair<const _Key, shared_mutex>>>
class loading_cache {
private:
    typedef timestamped_val<_Tp, _Key, _Hash, _Pred> ts_value_type;
    typedef bi::unordered_set<ts_value_type, bi::power_2_buckets<true>, bi::compare_hash<true>> set_type;
    typedef std::unordered_map<_Key, shared_mutex, _Hash, _Pred, SharedMutexMapAlloc> write_mutex_map_type;
    typedef loading_cache<_Key, _Tp, _Hash, _Pred, _Alloc> _MyType;
    typedef typename set_type::bucket_traits bi_set_bucket_traits;

    static constexpr int initial_num_buckets = 256;
    static constexpr int max_num_buckets = 1024 * 1024;

public:
    typedef _Tp value_type;
    typedef _Key key_type;
    typedef typename set_type::iterator iterator;

    template<typename Func>
    loading_cache(size_t max_size, std::chrono::milliseconds expiry, std::chrono::milliseconds refresh, logging::logger& logger, Func&& load)
                : _buckets(initial_num_buckets)
                , _set(bi_set_bucket_traits(_buckets.data(), _buckets.size()))
                , _max_size(max_size)
                , _expiry(expiry)
                , _refresh(refresh)
                , _logger(logger)
                , _load(std::forward<Func>(load)) {

        // If expiration period is zero - caching is disabled
        if (!caching_enabled()) {
            return;
        }

        // Sanity check: if expiration period is given then non-zero refresh period and maximal size are required
        if (_refresh == std::chrono::milliseconds(0) || _max_size == 0) {
            throw exceptions::configuration_exception("loading_cache: caching is enabled but refresh period and/or max_size are zero");
        }

        _timer.set_callback([this] { on_timer(); });
        _timer.arm(_refresh);
    }

    ~loading_cache() {
        _set.clear_and_dispose([] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
    }

    future<_Tp> get(const _Key& k) {
        // If caching is disabled - always load in the foreground
        if (!caching_enabled()) {
            return _load(k);
        }

        // If the key is not in the cache yet, then find_or_create() is going to
        // create a new uninitialized value in the map. If the value is already
        // in the cache (the fast path) simply return the value. Otherwise, take
        // the mutex and try to load the value (the slow path).
        iterator ts_value_it = find_or_create(k);
        if (*ts_value_it) {
            return make_ready_future<_Tp>(ts_value_it->value());
        } else {
            return slow_load(k);
        }
    }

private:
    bool caching_enabled() const {
        return _expiry != std::chrono::milliseconds(0);
    }

    /// Look for the entry with the given key. It it doesn't exist - create a new one and add it to the _set.
    ///
    /// \param k The key to look for
    ///
    /// \return An iterator to the value with the given key (always dirrerent from _set.end())
    template <typename KeyType>
    iterator find_or_create(KeyType&& k) {
        iterator i = _set.find(k, _Hash(), typename ts_value_type::key_eq());
        if (i == _set.end()) {
            ts_value_type* new_ts_val = _Alloc().allocate(1);
            new(new_ts_val) ts_value_type(std::forward<KeyType>(k));
            auto p = _set.insert(*new_ts_val);
            i = p.first;
        }

        return i;
    }

    static void destroy_ts_value(ts_value_type* val) {
        val->~ts_value_type();
        _Alloc().deallocate(val, 1);
    }

    future<_Tp> slow_load(const _Key& k) {
        // If the key is not in the cache yet, then _write_mutex_map[k] is going
        // to create a new value with the initialized mutex. The mutex is going
        // to serialize the producers and only the first one is going to
        // actually issue a load operation and initialize the value with the
        // received result. The rest are going to see (and read) the initialized
        // value when they enter the critical section.
        shared_mutex sm = _write_mutex_map[k];
        return with_semaphore(sm.get(), 1, [this, k] {
            iterator ts_value_it = find_or_create(k);
            if (*ts_value_it) {
                return make_ready_future<_Tp>(ts_value_it->value());
            }
            _logger.trace("{}: storing the value for the first time", k);
            return _load(k).then([this, k] (_Tp t) {
                // we have to "re-read" the _set here because the value may have been evicted by now
                iterator ts_value_it = find_or_create(std::move(k));
                *ts_value_it = std::move(t);
                return make_ready_future<_Tp>(ts_value_it->value());
            });
        }).finally([sm] {});
    }

    future<> reload(ts_value_type& ts_val) {
        return _load(ts_val.key()).then_wrapped([this, &ts_val] (auto&& f) {
            // The exceptions are related to the load operation itself.
            // We should ignore them for the background reads - if
            // they persist the value will age and will be reloaded in
            // the forground. If the foreground READ fails the error
            // will be propagated up to the user and will fail the
            // corresponding query.
            try {
                ts_val = f.get0();
            } catch (std::exception& e) {
                _logger.debug("{}: reload failed: {}", ts_val.key(), e.what());
            } catch (...) {
                _logger.debug("{}: reload failed: unknown error", ts_val.key());
            }
        });
    }

    void erase(iterator it) {
        _set.erase_and_dispose(it, [] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
    }

    // We really miss the std::erase_if()... :(
    void drop_expired() {
        auto now = loading_cache_clock_type::now();
        auto i = _set.begin();
        auto e = _set.end();

        while (i != e) {
            // An entry should be discarded if it hasn't been reloaded for too long or nobody cares about it anymore
            auto since_last_read = now - i->last_read();
            auto since_loaded = now - i->loaded();
            if (_expiry < since_last_read || _expiry < since_loaded) {
                using namespace std::chrono;
                _logger.trace("drop_expired(): {}: dropping the entry: _expiry {},  ms passed since: loaded {} last_read {}", i->key(), _expiry.count(), duration_cast<milliseconds>(since_loaded).count(), duration_cast<milliseconds>(since_last_read).count());
                erase(i++);
                continue;
            }
            ++i;
        }
    }

    // Shrink the cache to the _max_size discarding the least recently used items
    void shrink() {
        if (_max_size != 0 && _set.size() > _max_size) {
            std::vector<iterator> tmp;
            tmp.reserve(_set.size());

            iterator i = _set.begin();
            while (i != _set.end()) {
                tmp.emplace_back(i++);
            }

            std::sort(tmp.begin(), tmp.end(), [] (iterator i1, iterator i2) {
               return i1->last_read() < i2->last_read();
            });

            tmp.resize(_set.size() - _max_size);
            std::for_each(tmp.begin(), tmp.end(), [this] (auto& k) {
                using namespace std::chrono;
                _logger.trace("shrink(): {}: dropping the entry: ms since last_read {}", k->key(), duration_cast<milliseconds>(loading_cache_clock_type::now() - k->last_read()).count());
                this->erase(k);
            });
        }
    }

    void rehash() {
        size_t new_buckets_count = 0;

        // Don't grow or shrink too fast even if there is a steep drop/growth in the number of elements in the set.
        // Exponential growth/backoff should be good enough.
        //
        // Try to keep the load factor between 0.25 and 1.0.
        if (_set.size() < _current_buckets_count / 4) {
            new_buckets_count = _current_buckets_count / 4;
        } else if (_set.size() > _current_buckets_count) {
            new_buckets_count = _current_buckets_count * 2;
        }

        if (new_buckets_count < initial_num_buckets || new_buckets_count > max_num_buckets) {
            return;
        }

        std::vector<typename set_type::bucket_type> new_buckets(new_buckets_count);
        _set.rehash(bi_set_bucket_traits(new_buckets.data(), new_buckets.size()));
        _logger.trace("rehash(): buckets count changed: {} -> {}", _current_buckets_count, new_buckets_count);

        _buckets.swap(new_buckets);
        _current_buckets_count = new_buckets_count;
    }

    void on_timer() {
        _logger.trace("on_timer(): start");

        auto timer_start_tp = loading_cache_clock_type::now();

        // Clear all cached mutexes
        _write_mutex_map.clear();

        // Clean up items that were not touched for the whole _expiry period.
        drop_expired();

        // Remove the least recently used items if map is too big.
        shrink();

        // check if rehashing is needed and do it if it is.
        rehash();

        // Reload all those which vlaue needs to be reloaded.
        parallel_for_each(_set.begin(), _set.end(), [this, curr_time = timer_start_tp] (auto& ts_val) {
            _logger.trace("on_timer(): {}: checking the value age", ts_val.key());
            if (ts_val && ts_val.loaded() + _refresh < curr_time) {
                _logger.trace("on_timer(): {}: reloading the value", ts_val.key());
                return this->reload(ts_val);
            }
            return now();
        }).finally([this, timer_start_tp] {
            _logger.trace("on_timer(): rearming");
            _timer.arm(timer_start_tp + _refresh);
        });
    }

    std::vector<typename set_type::bucket_type> _buckets;
    size_t _current_buckets_count = initial_num_buckets;
    set_type _set;
    write_mutex_map_type _write_mutex_map;
    size_t _max_size;
    std::chrono::milliseconds _expiry;
    std::chrono::milliseconds _refresh;
    logging::logger& _logger;
    std::function<future<_Tp>(const _Key&)> _load;
    timer<lowres_clock> _timer;
};

}

<commit_msg>utils/loading_cache.hh: use intrusive list to store the lru entry<commit_after>/*
 * Copyright (C) 2016 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <chrono>
#include <unordered_map>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>

#include <seastar/core/timer.hh>

#include "utils/exceptions.hh"

namespace bi = boost::intrusive;

namespace utils {
// Simple variant of the "LoadingCache" used for permissions in origin.

typedef lowres_clock loading_cache_clock_type;
typedef bi::list_base_hook<bi::link_mode<bi::auto_unlink>> auto_unlink_list_hook;

template<typename _Tp, typename _Key, typename _Hash, typename _EqualPred>
class timestamped_val : public auto_unlink_list_hook, public bi::unordered_set_base_hook<bi::store_hash<true>> {
public:
    typedef bi::list<timestamped_val, bi::constant_time_size<false>> lru_list_type;
    typedef _Key key_type;
    typedef _Tp value_type;

private:
    std::experimental::optional<_Tp> _opt_value;
    loading_cache_clock_type::time_point _loaded;
    loading_cache_clock_type::time_point _last_read;
    lru_list_type& _lru_list; /// MRU item is at the front, LRU - at the back
    _Key _key;

public:
    struct key_eq {
       bool operator()(const _Key& k, const timestamped_val& c) const {
           return _EqualPred()(k, c.key());
       }

       bool operator()(const timestamped_val& c, const _Key& k) const {
           return _EqualPred()(c.key(), k);
       }
    };

    timestamped_val(lru_list_type& lru_list, const _Key& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _lru_list(lru_list)
        , _key(key) {}

    timestamped_val(lru_list_type& lru_list, _Key&& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _lru_list(lru_list)
        , _key(std::move(key)) {}

    timestamped_val(const timestamped_val&) = default;
    timestamped_val(timestamped_val&&) = default;

    // Make sure copy/move-assignments don't go through the template below
    timestamped_val& operator=(const timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&&) = default;

    template <typename U>
    timestamped_val& operator=(U&& new_val) {
        _opt_value = std::forward<U>(new_val);
        _loaded = loading_cache_clock_type::now();
        return *this;
    }

    const _Tp& value() {
        _last_read = loading_cache_clock_type::now();
        touch();
        return _opt_value.value();
    }

    explicit operator bool() const noexcept {
        return bool(_opt_value);
    }

    loading_cache_clock_type::time_point last_read() const noexcept {
        return _last_read;
    }

    loading_cache_clock_type::time_point loaded() const noexcept {
        return _loaded;
    }

    const _Key& key() const {
        return _key;
    }

    friend bool operator==(const timestamped_val& a, const timestamped_val& b){
        return _EqualPred()(a.key(), b.key());
    }

    friend std::size_t hash_value(const timestamped_val& v) {
        return _Hash()(v.key());
    }

private:
    /// Set the given item as the most recently used item.
    /// The MRU item is going to be at the front of the _lru_list, the LRU item - at the back.
    ///
    /// \param item item to set as MRU item
    void touch() noexcept {
        auto_unlink_list_hook::unlink();
        _lru_list.push_front(*this);
    }
};

class shared_mutex {
private:
    lw_shared_ptr<semaphore> _mutex_ptr;

public:
    shared_mutex() : _mutex_ptr(make_lw_shared<semaphore>(1)) {}
    semaphore& get() const noexcept {
        return *_mutex_ptr;
    }
};

template<typename _Key,
         typename _Tp,
         typename _Hash = std::hash<_Key>,
         typename _Pred = std::equal_to<_Key>,
         typename _Alloc = std::allocator<timestamped_val<_Tp, _Key, _Hash, _Pred>>,
         typename SharedMutexMapAlloc = std::allocator<std::pair<const _Key, shared_mutex>>>
class loading_cache {
private:
    typedef timestamped_val<_Tp, _Key, _Hash, _Pred> ts_value_type;
    typedef bi::unordered_set<ts_value_type, bi::power_2_buckets<true>, bi::compare_hash<true>> set_type;
    typedef std::unordered_map<_Key, shared_mutex, _Hash, _Pred, SharedMutexMapAlloc> write_mutex_map_type;
    typedef loading_cache<_Key, _Tp, _Hash, _Pred, _Alloc> _MyType;
    typedef typename ts_value_type::lru_list_type lru_list_type;
    typedef typename set_type::bucket_traits bi_set_bucket_traits;

    static constexpr int initial_num_buckets = 256;
    static constexpr int max_num_buckets = 1024 * 1024;

public:
    typedef _Tp value_type;
    typedef _Key key_type;
    typedef typename set_type::iterator iterator;

    template<typename Func>
    loading_cache(size_t max_size, std::chrono::milliseconds expiry, std::chrono::milliseconds refresh, logging::logger& logger, Func&& load)
                : _buckets(initial_num_buckets)
                , _set(bi_set_bucket_traits(_buckets.data(), _buckets.size()))
                , _max_size(max_size)
                , _expiry(expiry)
                , _refresh(refresh)
                , _logger(logger)
                , _load(std::forward<Func>(load)) {

        // If expiration period is zero - caching is disabled
        if (!caching_enabled()) {
            return;
        }

        // Sanity check: if expiration period is given then non-zero refresh period and maximal size are required
        if (_refresh == std::chrono::milliseconds(0) || _max_size == 0) {
            throw exceptions::configuration_exception("loading_cache: caching is enabled but refresh period and/or max_size are zero");
        }

        _timer.set_callback([this] { on_timer(); });
        _timer.arm(_refresh);
    }

    ~loading_cache() {
        _set.clear_and_dispose([] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
    }

    future<_Tp> get(const _Key& k) {
        // If caching is disabled - always load in the foreground
        if (!caching_enabled()) {
            return _load(k);
        }

        // If the key is not in the cache yet, then find_or_create() is going to
        // create a new uninitialized value in the map. If the value is already
        // in the cache (the fast path) simply return the value. Otherwise, take
        // the mutex and try to load the value (the slow path).
        iterator ts_value_it = find_or_create(k);
        if (*ts_value_it) {
            return make_ready_future<_Tp>(ts_value_it->value());
        } else {
            return slow_load(k);
        }
    }

private:
    bool caching_enabled() const {
        return _expiry != std::chrono::milliseconds(0);
    }

    /// Look for the entry with the given key. It it doesn't exist - create a new one and add it to the _set.
    ///
    /// \param k The key to look for
    ///
    /// \return An iterator to the value with the given key (always dirrerent from _set.end())
    template <typename KeyType>
    iterator find_or_create(KeyType&& k) {
        iterator i = _set.find(k, _Hash(), typename ts_value_type::key_eq());
        if (i == _set.end()) {
            ts_value_type* new_ts_val = _Alloc().allocate(1);
            new(new_ts_val) ts_value_type(_lru_list, std::forward<KeyType>(k));
            auto p = _set.insert(*new_ts_val);
            i = p.first;
        }

        return i;
    }

    static void destroy_ts_value(ts_value_type* val) {
        val->~ts_value_type();
        _Alloc().deallocate(val, 1);
    }

    future<_Tp> slow_load(const _Key& k) {
        // If the key is not in the cache yet, then _write_mutex_map[k] is going
        // to create a new value with the initialized mutex. The mutex is going
        // to serialize the producers and only the first one is going to
        // actually issue a load operation and initialize the value with the
        // received result. The rest are going to see (and read) the initialized
        // value when they enter the critical section.
        shared_mutex sm = _write_mutex_map[k];
        return with_semaphore(sm.get(), 1, [this, k] {
            iterator ts_value_it = find_or_create(k);
            if (*ts_value_it) {
                return make_ready_future<_Tp>(ts_value_it->value());
            }
            _logger.trace("{}: storing the value for the first time", k);
            return _load(k).then([this, k] (_Tp t) {
                // we have to "re-read" the _set here because the value may have been evicted by now
                iterator ts_value_it = find_or_create(std::move(k));
                *ts_value_it = std::move(t);
                return make_ready_future<_Tp>(ts_value_it->value());
            });
        }).finally([sm] {});
    }

    future<> reload(ts_value_type& ts_val) {
        return _load(ts_val.key()).then_wrapped([this, &ts_val] (auto&& f) {
            // The exceptions are related to the load operation itself.
            // We should ignore them for the background reads - if
            // they persist the value will age and will be reloaded in
            // the forground. If the foreground READ fails the error
            // will be propagated up to the user and will fail the
            // corresponding query.
            try {
                ts_val = f.get0();
            } catch (std::exception& e) {
                _logger.debug("{}: reload failed: {}", ts_val.key(), e.what());
            } catch (...) {
                _logger.debug("{}: reload failed: unknown error", ts_val.key());
            }
        });
    }

    void erase(iterator it) {
        _set.erase_and_dispose(it, [] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
        // no need to delete the item from _lru_list - it's auto-deleted
    }

    void drop_expired() {
        auto now = loading_cache_clock_type::now();
        _lru_list.remove_and_dispose_if([now, this] (const ts_value_type& v) {
            using namespace std::chrono;
            // An entry should be discarded if it hasn't been reloaded for too long or nobody cares about it anymore
            auto since_last_read = now - v.last_read();
            auto since_loaded = now - v.loaded();
            if (_expiry < since_last_read || _expiry < since_loaded) {
                _logger.trace("drop_expired(): {}: dropping the entry: _expiry {},  ms passed since: loaded {} last_read {}", v.key(), _expiry.count(), duration_cast<milliseconds>(since_loaded).count(), duration_cast<milliseconds>(since_last_read).count());
                return true;
            }
            return false;
        }, [this] (ts_value_type* p) {
            erase(_set.iterator_to(*p));
        });
    }

    // Shrink the cache to the _max_size discarding the least recently used items
    void shrink() {
        if (_set.size() > _max_size) {
            auto num_items_to_erase = _set.size() - _max_size;
            for (size_t i = 0; i < num_items_to_erase; ++i) {
                using namespace std::chrono;
                ts_value_type& ts_val = *_lru_list.rbegin();
                _logger.trace("shrink(): {}: dropping the entry: ms since last_read {}", ts_val.key(), duration_cast<milliseconds>(loading_cache_clock_type::now() - ts_val.last_read()).count());
                erase(_set.iterator_to(ts_val));
            }
        }
    }

    void rehash() {
        size_t new_buckets_count = 0;

        // Don't grow or shrink too fast even if there is a steep drop/growth in the number of elements in the set.
        // Exponential growth/backoff should be good enough.
        //
        // Try to keep the load factor between 0.25 and 1.0.
        if (_set.size() < _current_buckets_count / 4) {
            new_buckets_count = _current_buckets_count / 4;
        } else if (_set.size() > _current_buckets_count) {
            new_buckets_count = _current_buckets_count * 2;
        }

        if (new_buckets_count < initial_num_buckets || new_buckets_count > max_num_buckets) {
            return;
        }

        std::vector<typename set_type::bucket_type> new_buckets(new_buckets_count);
        _set.rehash(bi_set_bucket_traits(new_buckets.data(), new_buckets.size()));
        _logger.trace("rehash(): buckets count changed: {} -> {}", _current_buckets_count, new_buckets_count);

        _buckets.swap(new_buckets);
        _current_buckets_count = new_buckets_count;
    }

    void on_timer() {
        _logger.trace("on_timer(): start");

        auto timer_start_tp = loading_cache_clock_type::now();

        // Clear all cached mutexes
        _write_mutex_map.clear();

        // Clean up items that were not touched for the whole _expiry period.
        drop_expired();

        // Remove the least recently used items if map is too big.
        shrink();

        // check if rehashing is needed and do it if it is.
        rehash();

        // Reload all those which vlaue needs to be reloaded.
        parallel_for_each(_set.begin(), _set.end(), [this, curr_time = timer_start_tp] (auto& ts_val) {
            _logger.trace("on_timer(): {}: checking the value age", ts_val.key());
            if (ts_val && ts_val.loaded() + _refresh < curr_time) {
                _logger.trace("on_timer(): {}: reloading the value", ts_val.key());
                return this->reload(ts_val);
            }
            return now();
        }).finally([this, timer_start_tp] {
            _logger.trace("on_timer(): rearming");
            _timer.arm(timer_start_tp + _refresh);
        });
    }

    std::vector<typename set_type::bucket_type> _buckets;
    size_t _current_buckets_count = initial_num_buckets;
    set_type _set;
    write_mutex_map_type _write_mutex_map;
    lru_list_type _lru_list;
    size_t _max_size;
    std::chrono::milliseconds _expiry;
    std::chrono::milliseconds _refresh;
    logging::logger& _logger;
    std::function<future<_Tp>(const _Key&)> _load;
    timer<lowres_clock> _timer;
};

}

<|endoftext|>


<commit_before>
/*
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <stdint.h>
#include <memory>
#include "bytes.hh"
#include "utils/allocation_strategy.hh"

struct blob_storage {
    using size_type = uint32_t;
    using char_type = bytes_view::value_type;

    blob_storage** backref;
    size_type size;
    size_type frag_size;
    blob_storage* next;
    char_type data[];

    blob_storage(blob_storage** backref, size_type size, size_type frag_size) noexcept
        : backref(backref)
        , size(size)
        , frag_size(frag_size)
        , next(nullptr)
    {
        *backref = this;
    }

    blob_storage(blob_storage&& o) noexcept
        : backref(o.backref)
        , size(o.size)
        , frag_size(o.frag_size)
        , next(o.next)
    {
        *backref = this;
        o.next = nullptr;
        if (next) {
            next->backref = &next;
        }
        memcpy(data, o.data, size);
    }
} __attribute__((packed));

// A managed version of "bytes" (can be used with LSA).
class managed_bytes {
    static constexpr size_t max_inline_size = 15;
    struct small_blob {
        bytes_view::value_type data[max_inline_size];
        int8_t size; // -1 -> use blob_storage
    };
    union {
        blob_storage* ptr;
        small_blob small;
    } _u;
    static_assert(sizeof(small_blob) > sizeof(blob_storage*), "inline size too small");
private:
    bool external() const {
        return _u.small.size < 0;
    }
    size_t max_seg(allocation_strategy& alctr) {
        return alctr.preferred_max_contiguous_allocation() - sizeof(blob_storage);
    }
    void free_chain(blob_storage* p) {
        auto& alctr = current_allocator();
        while (p) {
            auto n = p->next;
            alctr.destroy(p);
            p = n;
        }
    }
public:
    using size_type = blob_storage::size_type;
    struct initialized_later {};

    managed_bytes() {
        _u.small.size = 0;
    }

    managed_bytes(const blob_storage::char_type* ptr, size_type size)
        : managed_bytes(bytes_view(ptr, size)) {}

    managed_bytes(const bytes& b) : managed_bytes(static_cast<bytes_view>(b)) {}

    managed_bytes(initialized_later, size_type size) {
        if (size <= max_inline_size) {
            _u.small.size = size;
        } else {
            _u.small.size = -1;
            auto& alctr = current_allocator();
            auto maxseg = max_seg(alctr);
            auto now = std::min(size_t(size), maxseg);
            void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                sizeof(blob_storage) + now, alignof(blob_storage));
            auto first = new (p) blob_storage(&_u.ptr, size, now);
            auto last = first;
            size -= now;
            try {
                while (size) {
                    auto now = std::min(size_t(size), maxseg);
                    void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                        sizeof(blob_storage) + now, alignof(blob_storage));
                    last = new (p) blob_storage(&last->next, 0, now);
                    size -= now;
                }
            } catch (...) {
                free_chain(first);
                throw;
            }
        }
    }

    managed_bytes(bytes_view v) : managed_bytes(initialized_later(), v.size()) {
        auto p = v.data();
        auto s = v.size();
        if (!external()) {
            memcpy(_u.small.data, p, s);
            return;
        }
        auto b = _u.ptr;
        while (s) {
            memcpy(b->data, p, b->frag_size);
            p += b->frag_size;
            s -= b->frag_size;
            b = b->next;
        }
        assert(!b);
    }

    managed_bytes(std::initializer_list<bytes::value_type> b) : managed_bytes(b.begin(), b.size()) {}

    ~managed_bytes() {
        if (external()) {
            free_chain(_u.ptr);
        }
    }

    managed_bytes(const managed_bytes& o) : managed_bytes(initialized_later(), o.size()) {
        if (!external()) {
            memcpy(data(), o.data(), size());
            return;
        }
        auto s = size();
        blob_storage* const* next_src = &o._u.ptr;
        blob_storage* blob_src = nullptr;
        size_type size_src = 0;
        size_type offs_src = 0;
        blob_storage** next_dst = &_u.ptr;
        blob_storage* blob_dst = nullptr;
        size_type size_dst = 0;
        size_type offs_dst = 0;
        while (s) {
            if (!size_src) {
                blob_src = *next_src;
                next_src = &blob_src->next;
                size_src = blob_src->frag_size;
                offs_src = 0;
            }
            if (!size_dst) {
                blob_dst = *next_dst;
                next_dst = &blob_dst->next;
                size_dst = blob_dst->frag_size;
                offs_dst = 0;
            }
            auto now = std::min(size_src, size_dst);
            memcpy(blob_dst->data + offs_dst, blob_src->data + offs_src, now);
            s -= now;
            offs_src += now; size_src -= now;
            offs_dst += now; size_dst -= now;
        }
        assert(size_src == 0 && size_dst == 0);
    }

    managed_bytes(managed_bytes&& o) noexcept
        : _u(o._u)
    {
        if (external()) {
            if (_u.ptr) {
                _u.ptr->backref = &_u.ptr;
            }
        }
        o._u.small.size = 0;
    }

    managed_bytes& operator=(managed_bytes&& o) {
        if (this != &o) {
            this->~managed_bytes();
            new (this) managed_bytes(std::move(o));
        }
        return *this;
    }

    managed_bytes& operator=(const managed_bytes& o) {
        if (this != &o) {
            // FIXME: not exception safe
            this->~managed_bytes();
            new (this) managed_bytes(o);
        }
        return *this;
    }

    bool operator==(const managed_bytes& o) const {
        return static_cast<bytes_view>(*this) == static_cast<bytes_view>(o);
    }

    bool operator!=(const managed_bytes& o) const {
        return !(*this == o);
    }

    operator bytes_view() const {
        return { data(), size() };
    }

    bytes_view::value_type& operator[](size_type index) {
        return data()[index];
    }

    const bytes_view::value_type& operator[](size_type index) const {
        return data()[index];
    }

    size_type size() const {
        if (external()) {
            return _u.ptr->size;
        } else {
            return _u.small.size;
        }
    }

    const blob_storage::char_type* begin() const {
        return data();
    }

    const blob_storage::char_type* end() const {
        return data() + size();
    }

    blob_storage::char_type* begin() {
        return data();
    }

    blob_storage::char_type* end() {
        return data() + size();
    }

    bool empty() const {
        return _u.small.size == 0;
    }

    blob_storage::char_type* data() {
        if (external()) {
            assert(!_u.ptr->next);  // must be linearized
            return _u.ptr->data;
        } else {
            return _u.small.data;
        }
    }

    const blob_storage::char_type* data() const {
        return const_cast<managed_bytes*>(this)->data();
    }

    void linearize() {
        if (!external() || !_u.ptr->next) {
            return;
        }
        auto& alctr = current_allocator();
        auto size = _u.ptr->size;
        void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
            sizeof(blob_storage) + size, alignof(blob_storage));
        auto old = _u.ptr;
        auto blob = new (p) blob_storage(&_u.ptr, size, size);
        auto pos = size_type(0);
        while (old) {
            memcpy(blob->data + pos, old->data, old->frag_size);
            pos += old->frag_size;
            auto next = old->next;
            alctr.destroy(old);
            old = next;
        }
        assert(pos == size);
    }

    void scatter() {
        if (!external()) {
            return;
        }
        if (_u.ptr->size <= max_seg(current_allocator())) {
            return;
        }
        *this = managed_bytes(*this);
    }
};

namespace std {

template <>
struct hash<managed_bytes> {
    size_t operator()(managed_bytes v) const {
        return hash<bytes_view>()(v);
    }
};

}
<commit_msg>managed_bytes: fix copy size in move constructor<commit_after>
/*
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <stdint.h>
#include <memory>
#include "bytes.hh"
#include "utils/allocation_strategy.hh"

struct blob_storage {
    using size_type = uint32_t;
    using char_type = bytes_view::value_type;

    blob_storage** backref;
    size_type size;
    size_type frag_size;
    blob_storage* next;
    char_type data[];

    blob_storage(blob_storage** backref, size_type size, size_type frag_size) noexcept
        : backref(backref)
        , size(size)
        , frag_size(frag_size)
        , next(nullptr)
    {
        *backref = this;
    }

    blob_storage(blob_storage&& o) noexcept
        : backref(o.backref)
        , size(o.size)
        , frag_size(o.frag_size)
        , next(o.next)
    {
        *backref = this;
        o.next = nullptr;
        if (next) {
            next->backref = &next;
        }
        memcpy(data, o.data, frag_size);
    }
} __attribute__((packed));

// A managed version of "bytes" (can be used with LSA).
class managed_bytes {
    static constexpr size_t max_inline_size = 15;
    struct small_blob {
        bytes_view::value_type data[max_inline_size];
        int8_t size; // -1 -> use blob_storage
    };
    union {
        blob_storage* ptr;
        small_blob small;
    } _u;
    static_assert(sizeof(small_blob) > sizeof(blob_storage*), "inline size too small");
private:
    bool external() const {
        return _u.small.size < 0;
    }
    size_t max_seg(allocation_strategy& alctr) {
        return alctr.preferred_max_contiguous_allocation() - sizeof(blob_storage);
    }
    void free_chain(blob_storage* p) {
        auto& alctr = current_allocator();
        while (p) {
            auto n = p->next;
            alctr.destroy(p);
            p = n;
        }
    }
public:
    using size_type = blob_storage::size_type;
    struct initialized_later {};

    managed_bytes() {
        _u.small.size = 0;
    }

    managed_bytes(const blob_storage::char_type* ptr, size_type size)
        : managed_bytes(bytes_view(ptr, size)) {}

    managed_bytes(const bytes& b) : managed_bytes(static_cast<bytes_view>(b)) {}

    managed_bytes(initialized_later, size_type size) {
        if (size <= max_inline_size) {
            _u.small.size = size;
        } else {
            _u.small.size = -1;
            auto& alctr = current_allocator();
            auto maxseg = max_seg(alctr);
            auto now = std::min(size_t(size), maxseg);
            void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                sizeof(blob_storage) + now, alignof(blob_storage));
            auto first = new (p) blob_storage(&_u.ptr, size, now);
            auto last = first;
            size -= now;
            try {
                while (size) {
                    auto now = std::min(size_t(size), maxseg);
                    void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                        sizeof(blob_storage) + now, alignof(blob_storage));
                    last = new (p) blob_storage(&last->next, 0, now);
                    size -= now;
                }
            } catch (...) {
                free_chain(first);
                throw;
            }
        }
    }

    managed_bytes(bytes_view v) : managed_bytes(initialized_later(), v.size()) {
        auto p = v.data();
        auto s = v.size();
        if (!external()) {
            memcpy(_u.small.data, p, s);
            return;
        }
        auto b = _u.ptr;
        while (s) {
            memcpy(b->data, p, b->frag_size);
            p += b->frag_size;
            s -= b->frag_size;
            b = b->next;
        }
        assert(!b);
    }

    managed_bytes(std::initializer_list<bytes::value_type> b) : managed_bytes(b.begin(), b.size()) {}

    ~managed_bytes() {
        if (external()) {
            free_chain(_u.ptr);
        }
    }

    managed_bytes(const managed_bytes& o) : managed_bytes(initialized_later(), o.size()) {
        if (!external()) {
            memcpy(data(), o.data(), size());
            return;
        }
        auto s = size();
        blob_storage* const* next_src = &o._u.ptr;
        blob_storage* blob_src = nullptr;
        size_type size_src = 0;
        size_type offs_src = 0;
        blob_storage** next_dst = &_u.ptr;
        blob_storage* blob_dst = nullptr;
        size_type size_dst = 0;
        size_type offs_dst = 0;
        while (s) {
            if (!size_src) {
                blob_src = *next_src;
                next_src = &blob_src->next;
                size_src = blob_src->frag_size;
                offs_src = 0;
            }
            if (!size_dst) {
                blob_dst = *next_dst;
                next_dst = &blob_dst->next;
                size_dst = blob_dst->frag_size;
                offs_dst = 0;
            }
            auto now = std::min(size_src, size_dst);
            memcpy(blob_dst->data + offs_dst, blob_src->data + offs_src, now);
            s -= now;
            offs_src += now; size_src -= now;
            offs_dst += now; size_dst -= now;
        }
        assert(size_src == 0 && size_dst == 0);
    }

    managed_bytes(managed_bytes&& o) noexcept
        : _u(o._u)
    {
        if (external()) {
            if (_u.ptr) {
                _u.ptr->backref = &_u.ptr;
            }
        }
        o._u.small.size = 0;
    }

    managed_bytes& operator=(managed_bytes&& o) {
        if (this != &o) {
            this->~managed_bytes();
            new (this) managed_bytes(std::move(o));
        }
        return *this;
    }

    managed_bytes& operator=(const managed_bytes& o) {
        if (this != &o) {
            // FIXME: not exception safe
            this->~managed_bytes();
            new (this) managed_bytes(o);
        }
        return *this;
    }

    bool operator==(const managed_bytes& o) const {
        return static_cast<bytes_view>(*this) == static_cast<bytes_view>(o);
    }

    bool operator!=(const managed_bytes& o) const {
        return !(*this == o);
    }

    operator bytes_view() const {
        return { data(), size() };
    }

    bytes_view::value_type& operator[](size_type index) {
        return data()[index];
    }

    const bytes_view::value_type& operator[](size_type index) const {
        return data()[index];
    }

    size_type size() const {
        if (external()) {
            return _u.ptr->size;
        } else {
            return _u.small.size;
        }
    }

    const blob_storage::char_type* begin() const {
        return data();
    }

    const blob_storage::char_type* end() const {
        return data() + size();
    }

    blob_storage::char_type* begin() {
        return data();
    }

    blob_storage::char_type* end() {
        return data() + size();
    }

    bool empty() const {
        return _u.small.size == 0;
    }

    blob_storage::char_type* data() {
        if (external()) {
            assert(!_u.ptr->next);  // must be linearized
            return _u.ptr->data;
        } else {
            return _u.small.data;
        }
    }

    const blob_storage::char_type* data() const {
        return const_cast<managed_bytes*>(this)->data();
    }

    void linearize() {
        if (!external() || !_u.ptr->next) {
            return;
        }
        auto& alctr = current_allocator();
        auto size = _u.ptr->size;
        void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
            sizeof(blob_storage) + size, alignof(blob_storage));
        auto old = _u.ptr;
        auto blob = new (p) blob_storage(&_u.ptr, size, size);
        auto pos = size_type(0);
        while (old) {
            memcpy(blob->data + pos, old->data, old->frag_size);
            pos += old->frag_size;
            auto next = old->next;
            alctr.destroy(old);
            old = next;
        }
        assert(pos == size);
    }

    void scatter() {
        if (!external()) {
            return;
        }
        if (_u.ptr->size <= max_seg(current_allocator())) {
            return;
        }
        *this = managed_bytes(*this);
    }
};

namespace std {

template <>
struct hash<managed_bytes> {
    size_t operator()(managed_bytes v) const {
        return hash<bytes_view>()(v);
    }
};

}
<|endoftext|>


<commit_before>
/*
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <stdint.h>
#include <memory>
#include "bytes.hh"
#include "utils/allocation_strategy.hh"
#include <seastar/core/unaligned.hh>
#include <unordered_map>
#include <type_traits>

struct blob_storage {
    using size_type = uint32_t;
    using char_type = bytes_view::value_type;

    blob_storage** backref;
    size_type size;
    size_type frag_size;
    blob_storage* next;
    char_type data[];

    blob_storage(blob_storage** backref, size_type size, size_type frag_size) noexcept
        : backref(backref)
        , size(size)
        , frag_size(frag_size)
        , next(nullptr)
    {
        *unaligned_cast<blob_storage**>(backref) = this;
    }

    blob_storage(blob_storage&& o) noexcept
        : backref(o.backref)
        , size(o.size)
        , frag_size(o.frag_size)
        , next(o.next)
    {
        *unaligned_cast<blob_storage**>(backref) = this;
        o.next = nullptr;
        if (next) {
            next->backref = &next;
        }
        memcpy(data, o.data, frag_size);
    }
} __attribute__((packed));

// A managed version of "bytes" (can be used with LSA).
class managed_bytes {
    struct linearization_context {
        unsigned _nesting = 0;
        // Map from first blob_storage address to linearized version
        // We use the blob_storage address to be insentive to moving
        // a managed_bytes object.
        std::unordered_map<const blob_storage*, std::unique_ptr<bytes_view::value_type[]>> _state;
        void enter() {
            ++_nesting;
        }
        void leave() {
            if (!--_nesting) {
                _state.clear();
            }
        }
        void forget(const blob_storage* p) noexcept;
    };
    static thread_local linearization_context _linearization_context;
public:
    struct linearization_context_guard {
        linearization_context_guard() {
            _linearization_context.enter();
        }
        ~linearization_context_guard() {
            _linearization_context.leave();
        }
    };
private:
    static constexpr size_t max_inline_size = 15;
    struct small_blob {
        bytes_view::value_type data[max_inline_size];
        int8_t size; // -1 -> use blob_storage
    };
    union {
        blob_storage* ptr;
        small_blob small;
    } _u;
    static_assert(sizeof(small_blob) > sizeof(blob_storage*), "inline size too small");
private:
    bool external() const {
        return _u.small.size < 0;
    }
    size_t max_seg(allocation_strategy& alctr) {
        return alctr.preferred_max_contiguous_allocation() - sizeof(blob_storage);
    }
    void free_chain(blob_storage* p) {
        if (p->next && _linearization_context._nesting) {
            _linearization_context.forget(p);
        }
        auto& alctr = current_allocator();
        while (p) {
            auto n = p->next;
            alctr.destroy(p);
            p = n;
        }
    }
    const bytes_view::value_type* read_linearize() const {
        if (!external()) {
            return _u.small.data;
        } else  if (!_u.ptr->next) {
            return _u.ptr->data;
        } else {
            return do_linearize();
        }
    }
    const bytes_view::value_type* do_linearize() const;
public:
    using size_type = blob_storage::size_type;
    struct initialized_later {};

    managed_bytes() {
        _u.small.size = 0;
    }

    managed_bytes(const blob_storage::char_type* ptr, size_type size)
        : managed_bytes(bytes_view(ptr, size)) {}

    managed_bytes(const bytes& b) : managed_bytes(static_cast<bytes_view>(b)) {}

    managed_bytes(initialized_later, size_type size) {
        if (size <= max_inline_size) {
            _u.small.size = size;
        } else {
            _u.small.size = -1;
            auto& alctr = current_allocator();
            auto maxseg = max_seg(alctr);
            auto now = std::min(size_t(size), maxseg);
            void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                sizeof(blob_storage) + now, alignof(blob_storage));
            auto first = new (p) blob_storage(&_u.ptr, size, now);
            auto last = first;
            size -= now;
            try {
                while (size) {
                    auto now = std::min(size_t(size), maxseg);
                    void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                        sizeof(blob_storage) + now, alignof(blob_storage));
                    last = new (p) blob_storage(&last->next, 0, now);
                    size -= now;
                }
            } catch (...) {
                free_chain(first);
                throw;
            }
        }
    }

    managed_bytes(bytes_view v) : managed_bytes(initialized_later(), v.size()) {
        if (!external()) {
            std::copy(v.begin(), v.end(), _u.small.data);
            return;
        }
        auto p = v.data();
        auto s = v.size();
        auto b = _u.ptr;
        while (s) {
            memcpy(b->data, p, b->frag_size);
            p += b->frag_size;
            s -= b->frag_size;
            b = b->next;
        }
        assert(!b);
    }

    managed_bytes(std::initializer_list<bytes::value_type> b) : managed_bytes(b.begin(), b.size()) {}

    ~managed_bytes() {
        if (external()) {
            free_chain(_u.ptr);
        }
    }

    managed_bytes(const managed_bytes& o) : managed_bytes(initialized_later(), o.size()) {
        if (!external()) {
            memcpy(data(), o.data(), size());
            return;
        }
        auto s = size();
        blob_storage* const* next_src = &o._u.ptr;
        blob_storage* blob_src = nullptr;
        size_type size_src = 0;
        size_type offs_src = 0;
        blob_storage** next_dst = &_u.ptr;
        blob_storage* blob_dst = nullptr;
        size_type size_dst = 0;
        size_type offs_dst = 0;
        while (s) {
            if (!size_src) {
                blob_src = *unaligned_cast<blob_storage**>(next_src);
                next_src = &blob_src->next;
                size_src = blob_src->frag_size;
                offs_src = 0;
            }
            if (!size_dst) {
                blob_dst = *unaligned_cast<blob_storage**>(next_dst);
                next_dst = &blob_dst->next;
                size_dst = blob_dst->frag_size;
                offs_dst = 0;
            }
            auto now = std::min(size_src, size_dst);
            memcpy(blob_dst->data + offs_dst, blob_src->data + offs_src, now);
            s -= now;
            offs_src += now; size_src -= now;
            offs_dst += now; size_dst -= now;
        }
        assert(size_src == 0 && size_dst == 0);
    }

    managed_bytes(managed_bytes&& o) noexcept
        : _u(o._u)
    {
        if (external()) {
            if (_u.ptr) {
                _u.ptr->backref = &_u.ptr;
            }
        }
        o._u.small.size = 0;
    }

    managed_bytes& operator=(managed_bytes&& o) {
        if (this != &o) {
            this->~managed_bytes();
            new (this) managed_bytes(std::move(o));
        }
        return *this;
    }

    managed_bytes& operator=(const managed_bytes& o) {
        if (this != &o) {
            // FIXME: not exception safe
            this->~managed_bytes();
            new (this) managed_bytes(o);
        }
        return *this;
    }

    bool operator==(const managed_bytes& o) const {
        if (size() != o.size()) {
            return false;
        }
        if (!external()) {
            return bytes_view(*this) == bytes_view(o);
        } else {
            auto a = _u.ptr;
            auto a_data = a->data;
            auto a_remain = a->frag_size;
            a = a->next;
            auto b = o._u.ptr;
            auto b_data = b->data;
            auto b_remain = b->frag_size;
            b = b->next;
            while (a_remain || b_remain) {
                auto now = std::min(a_remain, b_remain);
                if (bytes_view(a_data, now) != bytes_view(b_data, now)) {
                    return false;
                }
                a_data += now;
                a_remain -= now;
                if (!a_remain && a) {
                    a_data = a->data;
                    a_remain = a->frag_size;
                    a = a->next;
                }
                b_data += now;
                b_remain -= now;
                if (!b_remain && b) {
                    b_data = b->data;
                    b_remain = b->frag_size;
                    b = b->next;
                }
            }
            return true;
        }
    }

    bool operator!=(const managed_bytes& o) const {
        return !(*this == o);
    }

    operator bytes_view() const {
        return { data(), size() };
    }

    bytes_view::value_type& operator[](size_type index) {
        return data()[index];
    }

    const bytes_view::value_type& operator[](size_type index) const {
        return data()[index];
    }

    size_type size() const {
        if (external()) {
            return _u.ptr->size;
        } else {
            return _u.small.size;
        }
    }

    const blob_storage::char_type* begin() const {
        return data();
    }

    const blob_storage::char_type* end() const {
        return data() + size();
    }

    blob_storage::char_type* begin() {
        return data();
    }

    blob_storage::char_type* end() {
        return data() + size();
    }

    bool empty() const {
        return _u.small.size == 0;
    }

    blob_storage::char_type* data() {
        if (external()) {
            assert(!_u.ptr->next);  // must be linearized
            return _u.ptr->data;
        } else {
            return _u.small.data;
        }
    }

    const blob_storage::char_type* data() const {
        return read_linearize();
    }

    template <typename Func>
    friend std::result_of_t<Func()> with_linearized_managed_bytes(Func&& func);
};

// Run func() while ensuring that reads of managed_bytes objects are
// temporarlily linearized
template <typename Func>
inline
std::result_of_t<Func()>
with_linearized_managed_bytes(Func&& func) {
    managed_bytes::linearization_context_guard g;
    return func();
}

namespace std {

template <>
struct hash<managed_bytes> {
    size_t operator()(const managed_bytes& v) const {
        return hash<bytes_view>()(v);
    }
};

}
<commit_msg>managed_bytes: Make copy assignment exception-safe<commit_after>
/*
 * Copyright 2015 Cloudius Systems
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <stdint.h>
#include <memory>
#include "bytes.hh"
#include "utils/allocation_strategy.hh"
#include <seastar/core/unaligned.hh>
#include <unordered_map>
#include <type_traits>

struct blob_storage {
    using size_type = uint32_t;
    using char_type = bytes_view::value_type;

    blob_storage** backref;
    size_type size;
    size_type frag_size;
    blob_storage* next;
    char_type data[];

    blob_storage(blob_storage** backref, size_type size, size_type frag_size) noexcept
        : backref(backref)
        , size(size)
        , frag_size(frag_size)
        , next(nullptr)
    {
        *unaligned_cast<blob_storage**>(backref) = this;
    }

    blob_storage(blob_storage&& o) noexcept
        : backref(o.backref)
        , size(o.size)
        , frag_size(o.frag_size)
        , next(o.next)
    {
        *unaligned_cast<blob_storage**>(backref) = this;
        o.next = nullptr;
        if (next) {
            next->backref = &next;
        }
        memcpy(data, o.data, frag_size);
    }
} __attribute__((packed));

// A managed version of "bytes" (can be used with LSA).
class managed_bytes {
    struct linearization_context {
        unsigned _nesting = 0;
        // Map from first blob_storage address to linearized version
        // We use the blob_storage address to be insentive to moving
        // a managed_bytes object.
        std::unordered_map<const blob_storage*, std::unique_ptr<bytes_view::value_type[]>> _state;
        void enter() {
            ++_nesting;
        }
        void leave() {
            if (!--_nesting) {
                _state.clear();
            }
        }
        void forget(const blob_storage* p) noexcept;
    };
    static thread_local linearization_context _linearization_context;
public:
    struct linearization_context_guard {
        linearization_context_guard() {
            _linearization_context.enter();
        }
        ~linearization_context_guard() {
            _linearization_context.leave();
        }
    };
private:
    static constexpr size_t max_inline_size = 15;
    struct small_blob {
        bytes_view::value_type data[max_inline_size];
        int8_t size; // -1 -> use blob_storage
    };
    union {
        blob_storage* ptr;
        small_blob small;
    } _u;
    static_assert(sizeof(small_blob) > sizeof(blob_storage*), "inline size too small");
private:
    bool external() const {
        return _u.small.size < 0;
    }
    size_t max_seg(allocation_strategy& alctr) {
        return alctr.preferred_max_contiguous_allocation() - sizeof(blob_storage);
    }
    void free_chain(blob_storage* p) noexcept {
        if (p->next && _linearization_context._nesting) {
            _linearization_context.forget(p);
        }
        auto& alctr = current_allocator();
        while (p) {
            auto n = p->next;
            alctr.destroy(p);
            p = n;
        }
    }
    const bytes_view::value_type* read_linearize() const {
        if (!external()) {
            return _u.small.data;
        } else  if (!_u.ptr->next) {
            return _u.ptr->data;
        } else {
            return do_linearize();
        }
    }
    const bytes_view::value_type* do_linearize() const;
public:
    using size_type = blob_storage::size_type;
    struct initialized_later {};

    managed_bytes() {
        _u.small.size = 0;
    }

    managed_bytes(const blob_storage::char_type* ptr, size_type size)
        : managed_bytes(bytes_view(ptr, size)) {}

    managed_bytes(const bytes& b) : managed_bytes(static_cast<bytes_view>(b)) {}

    managed_bytes(initialized_later, size_type size) {
        if (size <= max_inline_size) {
            _u.small.size = size;
        } else {
            _u.small.size = -1;
            auto& alctr = current_allocator();
            auto maxseg = max_seg(alctr);
            auto now = std::min(size_t(size), maxseg);
            void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                sizeof(blob_storage) + now, alignof(blob_storage));
            auto first = new (p) blob_storage(&_u.ptr, size, now);
            auto last = first;
            size -= now;
            try {
                while (size) {
                    auto now = std::min(size_t(size), maxseg);
                    void* p = alctr.alloc(&standard_migrator<blob_storage>::object,
                        sizeof(blob_storage) + now, alignof(blob_storage));
                    last = new (p) blob_storage(&last->next, 0, now);
                    size -= now;
                }
            } catch (...) {
                free_chain(first);
                throw;
            }
        }
    }

    managed_bytes(bytes_view v) : managed_bytes(initialized_later(), v.size()) {
        if (!external()) {
            std::copy(v.begin(), v.end(), _u.small.data);
            return;
        }
        auto p = v.data();
        auto s = v.size();
        auto b = _u.ptr;
        while (s) {
            memcpy(b->data, p, b->frag_size);
            p += b->frag_size;
            s -= b->frag_size;
            b = b->next;
        }
        assert(!b);
    }

    managed_bytes(std::initializer_list<bytes::value_type> b) : managed_bytes(b.begin(), b.size()) {}

    ~managed_bytes() noexcept {
        if (external()) {
            free_chain(_u.ptr);
        }
    }

    managed_bytes(const managed_bytes& o) : managed_bytes(initialized_later(), o.size()) {
        if (!external()) {
            memcpy(data(), o.data(), size());
            return;
        }
        auto s = size();
        blob_storage* const* next_src = &o._u.ptr;
        blob_storage* blob_src = nullptr;
        size_type size_src = 0;
        size_type offs_src = 0;
        blob_storage** next_dst = &_u.ptr;
        blob_storage* blob_dst = nullptr;
        size_type size_dst = 0;
        size_type offs_dst = 0;
        while (s) {
            if (!size_src) {
                blob_src = *unaligned_cast<blob_storage**>(next_src);
                next_src = &blob_src->next;
                size_src = blob_src->frag_size;
                offs_src = 0;
            }
            if (!size_dst) {
                blob_dst = *unaligned_cast<blob_storage**>(next_dst);
                next_dst = &blob_dst->next;
                size_dst = blob_dst->frag_size;
                offs_dst = 0;
            }
            auto now = std::min(size_src, size_dst);
            memcpy(blob_dst->data + offs_dst, blob_src->data + offs_src, now);
            s -= now;
            offs_src += now; size_src -= now;
            offs_dst += now; size_dst -= now;
        }
        assert(size_src == 0 && size_dst == 0);
    }

    managed_bytes(managed_bytes&& o) noexcept
        : _u(o._u)
    {
        if (external()) {
            if (_u.ptr) {
                _u.ptr->backref = &_u.ptr;
            }
        }
        o._u.small.size = 0;
    }

    managed_bytes& operator=(managed_bytes&& o) {
        if (this != &o) {
            this->~managed_bytes();
            new (this) managed_bytes(std::move(o));
        }
        return *this;
    }

    managed_bytes& operator=(const managed_bytes& o) {
        if (this != &o) {
            managed_bytes tmp(o);
            this->~managed_bytes();
            new (this) managed_bytes(std::move(tmp));
        }
        return *this;
    }

    bool operator==(const managed_bytes& o) const {
        if (size() != o.size()) {
            return false;
        }
        if (!external()) {
            return bytes_view(*this) == bytes_view(o);
        } else {
            auto a = _u.ptr;
            auto a_data = a->data;
            auto a_remain = a->frag_size;
            a = a->next;
            auto b = o._u.ptr;
            auto b_data = b->data;
            auto b_remain = b->frag_size;
            b = b->next;
            while (a_remain || b_remain) {
                auto now = std::min(a_remain, b_remain);
                if (bytes_view(a_data, now) != bytes_view(b_data, now)) {
                    return false;
                }
                a_data += now;
                a_remain -= now;
                if (!a_remain && a) {
                    a_data = a->data;
                    a_remain = a->frag_size;
                    a = a->next;
                }
                b_data += now;
                b_remain -= now;
                if (!b_remain && b) {
                    b_data = b->data;
                    b_remain = b->frag_size;
                    b = b->next;
                }
            }
            return true;
        }
    }

    bool operator!=(const managed_bytes& o) const {
        return !(*this == o);
    }

    operator bytes_view() const {
        return { data(), size() };
    }

    bytes_view::value_type& operator[](size_type index) {
        return data()[index];
    }

    const bytes_view::value_type& operator[](size_type index) const {
        return data()[index];
    }

    size_type size() const {
        if (external()) {
            return _u.ptr->size;
        } else {
            return _u.small.size;
        }
    }

    const blob_storage::char_type* begin() const {
        return data();
    }

    const blob_storage::char_type* end() const {
        return data() + size();
    }

    blob_storage::char_type* begin() {
        return data();
    }

    blob_storage::char_type* end() {
        return data() + size();
    }

    bool empty() const {
        return _u.small.size == 0;
    }

    blob_storage::char_type* data() {
        if (external()) {
            assert(!_u.ptr->next);  // must be linearized
            return _u.ptr->data;
        } else {
            return _u.small.data;
        }
    }

    const blob_storage::char_type* data() const {
        return read_linearize();
    }

    template <typename Func>
    friend std::result_of_t<Func()> with_linearized_managed_bytes(Func&& func);
};

// Run func() while ensuring that reads of managed_bytes objects are
// temporarlily linearized
template <typename Func>
inline
std::result_of_t<Func()>
with_linearized_managed_bytes(Func&& func) {
    managed_bytes::linearization_context_guard g;
    return func();
}

namespace std {

template <>
struct hash<managed_bytes> {
    size_t operator()(const managed_bytes& v) const {
        return hash<bytes_view>()(v);
    }
};

}
<|endoftext|>