<commit_before>/*
 * Copyright (C) 2017 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

// A vector-like container that uses discontiguous storage. Provides fast random access,
// the ability to append at the end, and limited contiguous allocations.
//
// std::deque would be a good fit, except the backing array can grow quite large.

#include <boost/container/small_vector.hpp>
#include <boost/range/algorithm/equal.hpp>
#include <boost/algorithm/clamp.hpp>
#include <boost/version.hpp>
#include <memory>
#include <type_traits>
#include <iterator>
#include <utility>
#include <algorithm>
#include <stdexcept>

namespace utils {

namespace internal {

#if BOOST_VERSION >= 106000

template <typename T, size_t N>
using boost_small_vector = boost::container::small_vector<T, N>;

#else

// Older versions of boost have a double-free bug, so use std::vector instead.
template <typename T, size_t N>
using boost_small_vector = std::vector<T>;

#endif

}

struct chunked_vector_free_deleter {
    void operator()(void* x) const { ::free(x); }
};

template <typename T, size_t max_contiguous_allocation = 128*1024>
class chunked_vector {
    static_assert(std::is_nothrow_move_constructible<T>::value, "T must be nothrow move constructible");
    using chunk_ptr = std::unique_ptr<T[], chunked_vector_free_deleter>;
    // Each chunk holds max_chunk_capacity() items, except possibly the last
    internal::boost_small_vector<chunk_ptr, 1> _chunks;
    size_t _size = 0;
    size_t _capacity = 0;
private:
    static size_t max_chunk_capacity() {
        return std::max(max_contiguous_allocation / sizeof(T), size_t(1));
    }
    void reserve_for_push_back() {
        if (_size == _capacity) {
            do_reserve_for_push_back();
        }
    }
    void do_reserve_for_push_back();
    void make_room(size_t n);
    chunk_ptr new_chunk(size_t n);
    T* addr(size_t i) const {
        return &_chunks[i / max_chunk_capacity()][i % max_chunk_capacity()];
    }
    void check_bounds(size_t i) const {
        if (i >= _size) {
            throw std::out_of_range("chunked_vector out of range access");
        }
    }
    static void migrate(T* begin, T* end, T* result);
public:
    using value_type = T;
    using size_type = size_t;
    using difference_type = ssize_t;
    using reference = T&;
    using const_reference = const T&;
    using pointer = T*;
    using const_pointer = const T*;
public:
    chunked_vector() = default;
    chunked_vector(const chunked_vector& x);
    chunked_vector(chunked_vector&& x) noexcept;
    template <typename Iterator>
    chunked_vector(Iterator begin, Iterator end);
    explicit chunked_vector(size_t n, const T& value = T());
    ~chunked_vector();
    chunked_vector& operator=(const chunked_vector& x);
    chunked_vector& operator=(chunked_vector&& x) noexcept;

    bool empty() const {
        return !_size;
    }
    size_t size() const {
        return _size;
    }
    T& operator[](size_t i) {
        return *addr(i);
    }
    const T& operator[](size_t i) const {
        return *addr(i);
    }
    T& at(size_t i) {
        check_bounds(i);
        return *addr(i);
    }
    const T& at(size_t i) const {
        check_bounds(i);
        return *addr(i);
    }

    void push_back(const T& x) {
        reserve_for_push_back();
        new (addr(_size)) T(x);
        ++_size;
    }
    void push_back(T&& x) {
        reserve_for_push_back();
        new (addr(_size)) T(std::move(x));
        ++_size;
    }
    template <typename... Args>
    T& emplace_back(Args&&... args) {
        reserve_for_push_back();
        auto& ret = *new (addr(_size)) T(std::forward<Args>(args)...);
        ++_size;
        return ret;
    }
    void pop_back() {
        --_size;
        addr(_size)->~T();
    }
    const T& back() const {
        return *addr(_size - 1);
    }
    T& back() {
        return *addr(_size - 1);
    }

    void clear();
    void shrink_to_fit();
    void resize(size_t n);
    void reserve(size_t n) {
        if (n > _capacity) {
            make_room(n);
        }
    }
public:
    template <class ValueType>
    class iterator_type {
        const chunk_ptr* _chunks;
        size_t _i;
    public:
        using iterator_category = std::random_access_iterator_tag;
        using value_type = ValueType;
        using difference_type = ssize_t;
        using pointer = ValueType*;
        using reference = ValueType&;
    private:
        pointer addr() const {
            return &_chunks[_i / max_chunk_capacity()][_i % max_chunk_capacity()];
        }
        iterator_type(const chunk_ptr* chunks, size_t i) : _chunks(chunks), _i(i) {}
    public:
        iterator_type() = default;
        iterator_type(const iterator_type<std::remove_const_t<ValueType>>& x) : _chunks(x._chunks), _i(x._i) {} // needed for iterator->const_iterator conversion
        reference operator*() const {
            return *addr();
        }
        pointer operator->() const {
            return addr();
        }
        reference operator[](ssize_t n) const {
            return *(*this + n);
        }
        iterator_type& operator++() {
            ++_i;
            return *this;
        }
        iterator_type operator++(int) {
            auto x = *this;
            ++_i;
            return x;
        }
        iterator_type& operator--() {
            --_i;
            return *this;
        }
        iterator_type operator--(int) {
            auto x = *this;
            --_i;
            return x;
        }
        iterator_type& operator+=(ssize_t n) {
            _i += n;
            return *this;
        }
        iterator_type& operator-=(ssize_t n) {
            _i -= n;
            return *this;
        }
        iterator_type operator+(ssize_t n) const {
            auto x = *this;
            return x += n;
        }
        iterator_type operator-(ssize_t n) const {
            auto x = *this;
            return x -= n;
        }
        friend iterator_type operator+(ssize_t n, iterator_type a) {
            return a + n;
        }
        friend ssize_t operator-(iterator_type a, iterator_type b) {
            return a._i - b._i;
        }
        bool operator==(iterator_type x) const {
            return _i == x._i;
        }
        bool operator!=(iterator_type x) const {
            return _i != x._i;
        }
        bool operator<(iterator_type x) const {
            return _i < x._i;
        }
        bool operator<=(iterator_type x) const {
            return _i <= x._i;
        }
        bool operator>(iterator_type x) const {
            return _i > x._i;
        }
        bool operator>=(iterator_type x) const {
            return _i >= x._i;
        }
        friend class chunked_vector;
    };
    using iterator = iterator_type<T>;
    using const_iterator = iterator_type<const T>;
public:
    iterator begin() const { return iterator(_chunks.data(), 0); }
    iterator end() const { return iterator(_chunks.data(), _size); }
    const_iterator cbegin() const { return const_iterator(_chunks.data(), 0); }
    const_iterator cend() const { return const_iterator(_chunks.data(), _size); }
public:
    bool operator==(const chunked_vector& x) const {
        return boost::equal(*this, x);
    }
    bool operator!=(const chunked_vector& x) const {
        return !operator==(x);
    }
};

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(const chunked_vector& x)
        : chunked_vector() {
    reserve(x.size());
    std::copy(x.begin(), x.end(), std::back_inserter(*this));
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(chunked_vector&& x) noexcept
        : _chunks(std::exchange(x._chunks, {}))
        , _size(std::exchange(x._size, 0))
        , _capacity(std::exchange(x._capacity, 0)) {
}

template <typename T, size_t max_contiguous_allocation>
template <typename Iterator>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(Iterator begin, Iterator end)
        : chunked_vector() {
    auto is_random_access = std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>::value;
    if (is_random_access) {
        reserve(std::distance(begin, end));
    }
    std::copy(begin, end, std::back_inserter(*this));
    if (!is_random_access) {
        shrink_to_fit();
    }
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(size_t n, const T& value) {
    reserve(n);
    std::fill_n(std::back_inserter(*this), n, value);
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>&
chunked_vector<T, max_contiguous_allocation>::operator=(const chunked_vector& x) {
    auto tmp = chunked_vector(x);
    return *this = std::move(tmp);
}

template <typename T, size_t max_contiguous_allocation>
inline
chunked_vector<T, max_contiguous_allocation>&
chunked_vector<T, max_contiguous_allocation>::operator=(chunked_vector&& x) noexcept {
    if (this != &x) {
        this->~chunked_vector();
        new (this) chunked_vector(std::move(x));
    }
    return *this;
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::~chunked_vector() {
    if constexpr (!std::is_trivially_destructible_v<T>) {
        for (auto i = size_t(0); i != _size; ++i) {
            addr(i)->~T();
        }
    }
}

template <typename T, size_t max_contiguous_allocation>
typename chunked_vector<T, max_contiguous_allocation>::chunk_ptr
chunked_vector<T, max_contiguous_allocation>::new_chunk(size_t n) {
    auto p = malloc(n * sizeof(T));
    if (!p) {
        throw std::bad_alloc();
    }
    return chunk_ptr(reinterpret_cast<T*>(p));
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::migrate(T* begin, T* end, T* result) {
    while (begin != end) {
        new (result) T(std::move(*begin));
        begin->~T();
        ++begin;
        ++result;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::make_room(size_t n) {
    // First, if the last chunk is below max_chunk_capacity(), enlarge it

    auto last_chunk_capacity_deficit = _chunks.size() * max_chunk_capacity() - _capacity;
    if (last_chunk_capacity_deficit) {
        auto last_chunk_capacity = max_chunk_capacity() - last_chunk_capacity_deficit;
        auto capacity_increase = std::min(last_chunk_capacity_deficit, n - _capacity);
        auto new_last_chunk_capacity = last_chunk_capacity + capacity_increase;
        // FIXME: realloc? maybe not worth the complication; only works for PODs
        auto new_last_chunk = new_chunk(new_last_chunk_capacity);
        migrate(addr(_capacity - last_chunk_capacity), addr(_size), new_last_chunk.get());
        _chunks.back() = std::move(new_last_chunk);
        _capacity += capacity_increase;
    }

    // Reduce reallocations in the _chunks vector

    auto nr_chunks = (n + max_chunk_capacity() - 1) / max_chunk_capacity();
    _chunks.reserve(nr_chunks);

    // Add more chunks as needed

    while (_capacity < n) {
        auto now = std::min(n - _capacity, max_chunk_capacity());
        _chunks.push_back(new_chunk(now));
        _capacity += now;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::do_reserve_for_push_back() {
    if (_capacity == 0) {
        // allocate a bit of room in case utilization will be low
        reserve(boost::algorithm::clamp(512 / sizeof(T), 1, max_chunk_capacity()));
    } else if (_capacity < max_chunk_capacity() / 2) {
        // exponential increase when only one chunk to reduce copying
        reserve(_capacity * 2);
    } else {
        // add a chunk at a time later, since no copying will take place
        reserve((_capacity / max_chunk_capacity() + 1) * max_chunk_capacity());
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::resize(size_t n) {
    reserve(n);
    // FIXME: construct whole chunks at once
    while (_size > n) {
        pop_back();
    }
    while (_size < n) {
        push_back(T{});
    }
    shrink_to_fit();
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::shrink_to_fit() {
    if (_chunks.empty()) {
        return;
    }
    while (!_chunks.empty() && _size <= (_chunks.size() - 1) * max_chunk_capacity()) {
        _chunks.pop_back();
        _capacity = _chunks.size() * max_chunk_capacity();
    }

    auto overcapacity = _size - _capacity;
    if (overcapacity) {
        auto new_last_chunk_capacity = _size - (_chunks.size() - 1) * max_chunk_capacity();
        // FIXME: realloc? maybe not worth the complication; only works for PODs
        auto new_last_chunk = new_chunk(new_last_chunk_capacity);
        migrate(addr((_chunks.size() - 1) * max_chunk_capacity()), addr(_size), new_last_chunk.get());
        _chunks.back() = std::move(new_last_chunk);
        _capacity = _size;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::clear() {
    resize(0);
}

}
<commit_msg>chunked_vector: exports its current memory usage<commit_after>/*
 * Copyright (C) 2017 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

// A vector-like container that uses discontiguous storage. Provides fast random access,
// the ability to append at the end, and limited contiguous allocations.
//
// std::deque would be a good fit, except the backing array can grow quite large.

#include <boost/container/small_vector.hpp>
#include <boost/range/algorithm/equal.hpp>
#include <boost/algorithm/clamp.hpp>
#include <boost/version.hpp>
#include <memory>
#include <type_traits>
#include <iterator>
#include <utility>
#include <algorithm>
#include <stdexcept>

namespace utils {

namespace internal {

#if BOOST_VERSION >= 106000

template <typename T, size_t N>
using boost_small_vector = boost::container::small_vector<T, N>;

#else

// Older versions of boost have a double-free bug, so use std::vector instead.
template <typename T, size_t N>
using boost_small_vector = std::vector<T>;

#endif

}

struct chunked_vector_free_deleter {
    void operator()(void* x) const { ::free(x); }
};

template <typename T, size_t max_contiguous_allocation = 128*1024>
class chunked_vector {
    static_assert(std::is_nothrow_move_constructible<T>::value, "T must be nothrow move constructible");
    using chunk_ptr = std::unique_ptr<T[], chunked_vector_free_deleter>;
    // Each chunk holds max_chunk_capacity() items, except possibly the last
    internal::boost_small_vector<chunk_ptr, 1> _chunks;
    size_t _size = 0;
    size_t _capacity = 0;
private:
    static size_t max_chunk_capacity() {
        return std::max(max_contiguous_allocation / sizeof(T), size_t(1));
    }
    void reserve_for_push_back() {
        if (_size == _capacity) {
            do_reserve_for_push_back();
        }
    }
    void do_reserve_for_push_back();
    void make_room(size_t n);
    chunk_ptr new_chunk(size_t n);
    T* addr(size_t i) const {
        return &_chunks[i / max_chunk_capacity()][i % max_chunk_capacity()];
    }
    void check_bounds(size_t i) const {
        if (i >= _size) {
            throw std::out_of_range("chunked_vector out of range access");
        }
    }
    static void migrate(T* begin, T* end, T* result);
public:
    using value_type = T;
    using size_type = size_t;
    using difference_type = ssize_t;
    using reference = T&;
    using const_reference = const T&;
    using pointer = T*;
    using const_pointer = const T*;
public:
    chunked_vector() = default;
    chunked_vector(const chunked_vector& x);
    chunked_vector(chunked_vector&& x) noexcept;
    template <typename Iterator>
    chunked_vector(Iterator begin, Iterator end);
    explicit chunked_vector(size_t n, const T& value = T());
    ~chunked_vector();
    chunked_vector& operator=(const chunked_vector& x);
    chunked_vector& operator=(chunked_vector&& x) noexcept;

    bool empty() const {
        return !_size;
    }
    size_t size() const {
        return _size;
    }
    T& operator[](size_t i) {
        return *addr(i);
    }
    const T& operator[](size_t i) const {
        return *addr(i);
    }
    T& at(size_t i) {
        check_bounds(i);
        return *addr(i);
    }
    const T& at(size_t i) const {
        check_bounds(i);
        return *addr(i);
    }

    void push_back(const T& x) {
        reserve_for_push_back();
        new (addr(_size)) T(x);
        ++_size;
    }
    void push_back(T&& x) {
        reserve_for_push_back();
        new (addr(_size)) T(std::move(x));
        ++_size;
    }
    template <typename... Args>
    T& emplace_back(Args&&... args) {
        reserve_for_push_back();
        auto& ret = *new (addr(_size)) T(std::forward<Args>(args)...);
        ++_size;
        return ret;
    }
    void pop_back() {
        --_size;
        addr(_size)->~T();
    }
    const T& back() const {
        return *addr(_size - 1);
    }
    T& back() {
        return *addr(_size - 1);
    }

    void clear();
    void shrink_to_fit();
    void resize(size_t n);
    void reserve(size_t n) {
        if (n > _capacity) {
            make_room(n);
        }
    }

    size_t memory_size() const {
        return _capacity * sizeof(T);
    }
public:
    template <class ValueType>
    class iterator_type {
        const chunk_ptr* _chunks;
        size_t _i;
    public:
        using iterator_category = std::random_access_iterator_tag;
        using value_type = ValueType;
        using difference_type = ssize_t;
        using pointer = ValueType*;
        using reference = ValueType&;
    private:
        pointer addr() const {
            return &_chunks[_i / max_chunk_capacity()][_i % max_chunk_capacity()];
        }
        iterator_type(const chunk_ptr* chunks, size_t i) : _chunks(chunks), _i(i) {}
    public:
        iterator_type() = default;
        iterator_type(const iterator_type<std::remove_const_t<ValueType>>& x) : _chunks(x._chunks), _i(x._i) {} // needed for iterator->const_iterator conversion
        reference operator*() const {
            return *addr();
        }
        pointer operator->() const {
            return addr();
        }
        reference operator[](ssize_t n) const {
            return *(*this + n);
        }
        iterator_type& operator++() {
            ++_i;
            return *this;
        }
        iterator_type operator++(int) {
            auto x = *this;
            ++_i;
            return x;
        }
        iterator_type& operator--() {
            --_i;
            return *this;
        }
        iterator_type operator--(int) {
            auto x = *this;
            --_i;
            return x;
        }
        iterator_type& operator+=(ssize_t n) {
            _i += n;
            return *this;
        }
        iterator_type& operator-=(ssize_t n) {
            _i -= n;
            return *this;
        }
        iterator_type operator+(ssize_t n) const {
            auto x = *this;
            return x += n;
        }
        iterator_type operator-(ssize_t n) const {
            auto x = *this;
            return x -= n;
        }
        friend iterator_type operator+(ssize_t n, iterator_type a) {
            return a + n;
        }
        friend ssize_t operator-(iterator_type a, iterator_type b) {
            return a._i - b._i;
        }
        bool operator==(iterator_type x) const {
            return _i == x._i;
        }
        bool operator!=(iterator_type x) const {
            return _i != x._i;
        }
        bool operator<(iterator_type x) const {
            return _i < x._i;
        }
        bool operator<=(iterator_type x) const {
            return _i <= x._i;
        }
        bool operator>(iterator_type x) const {
            return _i > x._i;
        }
        bool operator>=(iterator_type x) const {
            return _i >= x._i;
        }
        friend class chunked_vector;
    };
    using iterator = iterator_type<T>;
    using const_iterator = iterator_type<const T>;
public:
    iterator begin() const { return iterator(_chunks.data(), 0); }
    iterator end() const { return iterator(_chunks.data(), _size); }
    const_iterator cbegin() const { return const_iterator(_chunks.data(), 0); }
    const_iterator cend() const { return const_iterator(_chunks.data(), _size); }
public:
    bool operator==(const chunked_vector& x) const {
        return boost::equal(*this, x);
    }
    bool operator!=(const chunked_vector& x) const {
        return !operator==(x);
    }
};

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(const chunked_vector& x)
        : chunked_vector() {
    reserve(x.size());
    std::copy(x.begin(), x.end(), std::back_inserter(*this));
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(chunked_vector&& x) noexcept
        : _chunks(std::exchange(x._chunks, {}))
        , _size(std::exchange(x._size, 0))
        , _capacity(std::exchange(x._capacity, 0)) {
}

template <typename T, size_t max_contiguous_allocation>
template <typename Iterator>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(Iterator begin, Iterator end)
        : chunked_vector() {
    auto is_random_access = std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<Iterator>::iterator_category>::value;
    if (is_random_access) {
        reserve(std::distance(begin, end));
    }
    std::copy(begin, end, std::back_inserter(*this));
    if (!is_random_access) {
        shrink_to_fit();
    }
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(size_t n, const T& value) {
    reserve(n);
    std::fill_n(std::back_inserter(*this), n, value);
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>&
chunked_vector<T, max_contiguous_allocation>::operator=(const chunked_vector& x) {
    auto tmp = chunked_vector(x);
    return *this = std::move(tmp);
}

template <typename T, size_t max_contiguous_allocation>
inline
chunked_vector<T, max_contiguous_allocation>&
chunked_vector<T, max_contiguous_allocation>::operator=(chunked_vector&& x) noexcept {
    if (this != &x) {
        this->~chunked_vector();
        new (this) chunked_vector(std::move(x));
    }
    return *this;
}

template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::~chunked_vector() {
    if constexpr (!std::is_trivially_destructible_v<T>) {
        for (auto i = size_t(0); i != _size; ++i) {
            addr(i)->~T();
        }
    }
}

template <typename T, size_t max_contiguous_allocation>
typename chunked_vector<T, max_contiguous_allocation>::chunk_ptr
chunked_vector<T, max_contiguous_allocation>::new_chunk(size_t n) {
    auto p = malloc(n * sizeof(T));
    if (!p) {
        throw std::bad_alloc();
    }
    return chunk_ptr(reinterpret_cast<T*>(p));
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::migrate(T* begin, T* end, T* result) {
    while (begin != end) {
        new (result) T(std::move(*begin));
        begin->~T();
        ++begin;
        ++result;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::make_room(size_t n) {
    // First, if the last chunk is below max_chunk_capacity(), enlarge it

    auto last_chunk_capacity_deficit = _chunks.size() * max_chunk_capacity() - _capacity;
    if (last_chunk_capacity_deficit) {
        auto last_chunk_capacity = max_chunk_capacity() - last_chunk_capacity_deficit;
        auto capacity_increase = std::min(last_chunk_capacity_deficit, n - _capacity);
        auto new_last_chunk_capacity = last_chunk_capacity + capacity_increase;
        // FIXME: realloc? maybe not worth the complication; only works for PODs
        auto new_last_chunk = new_chunk(new_last_chunk_capacity);
        migrate(addr(_capacity - last_chunk_capacity), addr(_size), new_last_chunk.get());
        _chunks.back() = std::move(new_last_chunk);
        _capacity += capacity_increase;
    }

    // Reduce reallocations in the _chunks vector

    auto nr_chunks = (n + max_chunk_capacity() - 1) / max_chunk_capacity();
    _chunks.reserve(nr_chunks);

    // Add more chunks as needed

    while (_capacity < n) {
        auto now = std::min(n - _capacity, max_chunk_capacity());
        _chunks.push_back(new_chunk(now));
        _capacity += now;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::do_reserve_for_push_back() {
    if (_capacity == 0) {
        // allocate a bit of room in case utilization will be low
        reserve(boost::algorithm::clamp(512 / sizeof(T), 1, max_chunk_capacity()));
    } else if (_capacity < max_chunk_capacity() / 2) {
        // exponential increase when only one chunk to reduce copying
        reserve(_capacity * 2);
    } else {
        // add a chunk at a time later, since no copying will take place
        reserve((_capacity / max_chunk_capacity() + 1) * max_chunk_capacity());
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::resize(size_t n) {
    reserve(n);
    // FIXME: construct whole chunks at once
    while (_size > n) {
        pop_back();
    }
    while (_size < n) {
        push_back(T{});
    }
    shrink_to_fit();
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::shrink_to_fit() {
    if (_chunks.empty()) {
        return;
    }
    while (!_chunks.empty() && _size <= (_chunks.size() - 1) * max_chunk_capacity()) {
        _chunks.pop_back();
        _capacity = _chunks.size() * max_chunk_capacity();
    }

    auto overcapacity = _size - _capacity;
    if (overcapacity) {
        auto new_last_chunk_capacity = _size - (_chunks.size() - 1) * max_chunk_capacity();
        // FIXME: realloc? maybe not worth the complication; only works for PODs
        auto new_last_chunk = new_chunk(new_last_chunk_capacity);
        migrate(addr((_chunks.size() - 1) * max_chunk_capacity()), addr(_size), new_last_chunk.get());
        _chunks.back() = std::move(new_last_chunk);
        _capacity = _size;
    }
}

template <typename T, size_t max_contiguous_allocation>
void
chunked_vector<T, max_contiguous_allocation>::clear() {
    resize(0);
}

}
<|endoftext|>


<commit_before>/**
 * @file llhost_test.cpp
 * @author Adroit
 * @date 2007-02
 * @brief llhost test cases.
 *
 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "../llhost.h"

#include "../test/lltut.h"

namespace tut
{
	struct host_data
	{
	};
	typedef test_group<host_data> host_test;
	typedef host_test::object host_object;
	tut::host_test host_testcase("LLHost");

	template<> template<>
	void host_object::test<1>()
	{
		LLHost host;
		ensure("IP address is not NULL", (0 == host.getAddress()) && (0 == host.getPort()) && !host.isOk());
	}
	template<> template<>
	void host_object::test<2>()
	{
		U32 ip_addr = 0xc098017d;
		U32 port = 8080;
		LLHost host(ip_addr, port);
		ensure("IP address is invalid", ip_addr == host.getAddress());
		ensure("Port Number is invalid", port == host.getPort());
		ensure("IP address and port number both should be ok", host.isOk());
	}

	template<> template<>
	void host_object::test<3>()
	{
		const char* str = "192.168.1.1";
		U32 port = 8080;
		LLHost host(str, port);
		ensure("IP address could not be processed", (host.getAddress() == ip_string_to_u32(str)));
		ensure("Port Number is invalid", (port == host.getPort()));
	}

	template<> template<>
	void host_object::test<4>()
	{
		U32 ip = ip_string_to_u32("192.168.1.1");
		U32 port = 22;
		U64 ip_port = (((U64) ip) << 32) | port;
		LLHost host(ip_port);
		ensure("IP address is invalid", ip == host.getAddress());
		ensure("Port Number is invalid", port == host.getPort());
	}

	template<> template<>
	void host_object::test<5>()
	{
		std::string ip_port_string = "192.168.1.1:8080";
		U32 ip = ip_string_to_u32("192.168.1.1");
		U32 port = 8080;

		LLHost host(ip_port_string);
		ensure("IP address from IP:port is invalid", ip == host.getAddress());
		ensure("Port Number from from IP:port is invalid", port == host.getPort());
	}

	template<> template<>
	void host_object::test<6>()
	{
		U32 ip = 0xc098017d, port = 8080;
		LLHost host;
		host.set(ip,port);
		ensure("IP address is invalid", (ip == host.getAddress()));
		ensure("Port Number is invalid", (port == host.getPort()));
	}

	template<> template<>
	void host_object::test<7>()
	{
		const char* str = "192.168.1.1";
		U32 port = 8080, ip;
		LLHost host;
		host.set(str,port);
		ip = ip_string_to_u32(str);
		ensure("IP address is invalid", (ip == host.getAddress()));
		ensure("Port Number is invalid", (port == host.getPort()));

		str = "64.233.187.99";
		ip = ip_string_to_u32(str);
		host.setAddress(str);
		ensure("IP address is invalid", (ip == host.getAddress()));

		ip = 0xc098017b;
		host.setAddress(ip);
		ensure("IP address is invalid", (ip == host.getAddress()));
		// should still use the old port
		ensure("Port Number is invalid", (port == host.getPort()));

		port = 8084;
		host.setPort(port);
		ensure("Port Number is invalid", (port == host.getPort()));
		// should still use the old address
		ensure("IP address is invalid", (ip == host.getAddress()));
	}

	template<> template<>
	void host_object::test<8>()
	{
		const std::string str("192.168.1.1");
		U32 port = 8080;
		LLHost host;
		host.set(str,port);

		std::string ip_string = host.getIPString();
		ensure("Function Failed", (ip_string == str));

		std::string ip_string_port = host.getIPandPort();
		ensure("Function Failed", (ip_string_port == "192.168.1.1:8080"));
	}

//	getHostName()  and setHostByName
	template<> template<>
	void host_object::test<9>()
	{
//		skip("setHostByName(\"google.com\"); getHostName() -> (e.g.) \"yx-in-f100.1e100.net\"");
		std::string hostStr = "linux.org";
		LLHost host;
		host.setHostByName(hostStr);

		// reverse DNS will likely result in appending of some
		// sub-domain to the main hostname. so look for
		// the main domain name and not do the exact compare

		std::string hostname = host.getHostName();
		try
		{
			ensure("getHostName failed", hostname.find(hostStr) != std::string::npos);
		}
		catch (const std::exception&)
		{
			std::cerr << "set '" << hostStr << "'; reported '" << hostname << "'" << std::endl;
			throw;
		}
	}

//	setHostByName for dotted IP
	template<> template<>
	void host_object::test<10>()
	{
		std::string hostStr = "64.233.167.99";
		LLHost host;
		host.setHostByName(hostStr);
		ensure("SetHostByName for dotted IP Address failed", host.getAddress() == ip_string_to_u32(hostStr.c_str()));
	}

	template<> template<>
	void host_object::test<11>()
	{
		LLHost host1(0xc098017d, 8080);
		LLHost host2 = host1;
		ensure("Both IP addresses are not same", (host1.getAddress() == host2.getAddress()));
		ensure("Both port numbers are not same", (host1.getPort() == host2.getPort()));
	}

	template<> template<>
	void host_object::test<12>()
	{
		LLHost host1("192.168.1.1", 8080);
		std::string str1 = "192.168.1.1:8080";
		std::ostringstream stream;
		stream << host1;
		ensure("Operator << failed", ( stream.str()== str1));

		// There is no istream >> llhost operator.
		//std::istringstream is(stream.str());
		//LLHost host2;
		//is >> host2;
		//ensure("Operator >> failed. Not compatible with <<", host1 == host2);
	}

	// operators ==, !=, <
	template<> template<>
	void host_object::test<13>()
	{
		U32 ip_addr = 0xc098017d;
		U32 port = 8080;
		LLHost host1(ip_addr, port);
		LLHost host2(ip_addr, port);
		ensure("operator== failed", host1 == host2);

		// change port
		host2.setPort(7070);
		ensure("operator!= failed", host1 != host2);

		// set port back to 8080 and change IP address now
		host2.setPort(8080);
		host2.setAddress(ip_addr+10);
		ensure("operator!= failed", host1 != host2);

		ensure("operator<  failed", host1 < host2);

		// set IP address back to same value and change port
		host2.setAddress(ip_addr);
		host2.setPort(host1.getPort() + 10);
		ensure("operator<  failed", host1 < host2);
	}

	// invalid ip address string
	template<> template<>
	void host_object::test<14>()
	{
		LLHost host1("10.0.1.2", 6143);
		ensure("10.0.1.2 should be a valid address", host1.isOk());

		LLHost host2("booger-brains", 6143);
		ensure("booger-brains should be an invalid ip addess", !host2.isOk());

		LLHost host3("255.255.255.255", 6143);
		ensure("255.255.255.255 should be valid broadcast address", host3.isOk());
	}
}
<commit_msg>use our own domain for llhost_test; linux.org changed dns and broke it<commit_after>/**
 * @file llhost_test.cpp
 * @author Adroit
 * @date 2007-02
 * @brief llhost test cases.
 *
 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "../llhost.h"

#include "../test/lltut.h"

namespace tut
{
	struct host_data
	{
	};
	typedef test_group<host_data> host_test;
	typedef host_test::object host_object;
	tut::host_test host_testcase("LLHost");

	template<> template<>
	void host_object::test<1>()
	{
		LLHost host;
		ensure("IP address is not NULL", (0 == host.getAddress()) && (0 == host.getPort()) && !host.isOk());
	}
	template<> template<>
	void host_object::test<2>()
	{
		U32 ip_addr = 0xc098017d;
		U32 port = 8080;
		LLHost host(ip_addr, port);
		ensure("IP address is invalid", ip_addr == host.getAddress());
		ensure("Port Number is invalid", port == host.getPort());
		ensure("IP address and port number both should be ok", host.isOk());
	}

	template<> template<>
	void host_object::test<3>()
	{
		const char* str = "192.168.1.1";
		U32 port = 8080;
		LLHost host(str, port);
		ensure("IP address could not be processed", (host.getAddress() == ip_string_to_u32(str)));
		ensure("Port Number is invalid", (port == host.getPort()));
	}

	template<> template<>
	void host_object::test<4>()
	{
		U32 ip = ip_string_to_u32("192.168.1.1");
		U32 port = 22;
		U64 ip_port = (((U64) ip) << 32) | port;
		LLHost host(ip_port);
		ensure("IP address is invalid", ip == host.getAddress());
		ensure("Port Number is invalid", port == host.getPort());
	}

	template<> template<>
	void host_object::test<5>()
	{
		std::string ip_port_string = "192.168.1.1:8080";
		U32 ip = ip_string_to_u32("192.168.1.1");
		U32 port = 8080;

		LLHost host(ip_port_string);
		ensure("IP address from IP:port is invalid", ip == host.getAddress());
		ensure("Port Number from from IP:port is invalid", port == host.getPort());
	}

	template<> template<>
	void host_object::test<6>()
	{
		U32 ip = 0xc098017d, port = 8080;
		LLHost host;
		host.set(ip,port);
		ensure("IP address is invalid", (ip == host.getAddress()));
		ensure("Port Number is invalid", (port == host.getPort()));
	}

	template<> template<>
	void host_object::test<7>()
	{
		const char* str = "192.168.1.1";
		U32 port = 8080, ip;
		LLHost host;
		host.set(str,port);
		ip = ip_string_to_u32(str);
		ensure("IP address is invalid", (ip == host.getAddress()));
		ensure("Port Number is invalid", (port == host.getPort()));

		str = "64.233.187.99";
		ip = ip_string_to_u32(str);
		host.setAddress(str);
		ensure("IP address is invalid", (ip == host.getAddress()));

		ip = 0xc098017b;
		host.setAddress(ip);
		ensure("IP address is invalid", (ip == host.getAddress()));
		// should still use the old port
		ensure("Port Number is invalid", (port == host.getPort()));

		port = 8084;
		host.setPort(port);
		ensure("Port Number is invalid", (port == host.getPort()));
		// should still use the old address
		ensure("IP address is invalid", (ip == host.getAddress()));
	}

	template<> template<>
	void host_object::test<8>()
	{
		const std::string str("192.168.1.1");
		U32 port = 8080;
		LLHost host;
		host.set(str,port);

		std::string ip_string = host.getIPString();
		ensure("Function Failed", (ip_string == str));

		std::string ip_string_port = host.getIPandPort();
		ensure("Function Failed", (ip_string_port == "192.168.1.1:8080"));
	}

//	getHostName()  and setHostByName
	template<> template<>
	void host_object::test<9>()
	{
//		skip("setHostByName(\"google.com\"); getHostName() -> (e.g.) \"yx-in-f100.1e100.net\"");
		std::string hostStr = "lindenlab.com";
		LLHost host;
		host.setHostByName(hostStr);

		// reverse DNS will likely result in appending of some
		// sub-domain to the main hostname. so look for
		// the main domain name and not do the exact compare

		std::string hostname = host.getHostName();
		try
		{
			ensure("getHostName failed", hostname.find(hostStr) != std::string::npos);
		}
		catch (const std::exception&)
		{
			std::cerr << "set '" << hostStr << "'; reported '" << hostname << "'" << std::endl;
			throw;
		}
	}

//	setHostByName for dotted IP
	template<> template<>
	void host_object::test<10>()
	{
		std::string hostStr = "64.233.167.99";
		LLHost host;
		host.setHostByName(hostStr);
		ensure("SetHostByName for dotted IP Address failed", host.getAddress() == ip_string_to_u32(hostStr.c_str()));
	}

	template<> template<>
	void host_object::test<11>()
	{
		LLHost host1(0xc098017d, 8080);
		LLHost host2 = host1;
		ensure("Both IP addresses are not same", (host1.getAddress() == host2.getAddress()));
		ensure("Both port numbers are not same", (host1.getPort() == host2.getPort()));
	}

	template<> template<>
	void host_object::test<12>()
	{
		LLHost host1("192.168.1.1", 8080);
		std::string str1 = "192.168.1.1:8080";
		std::ostringstream stream;
		stream << host1;
		ensure("Operator << failed", ( stream.str()== str1));

		// There is no istream >> llhost operator.
		//std::istringstream is(stream.str());
		//LLHost host2;
		//is >> host2;
		//ensure("Operator >> failed. Not compatible with <<", host1 == host2);
	}

	// operators ==, !=, <
	template<> template<>
	void host_object::test<13>()
	{
		U32 ip_addr = 0xc098017d;
		U32 port = 8080;
		LLHost host1(ip_addr, port);
		LLHost host2(ip_addr, port);
		ensure("operator== failed", host1 == host2);

		// change port
		host2.setPort(7070);
		ensure("operator!= failed", host1 != host2);

		// set port back to 8080 and change IP address now
		host2.setPort(8080);
		host2.setAddress(ip_addr+10);
		ensure("operator!= failed", host1 != host2);

		ensure("operator<  failed", host1 < host2);

		// set IP address back to same value and change port
		host2.setAddress(ip_addr);
		host2.setPort(host1.getPort() + 10);
		ensure("operator<  failed", host1 < host2);
	}

	// invalid ip address string
	template<> template<>
	void host_object::test<14>()
	{
		LLHost host1("10.0.1.2", 6143);
		ensure("10.0.1.2 should be a valid address", host1.isOk());

		LLHost host2("booger-brains", 6143);
		ensure("booger-brains should be an invalid ip addess", !host2.isOk());

		LLHost host3("255.255.255.255", 6143);
		ensure("255.255.255.255 should be valid broadcast address", host3.isOk());
	}
}
<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	pipeline.cxx
 *
 *   DESCRIPTION
 *      implementation of the pqxx::pipeline class
 *   Throughput-optimized query manager
 *
 * Copyright (c) 2003-2005, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler.h"

#include "pqxx/dbtransaction"
#include "pqxx/pipeline"

using namespace PGSTD;
using namespace pqxx;
using namespace pqxx::internal;
using namespace pqxx::internal::pq;

#define pqxxassert(ARG) /* ignore */

namespace
{
const string theSeparator("; ");
const string theDummyValue("1");
const string theDummyQuery("SELECT " + theDummyValue + theSeparator);
}

pqxx::pipeline::pipeline(transaction_base &t, const string &PName) :
  internal::transactionfocus(t, PName, "pipeline"),
  m_queries(),
  m_issuedrange(),
  m_retain(0),
  m_num_waiting(0),
  m_q_id(0),
  m_dummy_pending(false),
  m_error(qid_limit())
{
  m_issuedrange = make_pair(m_queries.end(), m_queries.end());
  register_me();
}

pqxx::pipeline::~pipeline() throw ()
{
#ifdef PQXX_QUIET_DESTRUCTORS
  disable_noticer Quiet(m_Trans.conn());
#endif
  try { flush(); } catch (const exception &) {}
  if (registered()) unregister_me();
}

pipeline::query_id pqxx::pipeline::insert(const string &q)
{
  const query_id qid = generate_id();
  pqxxassert(qid > 0);
  pqxxassert(m_queries.lower_bound(qid)==m_queries.end());
  const QueryMap::iterator i = m_queries.insert(make_pair(qid,Query(q))).first;

  if (m_issuedrange.second == m_queries.end())
  {
    m_issuedrange.second = i;
    if (m_issuedrange.first == m_queries.end()) m_issuedrange.first = i;
  }
  m_num_waiting++;

  pqxxassert(m_issuedrange.first != m_queries.end());
  pqxxassert(m_issuedrange.second != m_queries.end());

  if (m_num_waiting > m_retain)
  {
    if (have_pending()) receive_if_available();
    if (!have_pending()) issue();
  }

  return qid;
}

void pqxx::pipeline::complete()
{
  if (have_pending()) receive(m_issuedrange.second);
  if (m_num_waiting && (m_error == qid_limit()))
  {
    pqxxassert(!have_pending());
    issue();
    pqxxassert(!m_num_waiting);
    pqxxassert(have_pending());
    pqxxassert(m_issuedrange.second == m_queries.end());
    receive(m_queries.end());
    pqxxassert((m_error!=qid_limit()) || !have_pending());
  }
  pqxxassert((m_num_waiting == 0) || (m_error != qid_limit()));
  pqxxassert(!m_dummy_pending);
}

void pqxx::pipeline::flush()
{
  if (m_queries.empty()) return;
  if (have_pending()) receive(m_issuedrange.second);
  m_issuedrange.first = m_issuedrange.second = m_queries.end();
  m_num_waiting = 0;
  m_dummy_pending = false;
  m_queries.clear();
}

bool pqxx::pipeline::is_finished(pipeline::query_id q) const
{
  if (m_queries.find(q) == m_queries.end())
    throw logic_error("Requested status for unknown query " + to_string(q));
  return (QueryMap::const_iterator(m_issuedrange.first)==m_queries.end()) ||
         (q < m_issuedrange.first->first && q < m_error);
}

pair<pipeline::query_id, result> pqxx::pipeline::retrieve()
{
  if (m_queries.empty())
    throw logic_error("Attempt to retrieve result from empty pipeline");
  return retrieve(m_queries.begin());
}

int pqxx::pipeline::retain(int retain_max)
{
  if (retain_max < 0)
    throw range_error("Attempt to make pipeline retain " +
	to_string(retain_max) + " queries");

  const int oldvalue = m_retain;
  m_retain = retain_max;

  if (m_num_waiting >= m_retain) resume();

  return oldvalue;
}

void pqxx::pipeline::resume()
{
  if (have_pending()) receive_if_available();
  if (!have_pending() && m_num_waiting)
  {
    issue();
    receive_if_available();
  }
}

pipeline::query_id pqxx::pipeline::generate_id()
{
  if (m_q_id == qid_limit())
    throw overflow_error("Too many queries went through pipeline");
  ++m_q_id;
  return m_q_id;
}

void pqxx::pipeline::issue()
{
  pqxxassert(m_num_waiting);
  pqxxassert(!have_pending());
  pqxxassert(!m_dummy_pending);
  pqxxassert(m_num_waiting);

  // Retrieve that NULL result for the last query, if needed
  obtain_result();

  // Don't issue anything if we've encountered an error
  if (m_error < qid_limit()) return;

  // Start with oldest query (lowest id) not in previous issue range
  const QueryMap::iterator oldest = m_issuedrange.second;
  pqxxassert(oldest != m_queries.end());

  // Construct cumulative query string for entire batch
  string cum;
  int num_issued = 0;
  for (QueryMap::const_iterator i = oldest;
       i != m_queries.end();
       ++i, ++num_issued)
  {
    cum += i->second.get_query();
    cum += theSeparator;
  }
  cum.resize(cum.size() - theSeparator.size());
  const bool prepend_dummy = (num_issued > 1);
  if (prepend_dummy) cum = theDummyQuery + cum;

  m_Trans.start_exec(cum);

  // Since we managed to send out these queries, update state to reflect this
  m_dummy_pending = prepend_dummy;
  m_issuedrange.first = oldest;
  m_issuedrange.second = m_queries.end();
  m_num_waiting -= num_issued;
}

void pqxx::pipeline::internal_error(const string &err) throw (logic_error)
{
  set_error_at(0);
  throw logic_error(err);
}

bool pqxx::pipeline::obtain_result(bool expect_none)
{
  pqxxassert(!m_dummy_pending);
  pqxxassert(!m_queries.empty());

  internal::pq::PGresult *r = m_Trans.get_result();
  if (!r)
  {
    if (have_pending() && !expect_none)
    {
      set_error_at(m_issuedrange.first->first);
      m_issuedrange.second = m_issuedrange.first;
    }
    return false;
  }

  pqxxassert(r);
  const result res(r);

  if (!have_pending())
  {
    set_error_at(m_queries.begin()->first);
    throw logic_error("Got more results from pipeline than there were queries");
  }

  // Must be the result for the oldest pending query
  if (!m_issuedrange.first->second.get_result().empty())
    internal_error("libpqxx internal error: multiple results for one query");

  m_issuedrange.first->second.set_result(res);
  ++m_issuedrange.first;

  return true;
}

void pqxx::pipeline::obtain_dummy()
{
  pqxxassert(m_dummy_pending);
  internal::pq::PGresult *const r = m_Trans.get_result();
  m_dummy_pending = false;

  if (!r)
    internal_error("libpqxx internal error: "
	  "pipeline got no result from backend when it expected one");

  result R(r);
  bool OK = false;
  try
  {
    R.CheckStatus("");
    OK = true;
  }
  catch (const sql_error &)
  {
  }
  if (OK)
  {
    if (R.size() > 1)
      internal_error("libpqxx internal error: "
	  "unexpected result for dummy query in pipeline");

    if (string(R.at(0).at(0).c_str()) != theDummyValue)
      internal_error("libpqxx internal error: "
	    "dummy query in pipeline returned unexpected value");
    return;
  }

  /* Since none of the queries in the batch were actually executed, we can
   * afford to replay them one by one until we find the exact query that
   * caused the error.  This gives us not only a more specific error message
   * to report, but also tells us which query to report it for.
   */
  // First, give the whole batch the same syntax error message, in case all else
  // is going to fail.
  for (QueryMap::iterator i = m_issuedrange.first;
       i != m_issuedrange.second;
       ++i) i->second.set_result(R);

  // Remember where the end of this batch was
  const QueryMap::iterator stop = m_issuedrange.second;

  // Retrieve that NULL result for the last query, if needed
  obtain_result(true);

  // Reset internal state to forget botched batch attempt
  // (Not using std::distance() because of problem with SunC++)
  for (QueryMap::iterator i = m_issuedrange.first; i != stop; ++i)
    ++m_num_waiting;
  m_issuedrange.second = m_issuedrange.first;

  pqxxassert(!m_dummy_pending);
  pqxxassert(!have_pending());
  pqxxassert(m_num_waiting > 0);

  // Issue queries in failed batch one at a time.
  unregister_me();
  try
  {
    do
    {
      m_num_waiting--;
      const string &query = m_issuedrange.first->second.get_query();
      const result res(m_Trans.exec(query));
      m_issuedrange.first->second.set_result(res);
      res.CheckStatus(query);
      ++m_issuedrange.first;
    }
    while (m_issuedrange.first != stop);
  }
  catch (const exception &e)
  {
    pqxxassert(m_issuedrange.first != m_queries.end());

    const query_id thud = m_issuedrange.first->first;
    ++m_issuedrange.first;
    m_issuedrange.second = m_issuedrange.first;
    QueryMap::const_iterator q = m_issuedrange.first;
    set_error_at( (q == m_queries.end()) ?  thud + 1 : q->first);

    pqxxassert(m_num_waiting == distance(m_issuedrange.second,m_queries.end()));
  }

  pqxxassert(m_issuedrange.first != m_queries.end());
  pqxxassert(m_error <= m_q_id);
}

pair<pipeline::query_id, result>
pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q)
{
  if (q == m_queries.end())
    throw logic_error("Attempt to retrieve result for unknown query");

  if (q->first >= m_error)
    throw runtime_error("Could not complete query in pipeline "
	"due to error in earlier query");

  // If query hasn't issued yet, do it now
  if (m_issuedrange.second != m_queries.end() &&
      (q->first >= m_issuedrange.second->first))
  {
    pqxxassert(distance(m_issuedrange.second, q) >= 0);

    if (have_pending()) receive(m_issuedrange.second);
    if (m_error == qid_limit()) issue();
  }

  // If result not in yet, get it; else get at least whatever's convenient
  if (have_pending())
  {
    if (q->first >= m_issuedrange.first->first)
    {
      QueryMap::const_iterator suc = q;
      ++suc;
      receive(suc);
    }
    else
    {
      receive_if_available();
    }
  }

  pqxxassert((m_error <= q->first) || (q != m_issuedrange.first));

  if (q->first >= m_error)
    throw runtime_error("Could not complete query in pipeline "
	"due to error in earlier query");

  // Don't leave the backend idle if there are queries waiting to be issued
  if (m_num_waiting && !have_pending() && (m_error==qid_limit())) issue();

  const string query(q->second.get_query());
  const result R = q->second.get_result();
  pair<query_id,result> P(make_pair(q->first, R));

  m_queries.erase(q);

  R.CheckStatus(query);
  return P;
}

void pqxx::pipeline::get_further_available_results()
{
  pqxxassert(!m_dummy_pending);
  while (!m_Trans.is_busy() && obtain_result()) m_Trans.consume_input();
}

void pqxx::pipeline::receive_if_available()
{
  m_Trans.consume_input();
  if (m_Trans.is_busy()) return;

  if (m_dummy_pending) obtain_dummy();
  if (have_pending()) get_further_available_results();
}

void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop)
{
  pqxxassert(have_pending());

  if (m_dummy_pending) obtain_dummy();

  while (obtain_result() &&
         QueryMap::const_iterator(m_issuedrange.first) != stop);

  // Also haul in any remaining "targets of opportunity"
  if (QueryMap::const_iterator(m_issuedrange.first) == stop)
    get_further_available_results();
}

<commit_msg>Give name to dummy query; remove trailing whitespace<commit_after>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	pipeline.cxx
 *
 *   DESCRIPTION
 *      implementation of the pqxx::pipeline class
 *   Throughput-optimized query manager
 *
 * Copyright (c) 2003-2005, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler.h"

#include "pqxx/dbtransaction"
#include "pqxx/pipeline"

using namespace PGSTD;
using namespace pqxx;
using namespace pqxx::internal;
using namespace pqxx::internal::pq;

#define pqxxassert(ARG) /* ignore */

namespace
{
const string theSeparator("; ");
const string theDummyValue("1");
const string theDummyQuery("SELECT " + theDummyValue + theSeparator);
}

pqxx::pipeline::pipeline(transaction_base &t, const string &PName) :
  internal::transactionfocus(t, PName, "pipeline"),
  m_queries(),
  m_issuedrange(),
  m_retain(0),
  m_num_waiting(0),
  m_q_id(0),
  m_dummy_pending(false),
  m_error(qid_limit())
{
  m_issuedrange = make_pair(m_queries.end(), m_queries.end());
  register_me();
}

pqxx::pipeline::~pipeline() throw ()
{
#ifdef PQXX_QUIET_DESTRUCTORS
  disable_noticer Quiet(m_Trans.conn());
#endif
  try { flush(); } catch (const exception &) {}
  if (registered()) unregister_me();
}

pipeline::query_id pqxx::pipeline::insert(const string &q)
{
  const query_id qid = generate_id();
  pqxxassert(qid > 0);
  pqxxassert(m_queries.lower_bound(qid)==m_queries.end());
  const QueryMap::iterator i = m_queries.insert(make_pair(qid,Query(q))).first;

  if (m_issuedrange.second == m_queries.end())
  {
    m_issuedrange.second = i;
    if (m_issuedrange.first == m_queries.end()) m_issuedrange.first = i;
  }
  m_num_waiting++;

  pqxxassert(m_issuedrange.first != m_queries.end());
  pqxxassert(m_issuedrange.second != m_queries.end());

  if (m_num_waiting > m_retain)
  {
    if (have_pending()) receive_if_available();
    if (!have_pending()) issue();
  }

  return qid;
}

void pqxx::pipeline::complete()
{
  if (have_pending()) receive(m_issuedrange.second);
  if (m_num_waiting && (m_error == qid_limit()))
  {
    pqxxassert(!have_pending());
    issue();
    pqxxassert(!m_num_waiting);
    pqxxassert(have_pending());
    pqxxassert(m_issuedrange.second == m_queries.end());
    receive(m_queries.end());
    pqxxassert((m_error!=qid_limit()) || !have_pending());
  }
  pqxxassert((m_num_waiting == 0) || (m_error != qid_limit()));
  pqxxassert(!m_dummy_pending);
}

void pqxx::pipeline::flush()
{
  if (m_queries.empty()) return;
  if (have_pending()) receive(m_issuedrange.second);
  m_issuedrange.first = m_issuedrange.second = m_queries.end();
  m_num_waiting = 0;
  m_dummy_pending = false;
  m_queries.clear();
}

bool pqxx::pipeline::is_finished(pipeline::query_id q) const
{
  if (m_queries.find(q) == m_queries.end())
    throw logic_error("Requested status for unknown query " + to_string(q));
  return (QueryMap::const_iterator(m_issuedrange.first)==m_queries.end()) ||
         (q < m_issuedrange.first->first && q < m_error);
}

pair<pipeline::query_id, result> pqxx::pipeline::retrieve()
{
  if (m_queries.empty())
    throw logic_error("Attempt to retrieve result from empty pipeline");
  return retrieve(m_queries.begin());
}

int pqxx::pipeline::retain(int retain_max)
{
  if (retain_max < 0)
    throw range_error("Attempt to make pipeline retain " +
	to_string(retain_max) + " queries");

  const int oldvalue = m_retain;
  m_retain = retain_max;

  if (m_num_waiting >= m_retain) resume();

  return oldvalue;
}

void pqxx::pipeline::resume()
{
  if (have_pending()) receive_if_available();
  if (!have_pending() && m_num_waiting)
  {
    issue();
    receive_if_available();
  }
}

pipeline::query_id pqxx::pipeline::generate_id()
{
  if (m_q_id == qid_limit())
    throw overflow_error("Too many queries went through pipeline");
  ++m_q_id;
  return m_q_id;
}

void pqxx::pipeline::issue()
{
  pqxxassert(m_num_waiting);
  pqxxassert(!have_pending());
  pqxxassert(!m_dummy_pending);
  pqxxassert(m_num_waiting);

  // Retrieve that NULL result for the last query, if needed
  obtain_result();

  // Don't issue anything if we've encountered an error
  if (m_error < qid_limit()) return;

  // Start with oldest query (lowest id) not in previous issue range
  const QueryMap::iterator oldest = m_issuedrange.second;
  pqxxassert(oldest != m_queries.end());

  // Construct cumulative query string for entire batch
  string cum;
  int num_issued = 0;
  for (QueryMap::const_iterator i = oldest;
       i != m_queries.end();
       ++i, ++num_issued)
  {
    cum += i->second.get_query();
    cum += theSeparator;
  }
  cum.resize(cum.size() - theSeparator.size());
  const bool prepend_dummy = (num_issued > 1);
  if (prepend_dummy) cum = theDummyQuery + cum;

  m_Trans.start_exec(cum);

  // Since we managed to send out these queries, update state to reflect this
  m_dummy_pending = prepend_dummy;
  m_issuedrange.first = oldest;
  m_issuedrange.second = m_queries.end();
  m_num_waiting -= num_issued;
}

void pqxx::pipeline::internal_error(const string &err) throw (logic_error)
{
  set_error_at(0);
  throw logic_error(err);
}

bool pqxx::pipeline::obtain_result(bool expect_none)
{
  pqxxassert(!m_dummy_pending);
  pqxxassert(!m_queries.empty());

  internal::pq::PGresult *r = m_Trans.get_result();
  if (!r)
  {
    if (have_pending() && !expect_none)
    {
      set_error_at(m_issuedrange.first->first);
      m_issuedrange.second = m_issuedrange.first;
    }
    return false;
  }

  pqxxassert(r);
  const result res(r);

  if (!have_pending())
  {
    set_error_at(m_queries.begin()->first);
    throw logic_error("Got more results from pipeline than there were queries");
  }

  // Must be the result for the oldest pending query
  if (!m_issuedrange.first->second.get_result().empty())
    internal_error("libpqxx internal error: multiple results for one query");

  m_issuedrange.first->second.set_result(res);
  ++m_issuedrange.first;

  return true;
}

void pqxx::pipeline::obtain_dummy()
{
  pqxxassert(m_dummy_pending);
  internal::pq::PGresult *const r = m_Trans.get_result();
  m_dummy_pending = false;

  if (!r)
    internal_error("libpqxx internal error: "
	  "pipeline got no result from backend when it expected one");

  result R(r);
  bool OK = false;
  try
  {
    R.CheckStatus("[DUMMY PIPELINE QUERY]");
    OK = true;
  }
  catch (const sql_error &)
  {
  }
  if (OK)
  {
    if (R.size() > 1)
      internal_error("libpqxx internal error: "
	  "unexpected result for dummy query in pipeline");

    if (string(R.at(0).at(0).c_str()) != theDummyValue)
      internal_error("libpqxx internal error: "
	    "dummy query in pipeline returned unexpected value");
    return;
  }

  /* Since none of the queries in the batch were actually executed, we can
   * afford to replay them one by one until we find the exact query that
   * caused the error.  This gives us not only a more specific error message
   * to report, but also tells us which query to report it for.
   */
  // First, give the whole batch the same syntax error message, in case all else
  // is going to fail.
  for (QueryMap::iterator i = m_issuedrange.first;
       i != m_issuedrange.second;
       ++i) i->second.set_result(R);

  // Remember where the end of this batch was
  const QueryMap::iterator stop = m_issuedrange.second;

  // Retrieve that NULL result for the last query, if needed
  obtain_result(true);

  // Reset internal state to forget botched batch attempt
  // (Not using std::distance() because of problem with SunC++)
  for (QueryMap::iterator i = m_issuedrange.first; i != stop; ++i)
    ++m_num_waiting;
  m_issuedrange.second = m_issuedrange.first;

  pqxxassert(!m_dummy_pending);
  pqxxassert(!have_pending());
  pqxxassert(m_num_waiting > 0);

  // Issue queries in failed batch one at a time.
  unregister_me();
  try
  {
    do
    {
      m_num_waiting--;
      const string &query = m_issuedrange.first->second.get_query();
      const result res(m_Trans.exec(query));
      m_issuedrange.first->second.set_result(res);
      res.CheckStatus(query);
      ++m_issuedrange.first;
    }
    while (m_issuedrange.first != stop);
  }
  catch (const exception &e)
  {
    pqxxassert(m_issuedrange.first != m_queries.end());

    const query_id thud = m_issuedrange.first->first;
    ++m_issuedrange.first;
    m_issuedrange.second = m_issuedrange.first;
    QueryMap::const_iterator q = m_issuedrange.first;
    set_error_at( (q == m_queries.end()) ?  thud + 1 : q->first);

    pqxxassert(m_num_waiting == distance(m_issuedrange.second,m_queries.end()));
  }

  pqxxassert(m_issuedrange.first != m_queries.end());
  pqxxassert(m_error <= m_q_id);
}

pair<pipeline::query_id, result>
pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q)
{
  if (q == m_queries.end())
    throw logic_error("Attempt to retrieve result for unknown query");

  if (q->first >= m_error)
    throw runtime_error("Could not complete query in pipeline "
	"due to error in earlier query");

  // If query hasn't issued yet, do it now
  if (m_issuedrange.second != m_queries.end() &&
      (q->first >= m_issuedrange.second->first))
  {
    pqxxassert(distance(m_issuedrange.second, q) >= 0);

    if (have_pending()) receive(m_issuedrange.second);
    if (m_error == qid_limit()) issue();
  }

  // If result not in yet, get it; else get at least whatever's convenient
  if (have_pending())
  {
    if (q->first >= m_issuedrange.first->first)
    {
      QueryMap::const_iterator suc = q;
      ++suc;
      receive(suc);
    }
    else
    {
      receive_if_available();
    }
  }

  pqxxassert((m_error <= q->first) || (q != m_issuedrange.first));

  if (q->first >= m_error)
    throw runtime_error("Could not complete query in pipeline "
	"due to error in earlier query");

  // Don't leave the backend idle if there are queries waiting to be issued
  if (m_num_waiting && !have_pending() && (m_error==qid_limit())) issue();

  const string query(q->second.get_query());
  const result R = q->second.get_result();
  pair<query_id,result> P(make_pair(q->first, R));

  m_queries.erase(q);

  R.CheckStatus(query);
  return P;
}

void pqxx::pipeline::get_further_available_results()
{
  pqxxassert(!m_dummy_pending);
  while (!m_Trans.is_busy() && obtain_result()) m_Trans.consume_input();
}

void pqxx::pipeline::receive_if_available()
{
  m_Trans.consume_input();
  if (m_Trans.is_busy()) return;

  if (m_dummy_pending) obtain_dummy();
  if (have_pending()) get_further_available_results();
}

void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop)
{
  pqxxassert(have_pending());

  if (m_dummy_pending) obtain_dummy();

  while (obtain_result() &&
         QueryMap::const_iterator(m_issuedrange.first) != stop);

  // Also haul in any remaining "targets of opportunity"
  if (QueryMap::const_iterator(m_issuedrange.first) == stop)
    get_further_available_results();
}

<|endoftext|>


<commit_before>/**
 * @file llviewerdisplayname.cpp
 * @brief Wrapper for display name functionality
 *
 * $LicenseInfo:firstyear=2010&license=viewergpl$
 *
 * Copyright (c) 2010, Linden Research, Inc.
 *
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 *
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 *
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 *
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */
#include "llviewerprecompiledheaders.h"

#include "llviewerdisplayname.h"

// viewer includes
#include "llagent.h"
#include "llviewerregion.h"
#include "llvoavatar.h"

// library includes
#include "llavatarnamecache.h"
#include "llhttpclient.h"
#include "llhttpnode.h"
#include "llnotificationsutil.h"

namespace LLViewerDisplayName
{
	// Fired when viewer receives server response to display name change
	set_name_signal_t sSetDisplayNameSignal;
}

class LLSetDisplayNameResponder : public LLHTTPClient::Responder
{
public:
	// only care about errors
	/*virtual*/ void error(U32 status, const std::string& reason)
	{
		LLViewerDisplayName::sSetDisplayNameSignal(false, "", LLSD());
		LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots();
	}
};

void LLViewerDisplayName::set(const std::string& display_name, const set_name_slot_t& slot)
{
	// TODO: simple validation here

	LLViewerRegion* region = gAgent.getRegion();
	llassert(region);
	std::string cap_url = region->getCapability("SetDisplayName");
	if (cap_url.empty())
	{
		// JAMESDEBUG HACK for demos, fall back to prototype name service
		llinfos << "Set name via legacy service" << llendl;
		LLAvatarNameCache::setDisplayName(gAgent.getID(), display_name, slot);
		return;

		// this server does not support display names, report error
		//slot(false, "unsupported", LLSD());
		//return;
	}

	llinfos << "Set name POST to " << cap_url << llendl;

	// Record our caller for when the server sends back a reply
	sSetDisplayNameSignal.connect(slot);

	// POST the requested change.  The sim will not send a response back to
	// this request directly, rather it will send a separate message after it
	// communicates with the back-end.
	LLSD body;
	body["display_name"] = display_name;
	LLHTTPClient::post(cap_url, body, new LLSetDisplayNameResponder);
}

class LLSetDisplayNameReply : public LLHTTPNode
{
	/*virtual*/ void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		LLSD body = input["body"];

		S32 status = body["status"].asInteger();
		bool success = (status == 200);
		std::string reason = body["reason"].asString();
		LLSD content = body["content"];

		llinfos << "JAMESDEBUG LLSetDisplayNameReply status " << status
			<< " reason " << reason << llendl;

		// inform caller of result
		LLViewerDisplayName::sSetDisplayNameSignal(success, reason, content);
		LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots();
	}
};

class LLDisplayNameUpdate : public LLHTTPNode
{
	/*virtual*/ void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		LLSD body = input["body"];
		LLUUID agent_id = body["agent_id"];
		std::string slid = body["sl_id"];
		std::string old_display_name = body["old_display_name"];
		std::string new_display_name = body["new_display_name"];

		// force re-request of this agent's name data
		LLAvatarNameCache::erase(agent_id);

		// force name tag to update
		LLVOAvatar::invalidateNameTag(agent_id);

		LLSD args;
		args["OLD_NAME"] = old_display_name;
		args["SLID"] = slid;
		args["NEW_NAME"] = new_display_name;
		LLNotificationsUtil::add("DisplayNameUpdate", args);
	}
};

LLHTTPRegistration<LLSetDisplayNameReply>
    gHTTPRegistrationMessageSetDisplayNameReply(
		"/message/SetDisplayNameReply");

LLHTTPRegistration<LLDisplayNameUpdate>
    gHTTPRegistrationMessageDisplayNameUpdate(
		"/message/DisplayNameUpdate");
<commit_msg>Remove hack to set display name by talking directly to name service<commit_after>/**
 * @file llviewerdisplayname.cpp
 * @brief Wrapper for display name functionality
 *
 * $LicenseInfo:firstyear=2010&license=viewergpl$
 *
 * Copyright (c) 2010, Linden Research, Inc.
 *
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 *
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 *
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 *
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */
#include "llviewerprecompiledheaders.h"

#include "llviewerdisplayname.h"

// viewer includes
#include "llagent.h"
#include "llviewerregion.h"
#include "llvoavatar.h"

// library includes
#include "llavatarnamecache.h"
#include "llhttpclient.h"
#include "llhttpnode.h"
#include "llnotificationsutil.h"

namespace LLViewerDisplayName
{
	// Fired when viewer receives server response to display name change
	set_name_signal_t sSetDisplayNameSignal;
}

class LLSetDisplayNameResponder : public LLHTTPClient::Responder
{
public:
	// only care about errors
	/*virtual*/ void error(U32 status, const std::string& reason)
	{
		LLViewerDisplayName::sSetDisplayNameSignal(false, "", LLSD());
		LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots();
	}
};

void LLViewerDisplayName::set(const std::string& display_name, const set_name_slot_t& slot)
{
	// TODO: simple validation here

	LLViewerRegion* region = gAgent.getRegion();
	llassert(region);
	std::string cap_url = region->getCapability("SetDisplayName");
	if (cap_url.empty())
	{
		// this server does not support display names, report error
		slot(false, "unsupported", LLSD());
		return;
	}

	llinfos << "Set name POST to " << cap_url << llendl;

	// Record our caller for when the server sends back a reply
	sSetDisplayNameSignal.connect(slot);

	// POST the requested change.  The sim will not send a response back to
	// this request directly, rather it will send a separate message after it
	// communicates with the back-end.
	LLSD body;
	body["display_name"] = display_name;
	LLHTTPClient::post(cap_url, body, new LLSetDisplayNameResponder);
}

class LLSetDisplayNameReply : public LLHTTPNode
{
	/*virtual*/ void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		LLSD body = input["body"];

		S32 status = body["status"].asInteger();
		bool success = (status == 200);
		std::string reason = body["reason"].asString();
		LLSD content = body["content"];

		llinfos << "JAMESDEBUG LLSetDisplayNameReply status " << status
			<< " reason " << reason << llendl;

		// inform caller of result
		LLViewerDisplayName::sSetDisplayNameSignal(success, reason, content);
		LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots();
	}
};

class LLDisplayNameUpdate : public LLHTTPNode
{
	/*virtual*/ void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		LLSD body = input["body"];
		LLUUID agent_id = body["agent_id"];
		std::string slid = body["sl_id"];
		std::string old_display_name = body["old_display_name"];
		std::string new_display_name = body["new_display_name"];

		// force re-request of this agent's name data
		LLAvatarNameCache::erase(agent_id);

		// force name tag to update
		LLVOAvatar::invalidateNameTag(agent_id);

		LLSD args;
		args["OLD_NAME"] = old_display_name;
		args["SLID"] = slid;
		args["NEW_NAME"] = new_display_name;
		LLNotificationsUtil::add("DisplayNameUpdate", args);
	}
};

LLHTTPRegistration<LLSetDisplayNameReply>
    gHTTPRegistrationMessageSetDisplayNameReply(
		"/message/SetDisplayNameReply");

LLHTTPRegistration<LLDisplayNameUpdate>
    gHTTPRegistrationMessageDisplayNameUpdate(
		"/message/DisplayNameUpdate");
<|endoftext|>


<commit_before>#ifndef AMGCL_INTERP_SA_EMIN_HPP
#define AMGCL_INTERP_SA_EMIN_HPP

/*
The MIT License

Copyright (c) 2012 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   interp_sa_emin.hpp
 * \author Denis Demidov <ddemidov@ksu.ru>
 * \brief  Interpolation scheme based on smoothed aggregation with energy minimization.
 */

#include <vector>
#include <algorithm>
#include <functional>

#include <boost/typeof/typeof.hpp>

#include <amgcl/spmat.hpp>
#include <amgcl/aggr_connect.hpp>
#include <amgcl/tictoc.hpp>

namespace amgcl {

namespace interp {

/// Interpolation scheme based on smoothed aggregation with energy minimization.
/**
 * See \ref Sala_2008 "Sala (2008)"
 *
 * \param aggr_type \ref aggregation "Aggregation scheme".
 *
 * \ingroup interpolation
 */
template <class aggr_type>
struct sa_emin {

/// Parameters controlling aggregation.
struct params {
    /// Parameter \f$\varepsilon_{str}\f$ defining strong couplings.
    /**
     * Variable \f$i\f$ is defined to be strongly coupled to another variable,
     * \f$j\f$, if \f[|a_{ij}| \geq \varepsilon\sqrt{a_{ii} a_{jj}}\quad
     * \text{with fixed} \quad \varepsilon = \varepsilon_{str} \left(
     * \frac{1}{2} \right)^l,\f]
     * where \f$l\f$ is level number (finest level is 0).
     */
    mutable float eps_strong;

    params() : eps_strong(0.08f) {}
};

/// Constructs coarse level by aggregation.
/**
 * Returns interpolation operator, which is enough to construct system matrix
 * at coarser level.
 *
 * \param A   system matrix.
 * \param prm parameters.
 *
 * \returns interpolation operator.
 */
template < class value_t, class index_t >
static std::pair<
    sparse::matrix<value_t, index_t>,
    sparse::matrix<value_t, index_t>
    >
interp(const sparse::matrix<value_t, index_t> &A, const params &prm) {
    TIC("aggregates");
    BOOST_AUTO(aggr, aggr_type::aggregates(A, aggr::connect(A, prm.eps_strong)));
    prm.eps_strong *= 0.5;
    TOC("aggregates");

    const index_t n = sparse::matrix_rows(A);

    index_t nc = std::max(
            static_cast<index_t>(0),
            *std::max_element(aggr.begin(), aggr.end()) + static_cast<index_t>(1)
            );

    TIC("tentative interpolation");
    sparse::matrix<value_t, index_t> P_tent(n, nc);
    P_tent.col.reserve(n);
    P_tent.val.reserve(n);

    P_tent.row[0] = 0;
    for(index_t i = 0; i < n; ++i) {
        if (aggr[i] >= 0) {
            P_tent.row[i + 1] = P_tent.row[i] + 1;
            P_tent.col.push_back(aggr[i]);
            P_tent.val.push_back(static_cast<value_t>(1));
        } else {
            P_tent.row[i + 1] = P_tent.row[i];
        }
    }
    BOOST_AUTO(R_tent, sparse::transpose(P_tent));
    TOC("tentative interpolation");

    // Compute smoothed nterpolation and restriction operators.
    static std::pair<
        sparse::matrix<value_t, index_t>,
        sparse::matrix<value_t, index_t>
    > PR;

    improve_tentative_interp(A, P_tent, aggr).swap(PR.first);
    sparse::transpose(
            improve_tentative_interp(sparse::transpose(A), P_tent, aggr)
            ).swap(PR.second);

    return PR;
}

private:

template <class spmat>
static std::vector<typename sparse::matrix_value<spmat>::type>
colwise_inner_prod(const spmat &A, const spmat &B) {
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(A);

    assert(n == sparse::matrix_rows(B));
    assert(m == sparse::matrix_cols(B));

    BOOST_AUTO(Arow, sparse::matrix_outer_index(A));
    BOOST_AUTO(Acol, sparse::matrix_inner_index(A));
    BOOST_AUTO(Aval, sparse::matrix_values(A));

    BOOST_AUTO(Brow, sparse::matrix_outer_index(B));
    BOOST_AUTO(Bcol, sparse::matrix_inner_index(B));
    BOOST_AUTO(Bval, sparse::matrix_values(B));

    std::vector<value_t> sum(m, static_cast<value_t>(0));

    for(index_t i = 0; i < n; ++i) {
        for(
                index_t ja = Arow[i], ea = Arow[i + 1],
                        jb = Brow[i], eb = Brow[i + 1];
                ja < ea && jb < eb;
           )
        {
            index_t ca = Acol[ja];
            index_t cb = Bcol[jb];

            if (ca < cb)
                ++ja;
            else if (cb < ca)
                ++jb;
            else /*ca == cb*/ {
                sum[ca] += Aval[ja] * Bval[jb];
                ++ja;
                ++jb;
            }
        }
    }

    return sum;
}

template <class spmat>
static std::vector<typename sparse::matrix_value<spmat>::type>
colwise_norm(const spmat &A) {
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(A);

    BOOST_AUTO(Arow, sparse::matrix_outer_index(A));
    BOOST_AUTO(Acol, sparse::matrix_inner_index(A));
    BOOST_AUTO(Aval, sparse::matrix_values(A));

    std::vector<value_t> sum(m, static_cast<value_t>(0));

    for(index_t i = 0; i < n; ++i)
        for(index_t j = Arow[i], e = Arow[i + 1]; j < e; ++j)
            sum[Acol[j]] += Aval[j] * Aval[j];

    return sum;
}

template <class spmat>
static spmat improve_tentative_interp(const spmat &A, const spmat &P_tent,
        const std::vector<typename sparse::matrix_index<spmat>::type> &aggr)
{
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(P_tent);

    TIC("local damping");
    BOOST_AUTO(D, sparse::diagonal(A));
    BOOST_AUTO(AP, sparse::prod(A, P_tent));
    BOOST_AUTO(DAP, AP);
    for(index_t i = 0; i < n; ++i) {
        value_t dinv = 1 / D[i];
        D[i] = dinv;
        for(index_t j = DAP.row[i], e = DAP.row[i + 1]; j < e; ++j)
            DAP.val[j] *= dinv;
    }
    BOOST_AUTO(ADAP, sparse::prod(A, DAP));

    sparse::sort_rows(AP);
    sparse::sort_rows(ADAP);

    BOOST_AUTO(num, colwise_inner_prod(AP, ADAP));
    BOOST_AUTO(den, colwise_norm(ADAP));

    std::vector<value_t> omega(m);
    std::transform(num.begin(), num.end(), den.begin(), omega.begin(),
            std::divides<value_t>());
    TOC("local damping");

    // Update DAP to obtain P.
    for(index_t i = 0; i < n; ++i) {
        for(index_t j = DAP.row[i], e = DAP.row[i + 1]; j < e; ++j) {
            index_t c = DAP.col[j];
            DAP.val[j] *= -omega[c];
            if (c == aggr[i]) DAP.val[j] += 1;
        }
    }

    return DAP;
}

};

} // namespace interp
} // namespace amgcl

#endif
<commit_msg>profiling for sa_emin<commit_after>#ifndef AMGCL_INTERP_SA_EMIN_HPP
#define AMGCL_INTERP_SA_EMIN_HPP

/*
The MIT License

Copyright (c) 2012 Denis Demidov <ddemidov@ksu.ru>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   interp_sa_emin.hpp
 * \author Denis Demidov <ddemidov@ksu.ru>
 * \brief  Interpolation scheme based on smoothed aggregation with energy minimization.
 */

#include <vector>
#include <algorithm>
#include <functional>

#include <boost/typeof/typeof.hpp>

#include <amgcl/spmat.hpp>
#include <amgcl/aggr_connect.hpp>
#include <amgcl/tictoc.hpp>

namespace amgcl {

namespace interp {

/// Interpolation scheme based on smoothed aggregation with energy minimization.
/**
 * See \ref Sala_2008 "Sala (2008)"
 *
 * \param aggr_type \ref aggregation "Aggregation scheme".
 *
 * \ingroup interpolation
 */
template <class aggr_type>
struct sa_emin {

/// Parameters controlling aggregation.
struct params {
    /// Parameter \f$\varepsilon_{str}\f$ defining strong couplings.
    /**
     * Variable \f$i\f$ is defined to be strongly coupled to another variable,
     * \f$j\f$, if \f[|a_{ij}| \geq \varepsilon\sqrt{a_{ii} a_{jj}}\quad
     * \text{with fixed} \quad \varepsilon = \varepsilon_{str} \left(
     * \frac{1}{2} \right)^l,\f]
     * where \f$l\f$ is level number (finest level is 0).
     */
    mutable float eps_strong;

    params() : eps_strong(0.08f) {}
};

/// Constructs coarse level by aggregation.
/**
 * Returns interpolation operator, which is enough to construct system matrix
 * at coarser level.
 *
 * \param A   system matrix.
 * \param prm parameters.
 *
 * \returns interpolation operator.
 */
template < class value_t, class index_t >
static std::pair<
    sparse::matrix<value_t, index_t>,
    sparse::matrix<value_t, index_t>
    >
interp(const sparse::matrix<value_t, index_t> &A, const params &prm) {
    TIC("aggregates");
    BOOST_AUTO(aggr, aggr_type::aggregates(A, aggr::connect(A, prm.eps_strong)));
    prm.eps_strong *= 0.5;
    TOC("aggregates");

    const index_t n = sparse::matrix_rows(A);

    index_t nc = std::max(
            static_cast<index_t>(0),
            *std::max_element(aggr.begin(), aggr.end()) + static_cast<index_t>(1)
            );

    TIC("tentative interpolation");
    sparse::matrix<value_t, index_t> P_tent(n, nc);
    P_tent.col.reserve(n);
    P_tent.val.reserve(n);

    P_tent.row[0] = 0;
    for(index_t i = 0; i < n; ++i) {
        if (aggr[i] >= 0) {
            P_tent.row[i + 1] = P_tent.row[i] + 1;
            P_tent.col.push_back(aggr[i]);
            P_tent.val.push_back(static_cast<value_t>(1));
        } else {
            P_tent.row[i + 1] = P_tent.row[i];
        }
    }
    BOOST_AUTO(R_tent, sparse::transpose(P_tent));
    TOC("tentative interpolation");

    // Compute smoothed nterpolation and restriction operators.
    static std::pair<
        sparse::matrix<value_t, index_t>,
        sparse::matrix<value_t, index_t>
    > PR;

    TIC("smoothed interpolation");
    improve_tentative_interp(A, P_tent, aggr).swap(PR.first);
    TOC("smoothed interpolation");

    TIC("smoothed restriction");
    sparse::transpose(
            improve_tentative_interp(sparse::transpose(A), P_tent, aggr)
            ).swap(PR.second);
    TOC("smoothed restriction");

    return PR;
}

private:

template <class spmat>
static std::vector<typename sparse::matrix_value<spmat>::type>
colwise_inner_prod(const spmat &A, const spmat &B) {
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(A);

    assert(n == sparse::matrix_rows(B));
    assert(m == sparse::matrix_cols(B));

    BOOST_AUTO(Arow, sparse::matrix_outer_index(A));
    BOOST_AUTO(Acol, sparse::matrix_inner_index(A));
    BOOST_AUTO(Aval, sparse::matrix_values(A));

    BOOST_AUTO(Brow, sparse::matrix_outer_index(B));
    BOOST_AUTO(Bcol, sparse::matrix_inner_index(B));
    BOOST_AUTO(Bval, sparse::matrix_values(B));

    std::vector<value_t> sum(m, static_cast<value_t>(0));

    for(index_t i = 0; i < n; ++i) {
        for(
                index_t ja = Arow[i], ea = Arow[i + 1],
                        jb = Brow[i], eb = Brow[i + 1];
                ja < ea && jb < eb;
           )
        {
            index_t ca = Acol[ja];
            index_t cb = Bcol[jb];

            if (ca < cb)
                ++ja;
            else if (cb < ca)
                ++jb;
            else /*ca == cb*/ {
                sum[ca] += Aval[ja] * Bval[jb];
                ++ja;
                ++jb;
            }
        }
    }

    return sum;
}

template <class spmat>
static std::vector<typename sparse::matrix_value<spmat>::type>
colwise_norm(const spmat &A) {
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(A);

    BOOST_AUTO(Arow, sparse::matrix_outer_index(A));
    BOOST_AUTO(Acol, sparse::matrix_inner_index(A));
    BOOST_AUTO(Aval, sparse::matrix_values(A));

    std::vector<value_t> sum(m, static_cast<value_t>(0));

    for(index_t i = 0; i < n; ++i)
        for(index_t j = Arow[i], e = Arow[i + 1]; j < e; ++j)
            sum[Acol[j]] += Aval[j] * Aval[j];

    return sum;
}

template <class spmat>
static spmat improve_tentative_interp(const spmat &A, const spmat &P_tent,
        const std::vector<typename sparse::matrix_index<spmat>::type> &aggr)
{
    typedef typename sparse::matrix_value<spmat>::type value_t;
    typedef typename sparse::matrix_index<spmat>::type index_t;

    const index_t n = sparse::matrix_rows(A);
    const index_t m = sparse::matrix_cols(P_tent);

    BOOST_AUTO(D, sparse::diagonal(A));
    BOOST_AUTO(AP, sparse::prod(A, P_tent));
    BOOST_AUTO(DAP, AP);
    for(index_t i = 0; i < n; ++i) {
        value_t dinv = 1 / D[i];
        D[i] = dinv;
        for(index_t j = DAP.row[i], e = DAP.row[i + 1]; j < e; ++j)
            DAP.val[j] *= dinv;
    }
    BOOST_AUTO(ADAP, sparse::prod(A, DAP));

    sparse::sort_rows(AP);
    sparse::sort_rows(ADAP);

    BOOST_AUTO(num, colwise_inner_prod(AP, ADAP));
    BOOST_AUTO(den, colwise_norm(ADAP));

    std::vector<value_t> omega(m);
    std::transform(num.begin(), num.end(), den.begin(), omega.begin(),
            std::divides<value_t>());

    // Update DAP to obtain P.
    for(index_t i = 0; i < n; ++i) {
        for(index_t j = DAP.row[i], e = DAP.row[i + 1]; j < e; ++j) {
            index_t c = DAP.col[j];
            DAP.val[j] *= -omega[c];
            if (c == aggr[i]) DAP.val[j] += 1;
        }
    }

    return DAP;
}

};

} // namespace interp
} // namespace amgcl

#endif
<|endoftext|>


<commit_before>#include "test_helper.h"
#include "helpers/read_test_entries.h"
#include "helpers/load_language.h"
#include "helpers/stderr_logger.h"
#include "helpers/file_helpers.h"
#include "helpers/tree_helpers.h"
#include "runtime/alloc.h"

START_TEST

string grammars_dir_path = "test/fixtures/test_grammars";
vector<string> test_languages = list_directory(grammars_dir_path);

for (auto &language_name : test_languages) {
  if (language_name == "readme.md") continue;

  describe(("test grammar: " + language_name).c_str(), [&]() {
    string directory_path = grammars_dir_path + "/" + language_name;
    string grammar_path = directory_path + "/grammar.json";
    string grammar_json = read_file(grammar_path);
    string expected_error_path = directory_path + "/expected_error.txt";
    const TSLanguage *language = nullptr;

    if (file_exists(expected_error_path)) {
      it("fails with the correct error message", [&]() {
        TSCompileResult compile_result = ts_compile_grammar(grammar_json.c_str());
        string expected_error = read_file(expected_error_path);
        AssertThat((void *)compile_result.error_message, !Equals<void *>(nullptr));
        AssertThat(compile_result.error_message, Equals(expected_error));
      });
      return;
    }

    for (auto &entry : read_test_language_corpus(language_name)) {
      it(("parses " + entry.description).c_str(), [&]() {
        if (!language) {
          string external_scanner_path = directory_path + "/scanner.c";
          if (!file_exists(external_scanner_path)) external_scanner_path = "";

          TSCompileResult compile_result = ts_compile_grammar(grammar_json.c_str());

          language = load_test_language(
            language_name,
            compile_result,
            external_scanner_path
          );
        }

        TSDocument *document = ts_document_new();
        ts_document_set_language(document, language);
        ts_document_set_input_string_with_length(document, entry.input.c_str(), entry.input.size());

        // ts_document_set_logger(document, stderr_logger_new(true));
        // ts_document_print_debugging_graphs(document, true);
        ts_document_parse(document);

        TSNode root_node = ts_document_root_node(document);
        assert_consistent_tree_sizes(root_node);
        const char *node_string = ts_node_string(root_node, document);
        string result(node_string);
        ts_free((void *)node_string);
        ts_document_free(document);

        AssertThat(result, Equals(entry.tree_string));
      });
    }
  });
}

END_TEST
<commit_msg>Construct paths portably in test_grammars.cc<commit_after>#include "test_helper.h"
#include "helpers/read_test_entries.h"
#include "helpers/load_language.h"
#include "helpers/stderr_logger.h"
#include "helpers/file_helpers.h"
#include "helpers/tree_helpers.h"
#include "runtime/alloc.h"

START_TEST

string grammars_dir_path = join_path({"test", "fixtures", "test_grammars"});
vector<string> test_languages = list_directory(grammars_dir_path);

for (auto &language_name : test_languages) {
  if (language_name == "readme.md") continue;

  describe(("test grammar: " + language_name).c_str(), [&]() {
    string directory_path = join_path({grammars_dir_path, language_name});
    string grammar_path = join_path({directory_path, "grammar.json"});
    string expected_error_path = join_path({directory_path, "expected_error.txt"});

    string grammar_json = read_file(grammar_path);
    const TSLanguage *language = nullptr;

    if (file_exists(expected_error_path)) {
      it("fails with the correct error message", [&]() {
        TSCompileResult compile_result = ts_compile_grammar(grammar_json.c_str());
        string expected_error = read_file(expected_error_path);
        AssertThat((void *)compile_result.error_message, !Equals<void *>(nullptr));
        AssertThat(compile_result.error_message, Equals(expected_error));
      });
      return;
    }

    for (auto &entry : read_test_language_corpus(language_name)) {
      it(("parses " + entry.description).c_str(), [&]() {
        if (!language) {
          string external_scanner_path = join_path({directory_path, "scanner.c"});
          if (!file_exists(external_scanner_path)) external_scanner_path = "";

          TSCompileResult compile_result = ts_compile_grammar(grammar_json.c_str());

          language = load_test_language(
            language_name,
            compile_result,
            external_scanner_path
          );
        }

        TSDocument *document = ts_document_new();
        ts_document_set_language(document, language);
        ts_document_set_input_string_with_length(document, entry.input.c_str(), entry.input.size());

        // ts_document_set_logger(document, stderr_logger_new(true));
        // ts_document_print_debugging_graphs(document, true);
        ts_document_parse(document);

        TSNode root_node = ts_document_root_node(document);
        assert_consistent_tree_sizes(root_node);
        const char *node_string = ts_node_string(root_node, document);
        string result(node_string);
        ts_free((void *)node_string);
        ts_document_free(document);

        AssertThat(result, Equals(entry.tree_string));
      });
    }
  });
}

END_TEST
<|endoftext|>