├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── include └── chainbase │ ├── chainbase.hpp │ ├── chainbase_node_allocator.hpp │ ├── environment.hpp │ ├── pinnable_mapped_file.hpp │ ├── shared_cow_string.hpp │ └── undo_index.hpp ├── src ├── chainbase.cpp └── pinnable_mapped_file.cpp └── test ├── CMakeLists.txt ├── grow_shrink.cpp ├── test.cpp └── undo_index.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Defines ChainBase library target. 2 | project( ChainBase ) 3 | cmake_minimum_required( VERSION 3.5 ) 4 | 5 | #list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules" ) 6 | 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS "ON") 8 | 9 | SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) 10 | 11 | include( GNUInstallDirs ) 12 | 13 | IF( WIN32 ) 14 | SET(BOOST_ROOT $ENV{BOOST_ROOT}) 15 | set(Boost_USE_MULTITHREADED ON) 16 | set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries 17 | ENDIF(WIN32) 18 | 19 | FIND_PACKAGE(Boost 1.57 REQUIRED COMPONENTS filesystem unit_test_framework) 20 | 21 | SET(PLATFORM_LIBRARIES) 22 | 23 | if(CMAKE_CXX_STANDARD EQUAL 98) 24 | message(FATAL_ERROR "chainbase requires c++17 or newer") 25 | elseif(NOT CMAKE_CXX_STANDARD) 26 | set(CMAKE_CXX_STANDARD 17) 27 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 28 | endif() 29 | 30 | if( APPLE ) 31 | # Apple Specific Options Here 32 | message( STATUS "Configuring ChainBase on OS X" ) 33 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-conversion" ) 34 | else( APPLE ) 35 | # Linux Specific Options Here 36 | message( STATUS "Configuring ChainBase on Linux" ) 37 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) 38 | if ( FULL_STATIC_BUILD ) 39 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") 40 | endif ( FULL_STATIC_BUILD ) 41 | LIST( APPEND PLATFORM_LIBRARIES pthread ) 42 | endif( APPLE ) 43 | 44 | if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) 45 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp" ) 46 | endif() 47 | 48 | if( "${CMAKE_GENERATOR}" STREQUAL "Ninja" ) 49 | if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) 50 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics" ) 51 | endif() 52 | endif() 53 | 54 | # based on http://www.delorie.com/gnu/docs/gdb/gdb_70.html 55 | # uncomment this line to tell GDB about macros (slows compile times) 56 | # set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-2 -g3" ) 57 | 58 | set(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL "Build ChainBase for code coverage analysis") 59 | 60 | if(ENABLE_COVERAGE_TESTING) 61 | SET(CMAKE_CXX_FLAGS "--coverage ${CMAKE_CXX_FLAGS}") 62 | endif() 63 | 64 | 65 | file(GLOB HEADERS "include/chainbase/*.hpp") 66 | add_library( chainbase src/chainbase.cpp src/pinnable_mapped_file.cpp ${HEADERS} ) 67 | target_link_libraries( chainbase Boost::filesystem ${PLATFORM_LIBRARIES} ) 68 | target_include_directories( chainbase PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) 69 | 70 | if(WIN32) 71 | target_link_libraries( chainbase ws2_32 mswsock ) 72 | endif() 73 | 74 | add_subdirectory( test ) 75 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/chainbase DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 76 | 77 | install(TARGETS chainbase 78 | LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} 79 | ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) 80 | 81 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Steemit, Inc 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChainBase - a fast version controlled, transactional database 2 | 3 | ChainBase is designed to meet the demanding requirments of blockchain applications, but is suitable for use 4 | in any application that requires a robust transactional database with the ability have near-infinite levels of undo 5 | history. 6 | 7 | While chainbase was designed for blockchain applications, it is suitable for any program that needs to 8 | persist complex application state with the ability to undo. 9 | 10 | ## Features 11 | 12 | - Supports multiple objects (tables) with multiple indicies (based upon boost::multi_index_container) 13 | - State is persistant and sharable among multiple processes 14 | - Nested Transactional Writes with ability to undo changes 15 | 16 | ## Dependencies 17 | 18 | - C++17 19 | - [Boost](http://www.boost.org/) 20 | - CMake Build Process 21 | - Supports Linux, Mac OS X (no Windows Support) 22 | 23 | ## Example Usage 24 | 25 | ``` c++ 26 | enum tables { 27 | book_table 28 | }; 29 | 30 | /** 31 | * Defines a "table" for storing books. This table is assigned a 32 | * globally unique ID (book_table) and must inherit from chainbase::object<> which 33 | * decorates the book type by defining "id_type" and "type_id" 34 | */ 35 | struct book : public chainbase::object { 36 | 37 | /** defines a default constructor for types that don't have 38 | * members requiring dynamic memory allocation. 39 | */ 40 | CHAINBASE_DEFAULT_CONSTRUCTOR( book ) 41 | 42 | id_type id; ///< this manditory member is a primary key 43 | int pages = 0; 44 | int publish_date = 0; 45 | }; 46 | 47 | struct by_id; 48 | struct by_pages; 49 | struct by_date; 50 | 51 | /** 52 | * This is a relatively standard boost multi_index_container definition that has three 53 | * requirements to be used withn a chainbase database: 54 | * - it must use chainbase::allocator 55 | * - the first index must be on the primary key (id) and must be unique (hashed or ordered) 56 | */ 57 | typedef multi_index_container< 58 | book, 59 | indexed_by< 60 | ordered_unique< tag, member >, ///< required 61 | ordered_non_unique< tag, BOOST_MULTI_INDEX_MEMBER(book,int,pages) >, 62 | ordered_non_unique< tag, BOOST_MULTI_INDEX_MEMBER(book,int,publish_date) > 63 | >, 64 | chainbase::allocator ///< required for use with chainbase::database 65 | > book_index; 66 | 67 | /** 68 | This simple program will open database_dir and add two new books every time 69 | it is run and then print out all of the books in the database. 70 | */ 71 | int main( int argc, char** argv ) { 72 | chainbase::database db; 73 | db.open( "database_dir", database::read_write, 1024*1024*8 ); /// open or create a database with 8MB capacity 74 | db.add_index< book_index >(); /// open or create the book_index 75 | 76 | 77 | const auto& book_idx = db.get_index().indicies(); 78 | 79 | /** 80 | Returns a const reference to the book, this pointer will remain 81 | valid until the book is removed from the database. 82 | */ 83 | const auto& new_book300 = db.create( [&]( book& b ) { 84 | b.pages = 300+book_idx.size(); 85 | } ); 86 | const auto& new_book400 = db.create( [&]( book& b ) { 87 | b.pages = 300+book_idx.size(); 88 | } ); 89 | 90 | /** 91 | You modify a book by passing in a lambda that receives a 92 | non-const reference to the book you wish to modify. 93 | */ 94 | db.modify( new_book300, [&]( book& b ) { 95 | b.pages++; 96 | }); 97 | 98 | for( const auto& b : book_idx ) { 99 | std::cout << b.pages << "\n"; 100 | } 101 | 102 | auto itr = book_idx.get().lower_bound( 100 ); 103 | if( itr != book_idx.get().end() ) { 104 | std::cout << itr->pages; 105 | } 106 | 107 | db.remove( new_book400 ); 108 | 109 | return 0; 110 | } 111 | 112 | ``` 113 | 114 | ## Concurrent Access 115 | 116 | By default ChainBase provides no synchronization and has the same concurrency restrictions as any 117 | boost::multi_index_container. This means that two or more threads may read the database at the 118 | same time, but all writes must be protected by a mutex. 119 | 120 | Multiple processes may open the same database if care is taken to use interpocess locking on the 121 | database. 122 | 123 | ## Persistance 124 | 125 | By default data is only flushed to disk upon request or when the program exits. So long as the program 126 | does not crash in the middle of a call to db.modify(), or db.create() the content of the 127 | database should remain in a consistant state. This means that you should minimize the complexity of the 128 | lambdas used to create and/or modify state. 129 | 130 | If the operating system crashes or the computer loses power, then the database will be left in an undefined 131 | state depending upon which memory pages that operating system was able to sync to disk. 132 | 133 | ChainBase was designed to be used with blockchain applications where an append-only log of blocks is used 134 | to secure state in the event of power loss. This block log can be replayed to regenerate the full database 135 | state. Dealing with OS crashes, loss of power, and logs, is beyond the scope of ChainBase. 136 | 137 | ## Portability 138 | 139 | The contents of the database file is dependent upon the memory layout of the computer and process that created 140 | the database. Moving the database to a machine that uses a different compiler, operating system, libraries, or 141 | build type (release vs debug) will result in undefined behavior. 142 | 143 | If portability is desired, the developer will have to export the database to a suitable format. 144 | 145 | ## Background 146 | 147 | Blockchain applications depend upon a high performance database capable of millions of read/write 148 | operations per second. Additionally blockchains operate on the basis of "eventually consistant" which 149 | means that any changes made to the database are potentially reversible for an unknown amount of time depending 150 | upon the consenus protocol used. 151 | 152 | Existing database such as [libbitcoin Database](https://github.com/libbitcoin/libbitcoin-database) achieve high 153 | peformance using similar techniques (memory mapped files), but they are heavily specialised and do not implement 154 | the logic necessary for multiple indicies or undo history. 155 | 156 | Databases such as LevelDB provide a simple Key/Value database, but suffer from poor performance relative to 157 | memory mapped file implementations. 158 | -------------------------------------------------------------------------------- /include/chainbase/chainbase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifndef CHAINBASE_NUM_RW_LOCKS 36 | #define CHAINBASE_NUM_RW_LOCKS 10 37 | #endif 38 | 39 | #ifdef CHAINBASE_CHECK_LOCKING 40 | #define CHAINBASE_REQUIRE_READ_LOCK(m, t) require_read_lock(m, typeid(t).name()) 41 | #define CHAINBASE_REQUIRE_WRITE_LOCK(m, t) require_write_lock(m, typeid(t).name()) 42 | #else 43 | #define CHAINBASE_REQUIRE_READ_LOCK(m, t) 44 | #define CHAINBASE_REQUIRE_WRITE_LOCK(m, t) 45 | #endif 46 | 47 | namespace chainbase { 48 | 49 | namespace bip = boost::interprocess; 50 | namespace bfs = boost::filesystem; 51 | using std::unique_ptr; 52 | using std::vector; 53 | 54 | template 55 | using allocator = bip::allocator; 56 | 57 | template 58 | using node_allocator = chainbase_node_allocator; 59 | 60 | using shared_string = shared_cow_string; 61 | 62 | typedef boost::interprocess::interprocess_sharable_mutex read_write_mutex; 63 | typedef boost::interprocess::sharable_lock< read_write_mutex > read_lock; 64 | 65 | /** 66 | * Object ID type that includes the type of the object it references 67 | */ 68 | template 69 | class oid { 70 | public: 71 | oid( int64_t i = 0 ):_id(i){} 72 | 73 | oid& operator++() { ++_id; return *this; } 74 | 75 | friend bool operator < ( const oid& a, const oid& b ) { return a._id < b._id; } 76 | friend bool operator > ( const oid& a, const oid& b ) { return a._id > b._id; } 77 | friend bool operator <= ( const oid& a, const oid& b ) { return a._id <= b._id; } 78 | friend bool operator >= ( const oid& a, const oid& b ) { return a._id >= b._id; } 79 | friend bool operator == ( const oid& a, const oid& b ) { return a._id == b._id; } 80 | friend bool operator != ( const oid& a, const oid& b ) { return a._id != b._id; } 81 | friend std::ostream& operator<<(std::ostream& s, const oid& id) { 82 | s << boost::core::demangle(typeid(oid).name()) << '(' << id._id << ')'; return s; 83 | } 84 | 85 | int64_t _id = 0; 86 | }; 87 | 88 | template 89 | struct object 90 | { 91 | typedef oid id_type; 92 | static const uint16_t type_id = TypeNumber; 93 | }; 94 | 95 | /** this class is ment to be specified to enable lookup of index type by object type using 96 | * the SET_INDEX_TYPE macro. 97 | **/ 98 | template 99 | struct get_index_type {}; 100 | 101 | /** 102 | * This macro must be used at global scope and OBJECT_TYPE and INDEX_TYPE must be fully qualified 103 | */ 104 | #define CHAINBASE_SET_INDEX_TYPE( OBJECT_TYPE, INDEX_TYPE ) \ 105 | namespace chainbase { template<> struct get_index_type { typedef INDEX_TYPE type; }; } 106 | 107 | #define CHAINBASE_DEFAULT_CONSTRUCTOR( OBJECT_TYPE ) \ 108 | template \ 109 | OBJECT_TYPE( Constructor&& c, Allocator&& ) { c(*this); } 110 | 111 | /** 112 | * The code we want to implement is this: 113 | * 114 | * ++target; try { ... } finally { --target } 115 | * 116 | * In C++ the only way to implement finally is to create a class 117 | * with a destructor, so that's what we do here. 118 | */ 119 | class int_incrementer 120 | { 121 | public: 122 | int_incrementer( int32_t& target ) : _target(target) 123 | { ++_target; } 124 | ~int_incrementer() 125 | { --_target; } 126 | 127 | int32_t get()const 128 | { return _target; } 129 | 130 | private: 131 | int32_t& _target; 132 | }; 133 | 134 | template 135 | using generic_index = multi_index_to_undo_index; 136 | 137 | class abstract_session { 138 | public: 139 | virtual ~abstract_session(){}; 140 | virtual void push() = 0; 141 | virtual void squash() = 0; 142 | virtual void undo() = 0; 143 | }; 144 | 145 | template 146 | class session_impl : public abstract_session 147 | { 148 | public: 149 | session_impl( SessionType&& s ):_session( std::move( s ) ){} 150 | 151 | virtual void push() override { _session.push(); } 152 | virtual void squash() override{ _session.squash(); } 153 | virtual void undo() override { _session.undo(); } 154 | private: 155 | SessionType _session; 156 | }; 157 | 158 | class abstract_index 159 | { 160 | public: 161 | abstract_index( void* i ):_idx_ptr(i){} 162 | virtual ~abstract_index(){} 163 | virtual void set_revision( uint64_t revision ) = 0; 164 | virtual unique_ptr start_undo_session( bool enabled ) = 0; 165 | 166 | virtual int64_t revision()const = 0; 167 | virtual void undo()const = 0; 168 | virtual void squash()const = 0; 169 | virtual void commit( int64_t revision )const = 0; 170 | virtual void undo_all()const = 0; 171 | virtual uint32_t type_id()const = 0; 172 | virtual uint64_t row_count()const = 0; 173 | virtual const std::string& type_name()const = 0; 174 | virtual std::pair undo_stack_revision_range()const = 0; 175 | 176 | virtual void remove_object( int64_t id ) = 0; 177 | 178 | void* get()const { return _idx_ptr; } 179 | private: 180 | void* _idx_ptr; 181 | }; 182 | 183 | template 184 | class index_impl : public abstract_index { 185 | public: 186 | index_impl( BaseIndex& base ):abstract_index( &base ),_base(base){} 187 | 188 | virtual unique_ptr start_undo_session( bool enabled ) override { 189 | return unique_ptr(new session_impl( _base.start_undo_session( enabled ) ) ); 190 | } 191 | 192 | virtual void set_revision( uint64_t revision ) override { _base.set_revision( revision ); } 193 | virtual int64_t revision()const override { return _base.revision(); } 194 | virtual void undo()const override { _base.undo(); } 195 | virtual void squash()const override { _base.squash(); } 196 | virtual void commit( int64_t revision )const override { _base.commit(revision); } 197 | virtual void undo_all() const override {_base.undo_all(); } 198 | virtual uint32_t type_id()const override { return BaseIndex::value_type::type_id; } 199 | virtual uint64_t row_count()const override { return _base.indices().size(); } 200 | virtual const std::string& type_name() const override { return BaseIndex_name; } 201 | virtual std::pair undo_stack_revision_range()const override { return _base.undo_stack_revision_range(); } 202 | 203 | virtual void remove_object( int64_t id ) override { return _base.remove_object( id ); } 204 | private: 205 | BaseIndex& _base; 206 | std::string BaseIndex_name = boost::core::demangle( typeid( typename BaseIndex::value_type ).name() ); 207 | }; 208 | 209 | template 210 | class index : public index_impl { 211 | public: 212 | index( IndexType& i ):index_impl( i ){} 213 | }; 214 | 215 | 216 | class read_write_mutex_manager 217 | { 218 | public: 219 | read_write_mutex_manager() 220 | { 221 | _current_lock = 0; 222 | } 223 | 224 | ~read_write_mutex_manager(){} 225 | 226 | void next_lock() 227 | { 228 | _current_lock++; 229 | new( &_locks[ _current_lock % CHAINBASE_NUM_RW_LOCKS ] ) read_write_mutex(); 230 | } 231 | 232 | read_write_mutex& current_lock() 233 | { 234 | return _locks[ _current_lock % CHAINBASE_NUM_RW_LOCKS ]; 235 | } 236 | 237 | uint32_t current_lock_num() 238 | { 239 | return _current_lock; 240 | } 241 | 242 | private: 243 | std::array< read_write_mutex, CHAINBASE_NUM_RW_LOCKS > _locks; 244 | std::atomic< uint32_t > _current_lock; 245 | }; 246 | 247 | 248 | /** 249 | * This class 250 | */ 251 | class database 252 | { 253 | public: 254 | enum open_flags { 255 | read_only = 0, 256 | read_write = 1 257 | }; 258 | 259 | using database_index_row_count_multiset = std::multiset>; 260 | 261 | database(const bfs::path& dir, open_flags write = read_only, uint64_t shared_file_size = 0, bool allow_dirty = false, 262 | pinnable_mapped_file::map_mode = pinnable_mapped_file::map_mode::mapped, 263 | std::vector hugepage_paths = std::vector()); 264 | ~database(); 265 | database(database&&) = default; 266 | database& operator=(database&&) = default; 267 | bool is_read_only() const { return _read_only; } 268 | void flush(); 269 | void set_require_locking( bool enable_require_locking ); 270 | 271 | #ifdef CHAINBASE_CHECK_LOCKING 272 | void require_lock_fail( const char* method, const char* lock_type, const char* tname )const; 273 | 274 | void require_read_lock( const char* method, const char* tname )const 275 | { 276 | if( BOOST_UNLIKELY( _enable_require_locking & _read_only & (_read_lock_count <= 0) ) ) 277 | require_lock_fail(method, "read", tname); 278 | } 279 | 280 | void require_write_lock( const char* method, const char* tname ) 281 | { 282 | if( BOOST_UNLIKELY( _enable_require_locking & (_write_lock_count <= 0) ) ) 283 | require_lock_fail(method, "write", tname); 284 | } 285 | #endif 286 | 287 | struct session { 288 | public: 289 | session( session&& s ):_index_sessions( std::move(s._index_sessions) ){} 290 | session( vector>&& s ):_index_sessions( std::move(s) ) 291 | { 292 | } 293 | 294 | ~session() { 295 | undo(); 296 | } 297 | 298 | void push() 299 | { 300 | for( auto& i : _index_sessions ) i->push(); 301 | _index_sessions.clear(); 302 | } 303 | 304 | void squash() 305 | { 306 | for( auto& i : _index_sessions ) i->squash(); 307 | _index_sessions.clear(); 308 | } 309 | 310 | void undo() 311 | { 312 | for( auto& i : _index_sessions ) i->undo(); 313 | _index_sessions.clear(); 314 | } 315 | 316 | private: 317 | friend class database; 318 | session(){} 319 | 320 | vector< std::unique_ptr > _index_sessions; 321 | }; 322 | 323 | session start_undo_session( bool enabled ); 324 | 325 | int64_t revision()const { 326 | if( _index_list.size() == 0 ) return -1; 327 | return _index_list[0]->revision(); 328 | } 329 | 330 | void undo(); 331 | void squash(); 332 | void commit( int64_t revision ); 333 | void undo_all(); 334 | 335 | 336 | void set_revision( uint64_t revision ) 337 | { 338 | CHAINBASE_REQUIRE_WRITE_LOCK( "set_revision", uint64_t ); 339 | for( auto i : _index_list ) i->set_revision( revision ); 340 | } 341 | 342 | 343 | template 344 | void add_index() { 345 | const uint16_t type_id = generic_index::value_type::type_id; 346 | typedef generic_index index_type; 347 | typedef typename index_type::allocator_type index_alloc; 348 | 349 | std::string type_name = boost::core::demangle( typeid( typename index_type::value_type ).name() ); 350 | 351 | if( !( _index_map.size() <= type_id || _index_map[ type_id ] == nullptr ) ) { 352 | BOOST_THROW_EXCEPTION( std::logic_error( type_name + "::type_id is already in use" ) ); 353 | } 354 | 355 | index_type* idx_ptr = nullptr; 356 | if( _read_only ) 357 | idx_ptr = _db_file.get_segment_manager()->find_no_lock< index_type >( type_name.c_str() ).first; 358 | else 359 | idx_ptr = _db_file.get_segment_manager()->find< index_type >( type_name.c_str() ).first; 360 | bool first_time_adding = false; 361 | if( !idx_ptr ) { 362 | if( _read_only ) { 363 | BOOST_THROW_EXCEPTION( std::runtime_error( "unable to find index for " + type_name + " in read only database" ) ); 364 | } 365 | first_time_adding = true; 366 | idx_ptr = _db_file.get_segment_manager()->construct< index_type >( type_name.c_str() )( index_alloc( _db_file.get_segment_manager() ) ); 367 | } 368 | 369 | idx_ptr->validate(); 370 | 371 | // Ensure the undo stack of added index is consistent with the other indices in the database 372 | if( _index_list.size() > 0 ) { 373 | auto expected_revision_range = _index_list.front()->undo_stack_revision_range(); 374 | auto added_index_revision_range = idx_ptr->undo_stack_revision_range(); 375 | 376 | if( added_index_revision_range.first != expected_revision_range.first || 377 | added_index_revision_range.second != expected_revision_range.second ) { 378 | 379 | if( !first_time_adding ) { 380 | BOOST_THROW_EXCEPTION( std::logic_error( 381 | "existing index for " + type_name + " has an undo stack (revision range [" + 382 | std::to_string(added_index_revision_range.first) + ", " + std::to_string(added_index_revision_range.second) + 383 | "]) that is inconsistent with other indices in the database (revision range [" + 384 | std::to_string(expected_revision_range.first) + ", " + std::to_string(expected_revision_range.second) + 385 | "]); corrupted database?" 386 | ) ); 387 | } 388 | 389 | if( _read_only ) { 390 | BOOST_THROW_EXCEPTION( std::logic_error( 391 | "new index for " + type_name + 392 | " requires an undo stack that is consistent with other indices in the database; cannot fix in read-only mode" 393 | ) ); 394 | } 395 | 396 | idx_ptr->set_revision( static_cast(expected_revision_range.first) ); 397 | while( idx_ptr->revision() < expected_revision_range.second ) { 398 | idx_ptr->start_undo_session(true).push(); 399 | } 400 | } 401 | } 402 | 403 | if( type_id >= _index_map.size() ) 404 | _index_map.resize( type_id + 1 ); 405 | 406 | auto new_index = new index( *idx_ptr ); 407 | _index_map[ type_id ].reset( new_index ); 408 | _index_list.push_back( new_index ); 409 | } 410 | 411 | auto get_segment_manager() -> decltype( ((pinnable_mapped_file*)nullptr)->get_segment_manager()) { 412 | return _db_file.get_segment_manager(); 413 | } 414 | 415 | auto get_segment_manager()const -> std::add_const_t< decltype( ((pinnable_mapped_file*)nullptr)->get_segment_manager() ) > { 416 | return _db_file.get_segment_manager(); 417 | } 418 | 419 | size_t get_free_memory()const 420 | { 421 | return _db_file.get_segment_manager()->get_free_memory(); 422 | } 423 | 424 | template 425 | const generic_index& get_index()const 426 | { 427 | CHAINBASE_REQUIRE_READ_LOCK("get_index", typename MultiIndexType::value_type); 428 | typedef generic_index index_type; 429 | typedef index_type* index_type_ptr; 430 | assert( _index_map.size() > index_type::value_type::type_id ); 431 | assert( _index_map[index_type::value_type::type_id] ); 432 | return *index_type_ptr( _index_map[index_type::value_type::type_id]->get() ); 433 | } 434 | 435 | template 436 | auto get_index()const -> decltype( ((generic_index*)( nullptr ))->indices().template get() ) 437 | { 438 | CHAINBASE_REQUIRE_READ_LOCK("get_index", typename MultiIndexType::value_type); 439 | typedef generic_index index_type; 440 | typedef index_type* index_type_ptr; 441 | assert( _index_map.size() > index_type::value_type::type_id ); 442 | assert( _index_map[index_type::value_type::type_id] ); 443 | return index_type_ptr( _index_map[index_type::value_type::type_id]->get() )->indices().template get(); 444 | } 445 | 446 | template 447 | generic_index& get_mutable_index() 448 | { 449 | CHAINBASE_REQUIRE_WRITE_LOCK("get_mutable_index", typename MultiIndexType::value_type); 450 | typedef generic_index index_type; 451 | typedef index_type* index_type_ptr; 452 | assert( _index_map.size() > index_type::value_type::type_id ); 453 | assert( _index_map[index_type::value_type::type_id] ); 454 | return *index_type_ptr( _index_map[index_type::value_type::type_id]->get() ); 455 | } 456 | 457 | template< typename ObjectType, typename IndexedByType, typename CompatibleKey > 458 | const ObjectType* find( CompatibleKey&& key )const 459 | { 460 | CHAINBASE_REQUIRE_READ_LOCK("find", ObjectType); 461 | typedef typename get_index_type< ObjectType >::type index_type; 462 | const auto& idx = get_index< index_type >().indices().template get< IndexedByType >(); 463 | auto itr = idx.find( std::forward< CompatibleKey >( key ) ); 464 | if( itr == idx.end() ) return nullptr; 465 | return &*itr; 466 | } 467 | 468 | template< typename ObjectType > 469 | const ObjectType* find( oid< ObjectType > key = oid< ObjectType >() ) const 470 | { 471 | CHAINBASE_REQUIRE_READ_LOCK("find", ObjectType); 472 | typedef typename get_index_type< ObjectType >::type index_type; 473 | return get_index< index_type >().find( key ); 474 | } 475 | 476 | template< typename ObjectType, typename IndexedByType, typename CompatibleKey > 477 | const ObjectType& get( CompatibleKey&& key )const 478 | { 479 | CHAINBASE_REQUIRE_READ_LOCK("get", ObjectType); 480 | auto obj = find< ObjectType, IndexedByType >( std::forward< CompatibleKey >( key ) ); 481 | if( !obj ) { 482 | std::stringstream ss; 483 | ss << "unknown key (" << boost::core::demangle( typeid( key ).name() ) << "): " << key; 484 | BOOST_THROW_EXCEPTION( std::out_of_range( ss.str().c_str() ) ); 485 | } 486 | return *obj; 487 | } 488 | 489 | template< typename ObjectType > 490 | const ObjectType& get( const oid< ObjectType >& key = oid< ObjectType >() )const 491 | { 492 | CHAINBASE_REQUIRE_READ_LOCK("get", ObjectType); 493 | auto obj = find< ObjectType >( key ); 494 | if( !obj ) { 495 | std::stringstream ss; 496 | ss << "unknown key (" << boost::core::demangle( typeid( key ).name() ) << "): " << key._id; 497 | BOOST_THROW_EXCEPTION( std::out_of_range( ss.str().c_str() ) ); 498 | } 499 | return *obj; 500 | } 501 | 502 | template 503 | void modify( const ObjectType& obj, Modifier&& m ) 504 | { 505 | CHAINBASE_REQUIRE_WRITE_LOCK("modify", ObjectType); 506 | typedef typename get_index_type::type index_type; 507 | get_mutable_index().modify( obj, m ); 508 | } 509 | 510 | template 511 | void remove( const ObjectType& obj ) 512 | { 513 | CHAINBASE_REQUIRE_WRITE_LOCK("remove", ObjectType); 514 | typedef typename get_index_type::type index_type; 515 | return get_mutable_index().remove( obj ); 516 | } 517 | 518 | template 519 | const ObjectType& create( Constructor&& con ) 520 | { 521 | CHAINBASE_REQUIRE_WRITE_LOCK("create", ObjectType); 522 | typedef typename get_index_type::type index_type; 523 | return get_mutable_index().emplace( std::forward(con) ); 524 | } 525 | 526 | database_index_row_count_multiset row_count_per_index()const { 527 | database_index_row_count_multiset ret; 528 | for(const auto& ai_ptr : _index_map) { 529 | if(!ai_ptr) 530 | continue; 531 | ret.emplace(make_pair(ai_ptr->row_count(), ai_ptr->type_name())); 532 | } 533 | return ret; 534 | } 535 | 536 | private: 537 | pinnable_mapped_file _db_file; 538 | bool _read_only = false; 539 | 540 | /** 541 | * This is a sparse list of known indices kept to accelerate creation of undo sessions 542 | */ 543 | vector _index_list; 544 | 545 | /** 546 | * This is a full map (size 2^16) of all possible index designed for constant time lookup 547 | */ 548 | vector> _index_map; 549 | 550 | #ifdef CHAINBASE_CHECK_LOCKING 551 | int32_t _read_lock_count = 0; 552 | int32_t _write_lock_count = 0; 553 | bool _enable_require_locking = false; 554 | #endif 555 | }; 556 | 557 | template 558 | using shared_multi_index_container = boost::multi_index_container >; 559 | } // namepsace chainbase 560 | -------------------------------------------------------------------------------- /include/chainbase/chainbase_node_allocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace chainbase { 9 | 10 | namespace bip = boost::interprocess; 11 | 12 | template 13 | class chainbase_node_allocator { 14 | public: 15 | using value_type = T; 16 | using pointer = bip::offset_ptr; 17 | using segment_manager = pinnable_mapped_file::segment_manager; 18 | chainbase_node_allocator(segment_manager* manager) : _manager{manager} {} 19 | chainbase_node_allocator(const chainbase_node_allocator& other) : _manager(other._manager) {} 20 | template 21 | chainbase_node_allocator(const chainbase_node_allocator& other) : _manager(other._manager) {} 22 | pointer allocate(std::size_t num) { 23 | if (num == 1) { 24 | if (_freelist == nullptr) { 25 | get_some(); 26 | } 27 | list_item* result = &*_freelist; 28 | _freelist = _freelist->_next; 29 | result->~list_item(); 30 | return pointer{(T*)result}; 31 | } else { 32 | return pointer{(T*)_manager->allocate(num*sizeof(T))}; 33 | } 34 | } 35 | void deallocate(const pointer& p, std::size_t num) { 36 | if (num == 1) { 37 | _freelist = new (&*p) list_item{_freelist}; 38 | } else { 39 | _manager->deallocate(&*p); 40 | } 41 | } 42 | bool operator==(const chainbase_node_allocator& other) const { return this == &other; } 43 | bool operator!=(const chainbase_node_allocator& other) const { return this != &other; } 44 | segment_manager* get_segment_manager() const { return _manager.get(); } 45 | private: 46 | template 47 | friend class chainbase_node_allocator; 48 | void get_some() { 49 | static_assert(sizeof(T) >= sizeof(list_item), "Too small for free list"); 50 | static_assert(sizeof(T) % alignof(list_item) == 0, "Bad alignment for free list"); 51 | char* result = (char*)_manager->allocate(sizeof(T) * 64); 52 | _freelist = bip::offset_ptr{(list_item*)result}; 53 | for(int i = 0; i < 63; ++i) { 54 | char* next = result + sizeof(T); 55 | new(result) list_item{bip::offset_ptr{(list_item*)next}}; 56 | result = next; 57 | } 58 | new(result) list_item{nullptr}; 59 | } 60 | struct list_item { bip::offset_ptr _next; }; 61 | bip::offset_ptr _manager; 62 | bip::offset_ptr _freelist{}; 63 | }; 64 | 65 | } // namepsace chainbase 66 | -------------------------------------------------------------------------------- /include/chainbase/environment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace chainbase { 6 | 7 | constexpr size_t header_size = 1024; 8 | constexpr uint64_t header_id = 0x3242444f49534f45ULL; //"EOSIODB2" little endian 9 | 10 | struct environment { 11 | environment() { 12 | strncpy(compiler, __VERSION__, sizeof(compiler)-1); 13 | } 14 | 15 | enum os_t : unsigned char { 16 | OS_LINUX, 17 | OS_MACOS, 18 | OS_WINDOWS, 19 | OS_OTHER 20 | }; 21 | enum arch_t : unsigned char { 22 | ARCH_X86_64, 23 | ARCH_ARM, 24 | ARCH_RISCV, 25 | ARCH_OTHER 26 | }; 27 | 28 | bool debug = 29 | #ifndef NDEBUG 30 | true; 31 | #else 32 | false; 33 | #endif 34 | os_t os = 35 | #if defined(__linux__) 36 | OS_LINUX; 37 | #elif defined(__APPLE__) 38 | OS_MACOS; 39 | #elif defined(_WIN32) 40 | OS_WINDOWS; 41 | #else 42 | OS_OTHER; 43 | #endif 44 | arch_t arch = 45 | #if defined(__x86_64__) 46 | ARCH_X86_64; 47 | #elif defined(__aarch64__) 48 | ARCH_ARM; 49 | #elif defined(__riscv__) 50 | ARCH_RISCV; 51 | #else 52 | ARCH_OTHER; 53 | #endif 54 | 55 | unsigned boost_version = BOOST_VERSION; 56 | uint8_t reserved[512] = {}; 57 | char compiler[256] = {}; 58 | 59 | bool operator==(const environment& other) { 60 | return !memcmp(this, &other, sizeof(environment)); 61 | } 62 | bool operator!=(const environment& other) { 63 | return !(*this == other); 64 | } 65 | } __attribute__ ((packed)); 66 | 67 | struct db_header { 68 | uint64_t id = header_id; 69 | bool dirty = false; 70 | environment dbenviron; 71 | } __attribute__ ((packed)); 72 | 73 | constexpr size_t header_dirty_bit_offset = offsetof(db_header, dirty); 74 | 75 | static_assert(sizeof(db_header) <= header_size, "DB header struct too large"); 76 | 77 | std::ostream& operator<<(std::ostream& os, const chainbase::environment& dt); 78 | 79 | } -------------------------------------------------------------------------------- /include/chainbase/pinnable_mapped_file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace chainbase { 10 | 11 | namespace bip = boost::interprocess; 12 | namespace bfs = boost::filesystem; 13 | 14 | enum db_error_code { 15 | ok = 0, 16 | dirty, 17 | incompatible, 18 | incorrect_db_version, 19 | locked_mode_required, 20 | not_found, 21 | bad_size, 22 | no_huge_page, 23 | no_locked_mode, 24 | bad_header, 25 | no_access, 26 | aborted, 27 | no_mlock 28 | }; 29 | 30 | const std::error_category& chainbase_error_category(); 31 | 32 | inline std::error_code make_error_code(db_error_code e) noexcept { 33 | return std::error_code(static_cast(e), chainbase_error_category()); 34 | } 35 | 36 | class chainbase_error_category : public std::error_category { 37 | public: 38 | const char* name() const noexcept override; 39 | std::string message(int ev) const override; 40 | }; 41 | 42 | class pinnable_mapped_file { 43 | public: 44 | typedef typename bip::managed_mapped_file::segment_manager segment_manager; 45 | 46 | enum map_mode { 47 | mapped, 48 | heap, 49 | locked 50 | }; 51 | 52 | pinnable_mapped_file(const bfs::path& dir, bool writable, uint64_t shared_file_size, bool allow_dirty, map_mode mode, std::vector hugepage_paths); 53 | pinnable_mapped_file(pinnable_mapped_file&& o); 54 | pinnable_mapped_file& operator=(pinnable_mapped_file&&); 55 | pinnable_mapped_file(const pinnable_mapped_file&) = delete; 56 | pinnable_mapped_file& operator=(const pinnable_mapped_file&) = delete; 57 | ~pinnable_mapped_file(); 58 | 59 | segment_manager* get_segment_manager() const { return _segment_manager;} 60 | 61 | private: 62 | void set_mapped_file_db_dirty(bool); 63 | void load_database_file(boost::asio::io_service& sig_ios); 64 | void save_database_file(); 65 | bool all_zeros(char* data, size_t sz); 66 | bip::mapped_region get_huge_region(const std::vector& huge_paths); 67 | 68 | bip::file_lock _mapped_file_lock; 69 | bfs::path _data_file_path; 70 | std::string _database_name; 71 | bool _writable; 72 | 73 | bip::file_mapping _file_mapping; 74 | bip::mapped_region _file_mapped_region; 75 | bip::mapped_region _mapped_region; 76 | 77 | #ifdef _WIN32 78 | bip::permissions _db_permissions; 79 | #else 80 | bip::permissions _db_permissions{S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH}; 81 | #endif 82 | 83 | segment_manager* _segment_manager = nullptr; 84 | 85 | constexpr static unsigned _db_size_multiple_requirement = 1024*1024; //1MB 86 | }; 87 | 88 | std::istream& operator>>(std::istream& in, pinnable_mapped_file::map_mode& runtime); 89 | std::ostream& operator<<(std::ostream& osm, pinnable_mapped_file::map_mode m); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /include/chainbase/shared_cow_string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace chainbase { 15 | 16 | namespace bip = boost::interprocess; 17 | 18 | class shared_cow_string { 19 | struct impl { 20 | uint32_t reference_count; 21 | uint32_t size; 22 | char data[0]; 23 | }; 24 | public: 25 | using allocator_type = bip::allocator; 26 | using iterator = const char*; 27 | using const_iterator = const char*; 28 | explicit shared_cow_string(const allocator_type& alloc) : _data(nullptr), _alloc(alloc) {} 29 | template 30 | explicit shared_cow_string(Iter begin, Iter end, const allocator_type& alloc) : shared_cow_string(alloc) { 31 | std::size_t size = std::distance(begin, end); 32 | impl* new_data = (impl*)&*_alloc.allocate(sizeof(impl) + size + 1); 33 | new_data->reference_count = 1; 34 | new_data->size = size; 35 | std::copy(begin, end, new_data->data); 36 | new_data->data[size] = '\0'; 37 | _data = new_data; 38 | } 39 | explicit shared_cow_string(const char* ptr, std::size_t size, const allocator_type& alloc) : shared_cow_string(alloc) { 40 | impl* new_data = (impl*)&*_alloc.allocate(sizeof(impl) + size + 1); 41 | new_data->reference_count = 1; 42 | new_data->size = size; 43 | std::memcpy(new_data->data, ptr, size); 44 | new_data->data[size] = '\0'; 45 | _data = new_data; 46 | } 47 | explicit shared_cow_string(std::size_t size, boost::container::default_init_t, const allocator_type& alloc) : shared_cow_string(alloc) { 48 | impl* new_data = (impl*)&*_alloc.allocate(sizeof(impl) + size + 1); 49 | new_data->reference_count = 1; 50 | new_data->size = size; 51 | new_data->data[size] = '\0'; 52 | _data = new_data; 53 | } 54 | shared_cow_string(const shared_cow_string& other) : _data(other._data), _alloc(other._alloc) { 55 | if(_data != nullptr) { 56 | ++_data->reference_count; 57 | } 58 | } 59 | shared_cow_string(shared_cow_string&& other) : _data(other._data), _alloc(other._alloc) { 60 | other._data = nullptr; 61 | } 62 | shared_cow_string& operator=(const shared_cow_string& other) { 63 | *this = shared_cow_string{other}; 64 | return *this; 65 | } 66 | shared_cow_string& operator=(shared_cow_string&& other) { 67 | if (this != &other) { 68 | dec_refcount(); 69 | _data = other._data; 70 | other._data = nullptr; 71 | } 72 | return *this; 73 | } 74 | ~shared_cow_string() { 75 | dec_refcount(); 76 | } 77 | void resize(std::size_t new_size, boost::container::default_init_t) { 78 | impl* new_data = (impl*)&*_alloc.allocate(sizeof(impl) + new_size + 1); 79 | new_data->reference_count = 1; 80 | new_data->size = new_size; 81 | new_data->data[new_size] = '\0'; 82 | dec_refcount(); 83 | _data = new_data; 84 | } 85 | template 86 | void resize_and_fill(std::size_t new_size, F&& f) { 87 | resize(new_size, boost::container::default_init); 88 | static_cast(f)(_data->data, new_size); 89 | } 90 | void assign(const char* ptr, std::size_t size) { 91 | impl* new_data = (impl*)&*_alloc.allocate(sizeof(impl) + size + 1); 92 | new_data->reference_count = 1; 93 | new_data->size = size; 94 | if(size) 95 | std::memcpy(new_data->data, ptr, size); 96 | new_data->data[size] = '\0'; 97 | dec_refcount(); 98 | _data = new_data; 99 | } 100 | void assign(const unsigned char* ptr, std::size_t size) { 101 | assign((char*)ptr, size); 102 | } 103 | const char * data() const { 104 | if (_data) return _data->data; 105 | else return nullptr; 106 | } 107 | std::size_t size() const { 108 | if (_data) return _data->size; 109 | else return 0; 110 | } 111 | const_iterator begin() const { return data(); } 112 | const_iterator end() const { 113 | if (_data) return _data->data + _data->size; 114 | else return nullptr; 115 | } 116 | int compare(std::size_t start, std::size_t count, const char* other, std::size_t other_size) const { 117 | std::size_t sz = size(); 118 | if(start > sz) BOOST_THROW_EXCEPTION(std::out_of_range{"shared_cow_string::compare"}); 119 | count = std::min(count, sz - start); 120 | std::size_t cmp_len = std::min(count, other_size); 121 | const char* start_ptr = data() + start; 122 | int result = std::char_traits::compare(start_ptr, other, cmp_len); 123 | if (result != 0) return result; 124 | else if (count < other_size) return -1; 125 | else if(count > other_size) return 1; 126 | else return 0; 127 | } 128 | bool operator==(const shared_cow_string& rhs) const { 129 | return size() == rhs.size() && std::memcmp(data(), rhs.data(), size()) == 0; 130 | } 131 | bool operator!=(const shared_cow_string& rhs) const { return !(*this == rhs); } 132 | const allocator_type& get_allocator() const { return _alloc; } 133 | private: 134 | void dec_refcount() { 135 | if(_data && --_data->reference_count == 0) { 136 | _alloc.deallocate((char*)&*_data, sizeof(shared_cow_string) + _data->size + 1); 137 | } 138 | } 139 | bip::offset_ptr _data; 140 | allocator_type _alloc; 141 | }; 142 | 143 | } // namepsace chainbase 144 | -------------------------------------------------------------------------------- /include/chainbase/undo_index.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace chainbase { 22 | 23 | template 24 | struct scope_exit { 25 | public: 26 | scope_exit(F&& f) : _f(f) {} 27 | scope_exit(const scope_exit&) = delete; 28 | scope_exit& operator=(const scope_exit&) = delete; 29 | ~scope_exit() { if(!_canceled) _f(); } 30 | void cancel() { _canceled = true; } 31 | private: 32 | F _f; 33 | bool _canceled = false; 34 | }; 35 | 36 | // Adapts multi_index's idea of keys to intrusive 37 | template 38 | struct get_key { 39 | using type = std::decay_t()))>; 40 | decltype(auto) operator()(const T& arg) const { return KeyExtractor{}(arg); } 41 | }; 42 | 43 | template 44 | struct value_holder { 45 | template 46 | value_holder(A&&... a) : _item(static_cast(a)...) {} 47 | T _item; 48 | }; 49 | 50 | template 51 | struct offset_node_base { 52 | offset_node_base() = default; 53 | offset_node_base(const offset_node_base&) {} 54 | constexpr offset_node_base& operator=(const offset_node_base&) { return *this; } 55 | std::ptrdiff_t _parent; 56 | std::ptrdiff_t _left; 57 | std::ptrdiff_t _right; 58 | int _color; 59 | }; 60 | 61 | template 62 | struct offset_node_traits { 63 | using node = offset_node_base; 64 | using node_ptr = node*; 65 | using const_node_ptr = const node*; 66 | using color = int; 67 | static node_ptr get_parent(const_node_ptr n) { 68 | if(n->_parent == 1) return nullptr; 69 | return (node_ptr)((char*)n + n->_parent); 70 | } 71 | static void set_parent(node_ptr n, node_ptr parent) { 72 | if(parent == nullptr) n->_parent = 1; 73 | else n->_parent = (char*)parent - (char*)n; 74 | } 75 | static node_ptr get_left(const_node_ptr n) { 76 | if(n->_left == 1) return nullptr; 77 | return (node_ptr)((char*)n + n->_left); 78 | } 79 | static void set_left(node_ptr n, node_ptr left) { 80 | if(left == nullptr) n->_left = 1; 81 | else n->_left = (char*)left - (char*)n; 82 | } 83 | static node_ptr get_right(const_node_ptr n) { 84 | if(n->_right == 1) return nullptr; 85 | return (node_ptr)((char*)n + n->_right); 86 | } 87 | static void set_right(node_ptr n, node_ptr right) { 88 | if(right == nullptr) n->_right = 1; 89 | else n->_right = (char*)right - (char*)n; 90 | } 91 | // red-black tree 92 | static color get_color(node_ptr n) { 93 | return n->_color; 94 | } 95 | static void set_color(node_ptr n, color c) { 96 | n->_color = c; 97 | } 98 | static color black() { return 0; } 99 | static color red() { return 1; } 100 | // avl tree 101 | using balance = int; 102 | static balance get_balance(node_ptr n) { 103 | return n->_color; 104 | } 105 | static void set_balance(node_ptr n, balance c) { 106 | n->_color = c; 107 | } 108 | static balance negative() { return -1; } 109 | static balance zero() { return 0; } 110 | static balance positive() { return 1; } 111 | 112 | // list 113 | static node_ptr get_next(const_node_ptr n) { return get_right(n); } 114 | static void set_next(node_ptr n, node_ptr next) { set_right(n, next); } 115 | static node_ptr get_previous(const_node_ptr n) { return get_left(n); } 116 | static void set_previous(node_ptr n, node_ptr previous) { set_left(n, previous); } 117 | }; 118 | 119 | template 120 | struct offset_node_value_traits { 121 | using node_traits = offset_node_traits; 122 | using node_ptr = typename node_traits::node_ptr; 123 | using const_node_ptr = typename node_traits::const_node_ptr; 124 | using value_type = typename Node::value_type; 125 | using pointer = value_type*; 126 | using const_pointer = const value_type*; 127 | 128 | static node_ptr to_node_ptr(value_type &value) { 129 | return node_ptr{static_cast(boost::intrusive::get_parent_from_member(&value, &value_holder::_item))}; 130 | } 131 | static const_node_ptr to_node_ptr(const value_type &value) { 132 | return const_node_ptr{static_cast(boost::intrusive::get_parent_from_member(&value, &value_holder::_item))}; 133 | } 134 | static pointer to_value_ptr(node_ptr n) { return pointer{&static_cast(&*n)->_item}; } 135 | static const_pointer to_value_ptr(const_node_ptr n) { return const_pointer{&static_cast(&*n)->_item}; } 136 | 137 | static constexpr boost::intrusive::link_mode_type link_mode = boost::intrusive::normal_link; 138 | }; 139 | template 140 | using rebind_alloc_t = typename std::allocator_traits::template rebind_alloc; 141 | 142 | template 143 | struct index_tag_impl { using type = void; }; 144 | template class Index, typename Tag, typename... T> 145 | struct index_tag_impl, T...>> { using type = Tag; }; 146 | template 147 | using index_tag = typename index_tag_impl::type; 148 | 149 | template 150 | using find_tag = boost::mp11::mp_find...>, Tag>; 151 | 152 | template 153 | using hook = offset_node_base; 154 | 155 | template 156 | using set_base = boost::intrusive::avltree< 157 | typename Node::value_type, 158 | boost::intrusive::value_traits>, 159 | boost::intrusive::key_of_value>, 160 | boost::intrusive::compare>; 161 | 162 | template 163 | constexpr bool is_valid_index = false; 164 | template 165 | constexpr bool is_valid_index> = true; 166 | 167 | template 168 | using list_base = boost::intrusive::slist< 169 | typename Node::value_type, 170 | boost::intrusive::value_traits>>; 171 | 172 | template 173 | void remove_if_after_and_dispose(L& l, It it, It end, Pred&& p, Disposer&& d) { 174 | for(;;) { 175 | It next = it; 176 | ++next; 177 | if(next == end) break; 178 | if(p(*next)) { l.erase_after_and_dispose(it, d); } 179 | else { it = next; } 180 | } 181 | } 182 | 183 | template 184 | class undo_index; 185 | 186 | template 187 | struct set_impl : private set_base { 188 | using base_type = set_base; 189 | // Allow compatible keys to match multi_index 190 | template 191 | auto find(K&& k) const { 192 | return base_type::find(static_cast(k), this->key_comp()); 193 | } 194 | template 195 | auto lower_bound(K&& k) const { 196 | return base_type::lower_bound(static_cast(k), this->key_comp()); 197 | } 198 | template 199 | auto upper_bound(K&& k) const { 200 | return base_type::upper_bound(static_cast(k), this->key_comp()); 201 | } 202 | template 203 | auto equal_range(K&& k) const { 204 | return base_type::equal_range(static_cast(k), this->key_comp()); 205 | } 206 | using base_type::begin; 207 | using base_type::end; 208 | using base_type::rbegin; 209 | using base_type::rend; 210 | using base_type::size; 211 | using base_type::iterator_to; 212 | using base_type::empty; 213 | template 214 | friend class undo_index; 215 | }; 216 | 217 | template 218 | class chainbase_node_allocator; 219 | 220 | // Allows nested object to use a different allocator from the container. 221 | template class A, typename T> 222 | auto& propagate_allocator(A& a) { return a; } 223 | template 224 | auto& propagate_allocator(boost::interprocess::allocator& a) { return a; } 225 | template 226 | auto propagate_allocator(boost::interprocess::node_allocator& a) { return boost::interprocess::allocator{a.get_segment_manager()}; } 227 | template 228 | auto propagate_allocator(boost::interprocess::private_node_allocator& a) { return boost::interprocess::allocator{a.get_segment_manager()}; } 229 | template 230 | auto propagate_allocator(chainbase::chainbase_node_allocator& a) { return boost::interprocess::allocator{a.get_segment_manager()}; } 231 | 232 | // Similar to boost::multi_index_container with an undo stack. 233 | // Indices should be instances of ordered_unique. 234 | template 235 | class undo_index { 236 | public: 237 | using id_type = std::decay_t().id)>; 238 | using value_type = T; 239 | using allocator_type = Allocator; 240 | 241 | static_assert((... && is_valid_index), "Only ordered_unique indices are supported"); 242 | 243 | undo_index() = default; 244 | explicit undo_index(const Allocator& a) : _undo_stack{a}, _allocator{a}, _old_values_allocator{a} {} 245 | ~undo_index() { 246 | dispose_undo(); 247 | clear_impl<1>(); 248 | std::get<0>(_indices).clear_and_dispose([&](pointer p){ dispose_node(*p); }); 249 | } 250 | 251 | void validate()const { 252 | if( sizeof(node) != _size_of_value_type || sizeof(*this) != _size_of_this ) 253 | BOOST_THROW_EXCEPTION( std::runtime_error("content of memory does not match data expected by executable") ); 254 | } 255 | 256 | struct node : hook..., value_holder { 257 | using value_type = T; 258 | using allocator_type = Allocator; 259 | template 260 | explicit node(A&&... a) : value_holder{static_cast(a)...} {} 261 | const T& item() const { return *this; } 262 | uint64_t _mtime = 0; // _monotonic_revision when the node was last modified or created. 263 | }; 264 | static constexpr int erased_flag = 2; // 0,1,and -1 are used by the tree 265 | 266 | using indices_type = std::tuple...>; 267 | 268 | using index0_set_type = std::tuple_element_t<0, indices_type>; 269 | using alloc_traits = typename std::allocator_traits::template rebind_traits; 270 | 271 | static_assert(std::is_same_v, "first index must be id"); 272 | 273 | using index0_type = boost::mp11::mp_first>; 274 | struct old_node : hook, value_holder { 275 | using value_type = T; 276 | using allocator_type = Allocator; 277 | template 278 | explicit old_node(const T& t) : value_holder{t} {} 279 | uint64_t _mtime = 0; // Backup of the node's _mtime, to be restored on undo 280 | typename alloc_traits::pointer _current; // pointer to the actual node 281 | }; 282 | 283 | using id_pointer = id_type*; 284 | using pointer = value_type*; 285 | using const_iterator = typename index0_set_type::const_iterator; 286 | 287 | // The undo stack is implemented as a deque of undo_states 288 | // that index into a pair of singly linked lists. 289 | // 290 | // The primary key (id) is managed by the undo_index. The id's are assigned sequentially to 291 | // objects in the order of insertion. User code MUST NOT modify the primary key. 292 | // A given id can only be reused if its insertion is undone. 293 | // 294 | // Each undo session remembers the state of the table at the point when it was created. 295 | // 296 | // Within the undo state at the top of the undo stack: 297 | // A primary key is new if it is at least old_next_id. 298 | // 299 | // A primary key is removed if it exists in the removed_values list before removed_values_end. 300 | // A node has a flag which indicates whether it has been removed. 301 | // 302 | // A primary key is modified if it exists in the old_values list before old_values_end 303 | // 304 | // A primary key exists at most once in either the main table or removed values. 305 | // Every primary key in old_values also exists in either the main table OR removed_values. 306 | // If a primary key exists in both removed_values AND old_values, undo will restore the value from old_values. 307 | // A primary key may appear in old_values any number of times. If it appears more than once 308 | // within a single undo session, undo will restore the oldest value. 309 | // 310 | // The following describes the minimal set of operations required to maintain the undo stack: 311 | // start session: remember next_id and the current heads of old_values and removed_values. 312 | // squash: drop the last undo state 313 | // create: nop 314 | // modify: push a copy of the object onto old_values 315 | // remove: move node to removed index and set removed flag 316 | // 317 | // Operations on a given key MUST always follow the sequence: CREATE MODIFY* REMOVE? 318 | // When undoing multiple operations on the same key, the final result is determined 319 | // by the oldest operation. Therefore, the following optimizations can be applied: 320 | // - A primary key which is new may be discarded from old_values and removed_values 321 | // - If a primary key has multiple modifications, all but the oldest can be discarded. 322 | // - If a primary key is both modified and removed, the modified value can replace 323 | // the removed value, and can then be discarded. 324 | // These optimizations may be applied at any time, but are not required by the class 325 | // invariants. 326 | // 327 | // Notes regarding memory: 328 | // Nodes in the main table share the same layout as nodes in removed_values and may 329 | // be freely moved between the two. This permits undo to restore removed nodes 330 | // without allocating memory. 331 | // 332 | struct undo_state { 333 | typename std::allocator_traits::pointer old_values_end; 334 | typename std::allocator_traits::pointer removed_values_end; 335 | id_type old_next_id = 0; 336 | uint64_t ctime = 0; // _monotonic_revision at the point the undo_state was created 337 | }; 338 | 339 | // Exception safety: strong 340 | template 341 | const value_type& emplace( Constructor&& c ) { 342 | auto p = alloc_traits::allocate(_allocator, 1); 343 | auto guard0 = scope_exit{[&]{ alloc_traits::deallocate(_allocator, p, 1); }}; 344 | auto new_id = _next_id; 345 | auto constructor = [&]( value_type& v ) { 346 | v.id = new_id; 347 | c( v ); 348 | }; 349 | alloc_traits::construct(_allocator, &*p, constructor, propagate_allocator(_allocator)); 350 | auto guard1 = scope_exit{[&]{ alloc_traits::destroy(_allocator, &*p); }}; 351 | if(!insert_impl<1>(p->_item)) 352 | BOOST_THROW_EXCEPTION( std::logic_error{ "could not insert object, most likely a uniqueness constraint was violated" } ); 353 | std::get<0>(_indices).push_back(p->_item); // cannot fail and we know that it will definitely insert at the end. 354 | on_create(p->_item); 355 | ++_next_id; 356 | guard1.cancel(); 357 | guard0.cancel(); 358 | return p->_item; 359 | } 360 | 361 | // Exception safety: basic. 362 | // If the modifier leaves the object in a state that conflicts 363 | // with another object, it will either be reverted or erased. 364 | template 365 | void modify( const value_type& obj, Modifier&& m) { 366 | value_type* backup = on_modify(obj); 367 | value_type& node_ref = const_cast(obj); 368 | bool success = false; 369 | { 370 | auto guard0 = scope_exit{[&]{ 371 | if(!post_modify(node_ref)) { // The object id cannot be modified 372 | if(backup) { 373 | node_ref = std::move(*backup); 374 | bool success = post_modify(node_ref); 375 | (void)success; 376 | assert(success); 377 | assert(backup == &_old_values.front()); 378 | _old_values.pop_front_and_dispose([this](pointer p){ dispose_old(*p); }); 379 | } else { 380 | remove(obj); 381 | } 382 | } else { 383 | success = true; 384 | } 385 | }}; 386 | auto old_id = obj.id; 387 | m(node_ref); 388 | (void)old_id; 389 | assert(obj.id == old_id); 390 | } 391 | if(!success) 392 | BOOST_THROW_EXCEPTION( std::logic_error{ "could not modify object, most likely a uniqueness constraint was violated" } ); 393 | } 394 | 395 | // Allows testing whether a value has been removed from the undo_index. 396 | // 397 | // The lifetime of an object removed through a removed_nodes_tracker 398 | // does not end before the removed_nodes_tracker is destroyed or invalidated. 399 | // 400 | // A removed_nodes_tracker is invalidated by the following members of undo_index: 401 | // start_undo_session, commit, squash, and undo. 402 | class removed_nodes_tracker { 403 | public: 404 | explicit removed_nodes_tracker(undo_index& idx) : _self(&idx) {} 405 | ~removed_nodes_tracker() { 406 | _removed_values.clear_and_dispose([this](value_type* obj) { _self->dispose_node(*obj); }); 407 | } 408 | removed_nodes_tracker(const removed_nodes_tracker&) = delete; 409 | removed_nodes_tracker& operator=(const removed_nodes_tracker&) = delete; 410 | bool is_removed(const value_type& obj) const { 411 | return undo_index::get_removed_field(obj) == erased_flag; 412 | } 413 | // Must be used in place of undo_index::remove 414 | void remove(const value_type& obj) { 415 | _self->remove(obj, *this); 416 | } 417 | private: 418 | friend class undo_index; 419 | void save(value_type& obj) { 420 | undo_index::get_removed_field(obj) = erased_flag; 421 | _removed_values.push_front(obj); 422 | } 423 | undo_index* _self; 424 | list_base _removed_values; 425 | }; 426 | auto track_removed() { 427 | return removed_nodes_tracker(*this); 428 | } 429 | 430 | void remove( const value_type& obj ) noexcept { 431 | auto& node_ref = const_cast(obj); 432 | erase_impl(node_ref); 433 | if(on_remove(node_ref)) { 434 | dispose_node(node_ref); 435 | } 436 | } 437 | 438 | private: 439 | 440 | void remove( const value_type& obj, removed_nodes_tracker& tracker ) noexcept { 441 | auto& node_ref = const_cast(obj); 442 | erase_impl(node_ref); 443 | if(on_remove(node_ref)) { 444 | tracker.save(node_ref); 445 | } 446 | } 447 | 448 | public: 449 | 450 | template 451 | const value_type* find( CompatibleKey&& key) const { 452 | const auto& index = std::get<0>(_indices); 453 | auto iter = index.find(static_cast(key)); 454 | if (iter != index.end()) { 455 | return &*iter; 456 | } else { 457 | return nullptr; 458 | } 459 | } 460 | 461 | template 462 | const value_type& get( CompatibleKey&& key )const { 463 | auto ptr = find( static_cast(key) ); 464 | if( !ptr ) { 465 | std::stringstream ss; 466 | ss << "key not found (" << boost::core::demangle( typeid( key ).name() ) << "): " << key; 467 | BOOST_THROW_EXCEPTION( std::out_of_range( ss.str().c_str() ) ); 468 | } 469 | return *ptr; 470 | } 471 | 472 | void remove_object( int64_t id ) { 473 | const value_type* val = find( typename value_type::id_type(id) ); 474 | if( !val ) BOOST_THROW_EXCEPTION( std::out_of_range( boost::lexical_cast(id) ) ); 475 | remove( *val ); 476 | } 477 | 478 | class session { 479 | public: 480 | session(undo_index& idx, bool enabled) 481 | : _index(idx), 482 | _apply(enabled) { 483 | if(enabled) idx.add_session(); 484 | } 485 | session(session&& other) 486 | : _index(other._index), 487 | _apply(other._apply) 488 | { 489 | other._apply = false; 490 | } 491 | session& operator=(session&& other) { 492 | if(this != &other) { 493 | undo(); 494 | _apply = other._apply; 495 | other._apply = false; 496 | } 497 | return *this; 498 | } 499 | ~session() { if(_apply) _index.undo(); } 500 | void push() { _apply = false; } 501 | void squash() { 502 | if ( _apply ) _index.squash(); 503 | _apply = false; 504 | } 505 | void undo() { 506 | if ( _apply ) _index.undo(); 507 | _apply = false; 508 | } 509 | private: 510 | undo_index& _index; 511 | bool _apply = true; 512 | }; 513 | 514 | int64_t revision() const { return _revision; } 515 | 516 | session start_undo_session( bool enabled ) { 517 | return session{*this, enabled}; 518 | } 519 | 520 | void set_revision( uint64_t revision ) { 521 | if( _undo_stack.size() != 0 ) 522 | BOOST_THROW_EXCEPTION( std::logic_error("cannot set revision while there is an existing undo stack") ); 523 | 524 | if( revision > std::numeric_limits::max() ) 525 | BOOST_THROW_EXCEPTION( std::logic_error("revision to set is too high") ); 526 | 527 | if( revision < _revision ) 528 | BOOST_THROW_EXCEPTION( std::logic_error("revision cannot decrease") ); 529 | 530 | _revision = static_cast(revision); 531 | } 532 | 533 | std::pair undo_stack_revision_range() const { 534 | return { _revision - _undo_stack.size(), _revision }; 535 | } 536 | 537 | /** 538 | * Discards all undo history prior to revision 539 | */ 540 | void commit( int64_t revision ) noexcept { 541 | revision = std::min(revision, _revision); 542 | if (revision == _revision) { 543 | dispose_undo(); 544 | _undo_stack.clear(); 545 | } else if( (_revision - revision) < _undo_stack.size() ) { 546 | auto iter = _undo_stack.begin() + (_undo_stack.size() - (_revision - revision)); 547 | dispose(get_old_values_end(*iter), get_removed_values_end(*iter)); 548 | _undo_stack.erase(_undo_stack.begin(), iter); 549 | } 550 | } 551 | 552 | const undo_index& indices() const { return *this; } 553 | template 554 | const auto& get() const { return std::get::value>(_indices); } 555 | 556 | template 557 | const auto& get() const { return std::get(_indices); } 558 | 559 | std::size_t size() const { 560 | return std::get<0>(_indices).size(); 561 | } 562 | 563 | bool empty() const { 564 | return std::get<0>(_indices).empty(); 565 | } 566 | 567 | template 568 | auto project(Iter iter) const { 569 | return project::value>(iter); 570 | } 571 | 572 | template 573 | auto project(Iter iter) const { 574 | if(iter == get::const_iterator...>, Iter>::value>().end()) 575 | return get().end(); 576 | return get().iterator_to(*iter); 577 | } 578 | 579 | bool has_undo_session() const { return !_undo_stack.empty(); } 580 | 581 | struct delta { 582 | boost::iterator_range new_values; 583 | boost::iterator_range::const_iterator> old_values; 584 | boost::iterator_range::const_iterator> removed_values; 585 | }; 586 | 587 | delta last_undo_session() const { 588 | if(_undo_stack.empty()) 589 | return { { get<0>().end(), get<0>().end() }, 590 | { _old_values.end(), _old_values.end() }, 591 | { _removed_values.end(), _removed_values.end() } }; 592 | // Warning: This is safe ONLY as long as nothing exposes the undo stack to client code. 593 | // Compressing the undo stack does not change the logical state of the undo_index. 594 | const_cast(this)->compress_last_undo_session(); 595 | return { { get<0>().lower_bound(_undo_stack.back().old_next_id), get<0>().end() }, 596 | { _old_values.begin(), get_old_values_end(_undo_stack.back()) }, 597 | { _removed_values.begin(), get_removed_values_end(_undo_stack.back()) } }; 598 | } 599 | 600 | auto begin() const { return get<0>().begin(); } 601 | auto end() const { return get<0>().end(); } 602 | 603 | void undo_all() { 604 | while(!_undo_stack.empty()) { 605 | undo(); 606 | } 607 | } 608 | 609 | // Resets the contents to the state at the top of the undo stack. 610 | void undo() noexcept { 611 | if (_undo_stack.empty()) return; 612 | undo_state& undo_info = _undo_stack.back(); 613 | // erase all new_ids 614 | auto& by_id = std::get<0>(_indices); 615 | auto new_ids_iter = by_id.lower_bound(undo_info.old_next_id); 616 | by_id.erase_and_dispose(new_ids_iter, by_id.end(), [this](pointer p){ 617 | erase_impl<1>(*p); 618 | dispose_node(*p); 619 | }); 620 | // replace old_values 621 | _old_values.erase_after_and_dispose(_old_values.before_begin(), get_old_values_end(undo_info), [this, &undo_info](pointer p) { 622 | auto restored_mtime = to_old_node(*p)._mtime; 623 | // Skip restoring values that overwrite an earlier modify in the same session. 624 | // Duplicate modifies can only happen because of squash. 625 | if(restored_mtime < undo_info.ctime) { 626 | auto iter = &to_old_node(*p)._current->_item; 627 | *iter = std::move(*p); 628 | auto& node_mtime = to_node(*iter)._mtime; 629 | node_mtime = restored_mtime; 630 | if (get_removed_field(*iter) != erased_flag) { 631 | // Non-unique items are transient and are guaranteed to be fixed 632 | // by the time we finish processing old_values. 633 | post_modify(*iter); 634 | } else { 635 | // The item was removed. It will be inserted when we process removed_values 636 | } 637 | } 638 | dispose_old(*p); 639 | }); 640 | // insert all removed_values 641 | _removed_values.erase_after_and_dispose(_removed_values.before_begin(), get_removed_values_end(undo_info), [this, &undo_info](pointer p) { 642 | if (p->id < undo_info.old_next_id) { 643 | get_removed_field(*p) = 0; // Will be overwritten by tree algorithms, because we're reusing the color. 644 | insert_impl(*p); 645 | } else { 646 | dispose_node(*p); 647 | } 648 | }); 649 | _next_id = undo_info.old_next_id; 650 | _undo_stack.pop_back(); 651 | --_revision; 652 | } 653 | 654 | // Combines the top two states on the undo stack 655 | void squash() noexcept { 656 | squash_and_compress(); 657 | } 658 | 659 | void squash_fast() noexcept { 660 | if (_undo_stack.empty()) { 661 | return; 662 | } else if (_undo_stack.size() == 1) { 663 | dispose_undo(); 664 | } 665 | _undo_stack.pop_back(); 666 | --_revision; 667 | } 668 | 669 | void squash_and_compress() noexcept { 670 | if(_undo_stack.size() >= 2) { 671 | compress_impl(_undo_stack[_undo_stack.size() - 2]); 672 | } 673 | squash_fast(); 674 | } 675 | 676 | void compress_last_undo_session() noexcept { 677 | compress_impl(_undo_stack.back()); 678 | } 679 | 680 | private: 681 | 682 | // Removes elements of the last undo session that would be redundant 683 | // if all the sessions after @c session were squashed. 684 | // 685 | // WARNING: This function leaves any undo sessions after @c session in 686 | // an indeterminate state. The caller MUST use squash to restore the 687 | // undo stack to a sane state. 688 | void compress_impl(undo_state& session) noexcept { 689 | auto session_start = session.ctime; 690 | auto old_next_id = session.old_next_id; 691 | remove_if_after_and_dispose(_old_values, _old_values.before_begin(), get_old_values_end(_undo_stack.back()), 692 | [session_start](value_type& v){ 693 | if(to_old_node(v)._mtime >= session_start) return true; 694 | auto& item = to_old_node(v)._current->_item; 695 | if (get_removed_field(item) == erased_flag) { 696 | item = std::move(v); 697 | to_node(item)._mtime = to_old_node(v)._mtime; 698 | return true; 699 | } 700 | return false; 701 | }, 702 | [&](pointer p) { dispose_old(*p); }); 703 | remove_if_after_and_dispose(_removed_values, _removed_values.before_begin(), get_removed_values_end(_undo_stack.back()), 704 | [old_next_id](value_type& v){ 705 | return v.id >= old_next_id; 706 | }, 707 | [this](pointer p) { dispose_node(*p); }); 708 | } 709 | 710 | // starts a new undo session. 711 | // Exception safety: strong 712 | int64_t add_session() { 713 | _undo_stack.emplace_back(); 714 | _undo_stack.back().old_values_end = _old_values.empty()?nullptr:&*_old_values.begin(); 715 | _undo_stack.back().removed_values_end = _removed_values.empty()?nullptr:&*_removed_values.begin(); 716 | _undo_stack.back().old_next_id = _next_id; 717 | _undo_stack.back().ctime = ++_monotonic_revision; 718 | return ++_revision; 719 | } 720 | 721 | template 722 | bool insert_impl(value_type& p) { 723 | if constexpr (N < sizeof...(Indices)) { 724 | auto [iter, inserted] = std::get(_indices).insert_unique(p); 725 | if(!inserted) return false; 726 | auto guard = scope_exit{[this,iter=iter]{ std::get(_indices).erase(iter); }}; 727 | if(insert_impl(p)) { 728 | guard.cancel(); 729 | return true; 730 | } 731 | return false; 732 | } 733 | return true; 734 | } 735 | 736 | // Moves a modified node into the correct location 737 | template 738 | bool post_modify(value_type& p) { 739 | if constexpr (N < sizeof...(Indices)) { 740 | auto& idx = std::get(_indices); 741 | auto iter = idx.iterator_to(p); 742 | bool fixup = false; 743 | if (iter != idx.begin()) { 744 | auto copy = iter; 745 | --copy; 746 | if (!idx.value_comp()(*copy, p)) fixup = true; 747 | } 748 | ++iter; 749 | if (iter != idx.end()) { 750 | if(!idx.value_comp()(p, *iter)) fixup = true; 751 | } 752 | if(fixup) { 753 | auto iter2 = idx.iterator_to(p); 754 | idx.erase(iter2); 755 | if constexpr (unique) { 756 | auto [new_pos, inserted] = idx.insert_unique(p); 757 | if (!inserted) { 758 | idx.insert_before(new_pos, p); 759 | return false; 760 | } 761 | } else { 762 | idx.insert_equal(p); 763 | } 764 | } 765 | return post_modify(p); 766 | } 767 | return true; 768 | } 769 | 770 | template 771 | void erase_impl(value_type& p) { 772 | if constexpr (N < sizeof...(Indices)) { 773 | auto& setN = std::get(_indices); 774 | setN.erase(setN.iterator_to(p)); 775 | erase_impl(p); 776 | } 777 | } 778 | 779 | void on_create(const value_type& value) noexcept { 780 | if(!_undo_stack.empty()) { 781 | // Not in old_values, removed_values, or new_ids 782 | to_node(value)._mtime = _monotonic_revision; 783 | } 784 | } 785 | 786 | value_type* on_modify( const value_type& obj) { 787 | if (!_undo_stack.empty()) { 788 | auto& undo_info = _undo_stack.back(); 789 | if ( to_node(obj)._mtime >= undo_info.ctime ) { 790 | // Nothing to do 791 | } else { 792 | // Not in removed_values 793 | auto p = old_alloc_traits::allocate(_old_values_allocator, 1); 794 | auto guard0 = scope_exit{[&]{ _old_values_allocator.deallocate(p, 1); }}; 795 | old_alloc_traits::construct(_old_values_allocator, &*p, obj); 796 | p->_mtime = to_node(obj)._mtime; 797 | p->_current = &to_node(obj); 798 | guard0.cancel(); 799 | _old_values.push_front(p->_item); 800 | to_node(obj)._mtime = _monotonic_revision; 801 | return &p->_item; 802 | } 803 | } 804 | return nullptr; 805 | } 806 | template 807 | void clear_impl() noexcept { 808 | if constexpr(N < sizeof...(Indices)) { 809 | std::get(_indices).clear(); 810 | clear_impl(); 811 | } 812 | } 813 | void dispose_node(node& node_ref) noexcept { 814 | node* p{&node_ref}; 815 | alloc_traits::destroy(_allocator, p); 816 | alloc_traits::deallocate(_allocator, p, 1); 817 | } 818 | void dispose_node(value_type& node_ref) noexcept { 819 | dispose_node(static_cast(*boost::intrusive::get_parent_from_member(&node_ref, &value_holder::_item))); 820 | } 821 | void dispose_old(old_node& node_ref) noexcept { 822 | old_node* p{&node_ref}; 823 | old_alloc_traits::destroy(_old_values_allocator, p); 824 | old_alloc_traits::deallocate(_old_values_allocator, p, 1); 825 | } 826 | void dispose_old(value_type& node_ref) noexcept { 827 | dispose_old(static_cast(*boost::intrusive::get_parent_from_member(&node_ref, &value_holder::_item))); 828 | } 829 | void dispose(typename list_base::iterator old_start, typename list_base::iterator removed_start) noexcept { 830 | // This will leave one element around. That's okay, because we'll clean it up the next time. 831 | if(old_start != _old_values.end()) 832 | _old_values.erase_after_and_dispose(old_start, _old_values.end(), [this](pointer p){ dispose_old(*p); }); 833 | if(removed_start != _removed_values.end()) 834 | _removed_values.erase_after_and_dispose(removed_start, _removed_values.end(), [this](pointer p){ dispose_node(*p); }); 835 | } 836 | void dispose_undo() noexcept { 837 | _old_values.clear_and_dispose([this](pointer p){ dispose_old(*p); }); 838 | _removed_values.clear_and_dispose([this](pointer p){ dispose_node(*p); }); 839 | } 840 | static node& to_node(value_type& obj) { 841 | return static_cast(*boost::intrusive::get_parent_from_member(&obj, &value_holder::_item)); 842 | } 843 | static node& to_node(const value_type& obj) { 844 | return to_node(const_cast(obj)); 845 | } 846 | static old_node& to_old_node(value_type& obj) { 847 | return static_cast(*boost::intrusive::get_parent_from_member(&obj, &value_holder::_item)); 848 | } 849 | 850 | auto get_old_values_end(const undo_state& info) { 851 | if(info.old_values_end == nullptr) { 852 | return _old_values.end(); 853 | } else { 854 | return _old_values.iterator_to(*info.old_values_end); 855 | } 856 | } 857 | 858 | auto get_old_values_end(const undo_state& info) const { 859 | return static_cast(const_cast(this)->get_old_values_end(info)); 860 | } 861 | 862 | auto get_removed_values_end(const undo_state& info) { 863 | if(info.removed_values_end == nullptr) { 864 | return _removed_values.end(); 865 | } else { 866 | return _removed_values.iterator_to(*info.removed_values_end); 867 | } 868 | } 869 | 870 | auto get_removed_values_end(const undo_state& info) const { 871 | return static_cast(const_cast(this)->get_removed_values_end(info)); 872 | } 873 | 874 | // returns true if the node should be destroyed 875 | bool on_remove( value_type& obj) { 876 | if (!_undo_stack.empty()) { 877 | auto& undo_info = _undo_stack.back(); 878 | if ( obj.id >= undo_info.old_next_id ) { 879 | return true; 880 | } 881 | get_removed_field(obj) = erased_flag; 882 | 883 | _removed_values.push_front(obj); 884 | return false; 885 | } 886 | return true; 887 | } 888 | // Returns the field indicating whether the node has been removed 889 | static int& get_removed_field(const value_type& obj) { 890 | return static_cast&>(to_node(obj))._color; 891 | } 892 | using old_alloc_traits = typename std::allocator_traits::template rebind_traits; 893 | indices_type _indices; 894 | boost::container::deque> _undo_stack; 895 | list_base _old_values; 896 | list_base _removed_values; 897 | rebind_alloc_t _allocator; 898 | rebind_alloc_t _old_values_allocator; 899 | id_type _next_id = 0; 900 | int64_t _revision = 0; 901 | uint64_t _monotonic_revision = 0; 902 | uint32_t _size_of_value_type = sizeof(node); 903 | uint32_t _size_of_this = sizeof(undo_index); 904 | }; 905 | 906 | template 907 | struct multi_index_to_undo_index_impl; 908 | 909 | template 910 | struct mi_to_ui_ii; 911 | template 912 | struct mi_to_ui_ii, A> { 913 | using type = undo_index; 914 | }; 915 | 916 | struct to_mp11 { 917 | template 918 | using apply = boost::mpl::identity>; 919 | }; 920 | 921 | template 922 | struct multi_index_to_undo_index_impl> { 923 | using as_mp11 = typename boost::mpl::fold, to_mp11>::type; 924 | using type = typename mi_to_ui_ii::type; 925 | }; 926 | 927 | // Converts a multi_index_container to a corresponding undo_index. 928 | template 929 | using multi_index_to_undo_index = typename multi_index_to_undo_index_impl::type; 930 | } 931 | -------------------------------------------------------------------------------- /src/chainbase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #ifndef _WIN32 7 | #include 8 | #endif 9 | 10 | namespace chainbase { 11 | 12 | database::database(const bfs::path& dir, open_flags flags, uint64_t shared_file_size, bool allow_dirty, 13 | pinnable_mapped_file::map_mode db_map_mode, std::vector hugepage_paths ) : 14 | _db_file(dir, flags & database::read_write, shared_file_size, allow_dirty, db_map_mode, hugepage_paths), 15 | _read_only(flags == database::read_only) 16 | { 17 | } 18 | 19 | database::~database() 20 | { 21 | _index_list.clear(); 22 | _index_map.clear(); 23 | } 24 | 25 | void database::set_require_locking( bool enable_require_locking ) 26 | { 27 | #ifdef CHAINBASE_CHECK_LOCKING 28 | _enable_require_locking = enable_require_locking; 29 | #endif 30 | } 31 | 32 | #ifdef CHAINBASE_CHECK_LOCKING 33 | void database::require_lock_fail( const char* method, const char* lock_type, const char* tname )const 34 | { 35 | std::string err_msg = "database::" + std::string( method ) + " require_" + std::string( lock_type ) + "_lock() failed on type " + std::string( tname ); 36 | std::cerr << err_msg << std::endl; 37 | BOOST_THROW_EXCEPTION( std::runtime_error( err_msg ) ); 38 | } 39 | #endif 40 | 41 | void database::undo() 42 | { 43 | for( auto& item : _index_list ) 44 | { 45 | item->undo(); 46 | } 47 | } 48 | 49 | void database::squash() 50 | { 51 | for( auto& item : _index_list ) 52 | { 53 | item->squash(); 54 | } 55 | } 56 | 57 | void database::commit( int64_t revision ) 58 | { 59 | for( auto& item : _index_list ) 60 | { 61 | item->commit( revision ); 62 | } 63 | } 64 | 65 | void database::undo_all() 66 | { 67 | for( auto& item : _index_list ) 68 | { 69 | item->undo_all(); 70 | } 71 | } 72 | 73 | database::session database::start_undo_session( bool enabled ) 74 | { 75 | if( enabled ) { 76 | vector< std::unique_ptr > _sub_sessions; 77 | _sub_sessions.reserve( _index_list.size() ); 78 | for( auto& item : _index_list ) { 79 | _sub_sessions.push_back( item->start_undo_session( enabled ) ); 80 | } 81 | return session( std::move( _sub_sessions ) ); 82 | } else { 83 | return session(); 84 | } 85 | } 86 | 87 | } // namespace chainbase 88 | -------------------------------------------------------------------------------- /src/pinnable_mapped_file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __linux__ 9 | #include 10 | #include 11 | #endif 12 | 13 | namespace chainbase { 14 | 15 | const char* chainbase_error_category::name() const noexcept { 16 | return "chainbase"; 17 | } 18 | 19 | std::string chainbase_error_category::message(int ev) const { 20 | switch(ev) { 21 | case db_error_code::ok: 22 | return "Ok"; 23 | case db_error_code::dirty: 24 | return "Database dirty flag set"; 25 | case db_error_code::incompatible: 26 | return "Database incompatible; All environment parameters must match"; 27 | case db_error_code::incorrect_db_version: 28 | return "Database format not compatible with this version of chainbase"; 29 | case db_error_code::locked_mode_required: 30 | return "Locked mode is required for hugepage usage"; 31 | case db_error_code::not_found: 32 | return "Database file not found"; 33 | case db_error_code::bad_size: 34 | return "Bad size"; 35 | case db_error_code::no_huge_page: 36 | return "Hugepage support is a linux-only feature"; 37 | case db_error_code::no_locked_mode: 38 | return "Locked mode not supported on win32"; 39 | case db_error_code::bad_header: 40 | return "Failed to read DB header"; 41 | case db_error_code::no_access: 42 | return "Could not gain write access to the shared memory file"; 43 | case db_error_code::aborted: 44 | return "Database load aborted"; 45 | case db_error_code::no_mlock: 46 | return "Failed to mlock database"; 47 | default: 48 | return "Unrecognized error code"; 49 | } 50 | } 51 | 52 | const std::error_category& chainbase_error_category() { 53 | static class chainbase_error_category the_category; 54 | return the_category; 55 | } 56 | 57 | pinnable_mapped_file::pinnable_mapped_file(const bfs::path& dir, bool writable, uint64_t shared_file_size, bool allow_dirty, 58 | map_mode mode, std::vector hugepage_paths) : 59 | _data_file_path(bfs::absolute(dir/"shared_memory.bin")), 60 | _database_name(dir.filename().string()), 61 | _writable(writable) 62 | { 63 | if(shared_file_size % _db_size_multiple_requirement) { 64 | std::string what_str("Database must be mulitple of " + std::to_string(_db_size_multiple_requirement) + " bytes"); 65 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::bad_size), what_str)); 66 | } 67 | #ifndef __linux__ 68 | if(hugepage_paths.size()) 69 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::no_huge_page))); 70 | #endif 71 | if(hugepage_paths.size() && mode != locked) 72 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::locked_mode_required))); 73 | #ifdef _WIN32 74 | if(mode == locked) 75 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::no_locked_mode))); 76 | #endif 77 | 78 | if(!_writable && !bfs::exists(_data_file_path)){ 79 | std::string what_str("database file not found at " + _data_file_path.string()); 80 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::not_found), what_str)); 81 | } 82 | 83 | bfs::create_directories(dir); 84 | 85 | if(bfs::exists(_data_file_path)) { 86 | char header[header_size]; 87 | std::ifstream hs(_data_file_path.generic_string(), std::ifstream::binary); 88 | hs.read(header, header_size); 89 | if(hs.fail()) 90 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::bad_header))); 91 | 92 | db_header* dbheader = reinterpret_cast(header); 93 | if(dbheader->id != header_id) { 94 | std::string what_str("\"" + _database_name + "\" database format not compatible with this version of chainbase."); 95 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::incorrect_db_version), what_str)); 96 | } 97 | if(!allow_dirty && dbheader->dirty) { 98 | std::string what_str("\"" + _database_name + "\" database dirty flag set"); 99 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::dirty))); 100 | } 101 | if(dbheader->dbenviron != environment()) { 102 | std::cerr << "CHAINBASE: \"" << _database_name << "\" database was created with a chainbase from a different environment" << std::endl; 103 | std::cerr << "Current compiler environment:" << std::endl; 104 | std::cerr << environment(); 105 | std::cerr << "DB created with compiler environment:" << std::endl; 106 | std::cerr << dbheader->dbenviron; 107 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::incompatible))); 108 | } 109 | } 110 | 111 | segment_manager* file_mapped_segment_manager = nullptr; 112 | if(!bfs::exists(_data_file_path)) { 113 | std::ofstream ofs(_data_file_path.generic_string(), std::ofstream::trunc); 114 | //win32 impl of bfs::resize_file() doesn't like the file being open 115 | ofs.close(); 116 | bfs::resize_file(_data_file_path, shared_file_size); 117 | _file_mapping = bip::file_mapping(_data_file_path.generic_string().c_str(), bip::read_write); 118 | _file_mapped_region = bip::mapped_region(_file_mapping, bip::read_write); 119 | file_mapped_segment_manager = new ((char*)_file_mapped_region.get_address()+header_size) segment_manager(shared_file_size-header_size); 120 | new (_file_mapped_region.get_address()) db_header; 121 | } 122 | else if(_writable) { 123 | auto existing_file_size = bfs::file_size(_data_file_path); 124 | size_t grow = 0; 125 | if(shared_file_size > existing_file_size) { 126 | grow = shared_file_size - existing_file_size; 127 | bfs::resize_file(_data_file_path, shared_file_size); 128 | } 129 | else if(shared_file_size < existing_file_size) { 130 | std::cerr << "CHAINBASE: \"" << _database_name << "\" requested size of " << shared_file_size << " is less than " 131 | "existing size of " << existing_file_size << ". This database will not be shrunk and will " 132 | "remain at " << existing_file_size << std::endl; 133 | } 134 | _file_mapping = bip::file_mapping(_data_file_path.generic_string().c_str(), bip::read_write); 135 | _file_mapped_region = bip::mapped_region(_file_mapping, bip::read_write); 136 | file_mapped_segment_manager = reinterpret_cast((char*)_file_mapped_region.get_address()+header_size); 137 | if(grow) 138 | file_mapped_segment_manager->grow(grow); 139 | } 140 | else { 141 | _file_mapping = bip::file_mapping(_data_file_path.generic_string().c_str(), bip::read_only); 142 | _file_mapped_region = bip::mapped_region(_file_mapping, bip::read_only); 143 | file_mapped_segment_manager = reinterpret_cast((char*)_file_mapped_region.get_address()+header_size); 144 | } 145 | 146 | if(_writable) { 147 | //remove meta file created in earlier versions 148 | boost::system::error_code ec; 149 | bfs::remove(bfs::absolute(dir/"shared_memory.meta"), ec); 150 | 151 | _mapped_file_lock = bip::file_lock(_data_file_path.generic_string().c_str()); 152 | if(!_mapped_file_lock.try_lock()) 153 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::no_access))); 154 | 155 | set_mapped_file_db_dirty(true); 156 | } 157 | 158 | if(mode == mapped) { 159 | _segment_manager = file_mapped_segment_manager; 160 | } 161 | else { 162 | boost::asio::io_service sig_ios; 163 | boost::asio::signal_set sig_set(sig_ios, SIGINT, SIGTERM); 164 | #ifdef SIGPIPE 165 | sig_set.add(SIGPIPE); 166 | #endif 167 | sig_set.async_wait([](const boost::system::error_code&, int) { 168 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::aborted))); 169 | }); 170 | 171 | try { 172 | if(mode == heap) 173 | _mapped_region = bip::mapped_region(bip::anonymous_shared_memory(_file_mapped_region.get_size())); 174 | else 175 | _mapped_region = get_huge_region(hugepage_paths); 176 | 177 | load_database_file(sig_ios); 178 | 179 | if(mode == locked) { 180 | #ifndef _WIN32 181 | if(mlock(_mapped_region.get_address(), _mapped_region.get_size())) { 182 | std::string what_str("Failed to mlock database \"" + _database_name + "\""); 183 | BOOST_THROW_EXCEPTION(std::system_error(make_error_code(db_error_code::no_mlock), what_str)); 184 | } 185 | std::cerr << "CHAINBASE: Database \"" << _database_name << "\" has been successfully locked in memory" << std::endl; 186 | #endif 187 | } 188 | 189 | _file_mapped_region = bip::mapped_region(); 190 | } 191 | catch(...) { 192 | if(_writable) 193 | set_mapped_file_db_dirty(false); 194 | throw; 195 | } 196 | 197 | _segment_manager = reinterpret_cast((char*)_mapped_region.get_address()+header_size); 198 | } 199 | } 200 | 201 | bip::mapped_region pinnable_mapped_file::get_huge_region(const std::vector& huge_paths) { 202 | std::map page_size_to_paths; 203 | const auto mapped_file_size = _file_mapped_region.get_size(); 204 | 205 | #ifdef __linux__ 206 | for(const std::string& p : huge_paths) { 207 | struct statfs fs; 208 | if(statfs(p.c_str(), &fs)) 209 | BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Could not statfs() path ") + p)); 210 | if(fs.f_type != HUGETLBFS_MAGIC) 211 | BOOST_THROW_EXCEPTION(std::runtime_error(p + std::string(" does not look like a hugepagefs mount"))); 212 | page_size_to_paths[fs.f_bsize] = p; 213 | } 214 | for(auto it = page_size_to_paths.rbegin(); it != page_size_to_paths.rend(); ++it) { 215 | if(mapped_file_size % it->first == 0) { 216 | bfs::path hugepath = bfs::unique_path(bfs::path(it->second + "/%%%%%%%%%%%%%%%%%%%%%%%%%%")); 217 | int fd = creat(hugepath.string().c_str(), _db_permissions.get_permissions()); 218 | if(fd < 0) 219 | BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Could not open hugepage file in ") + it->second + ": " + std::string(strerror(errno)))); 220 | if(ftruncate(fd, mapped_file_size)) 221 | BOOST_THROW_EXCEPTION(std::runtime_error(std::string("Failed to grow hugepage file to specified size"))); 222 | close(fd); 223 | bip::file_mapping filemap(hugepath.generic_string().c_str(), _writable ? bip::read_write : bip::read_only); 224 | bfs::remove(hugepath); 225 | std::cerr << "CHAINBASE: Database \"" << _database_name << "\" using " << it->first << " byte pages" << std::endl; 226 | return bip::mapped_region(filemap, _writable ? bip::read_write : bip::read_only); 227 | } 228 | } 229 | #endif 230 | 231 | std::cerr << "CHAINBASE: Database \"" << _database_name << "\" not using huge pages" << std::endl; 232 | return bip::mapped_region(bip::anonymous_shared_memory(mapped_file_size)); 233 | } 234 | 235 | void pinnable_mapped_file::load_database_file(boost::asio::io_service& sig_ios) { 236 | std::cerr << "CHAINBASE: Preloading \"" << _database_name << "\" database file, this could take a moment..." << std::endl; 237 | char* const src = (char*)_file_mapped_region.get_address(); 238 | char* const dst = (char*)_mapped_region.get_address(); 239 | size_t offset = 0; 240 | time_t t = time(nullptr); 241 | while(offset != _file_mapped_region.get_size()) { 242 | memcpy(dst+offset, src+offset, _db_size_multiple_requirement); 243 | offset += _db_size_multiple_requirement; 244 | 245 | if(time(nullptr) != t) { 246 | t = time(nullptr); 247 | std::cerr << " " << offset/(_file_mapped_region.get_size()/100) << "% complete..." << std::endl; 248 | } 249 | sig_ios.poll(); 250 | } 251 | std::cerr << " Complete" << std::endl; 252 | } 253 | 254 | bool pinnable_mapped_file::all_zeros(char* data, size_t sz) { 255 | uint64_t* p = (uint64_t*)data; 256 | uint64_t* end = p+sz/sizeof(uint64_t); 257 | while(p != end) { 258 | if(*p++ != 0) 259 | return false; 260 | } 261 | return true; 262 | } 263 | 264 | void pinnable_mapped_file::save_database_file() { 265 | std::cerr << "CHAINBASE: Writing \"" << _database_name << "\" database file, this could take a moment..." << std::endl; 266 | char* src = (char*)_mapped_region.get_address(); 267 | char* dst = (char*)_file_mapped_region.get_address(); 268 | size_t offset = 0; 269 | time_t t = time(nullptr); 270 | while(offset != _file_mapped_region.get_size()) { 271 | if(!all_zeros(src+offset, _db_size_multiple_requirement)) 272 | memcpy(dst+offset, src+offset, _db_size_multiple_requirement); 273 | offset += _db_size_multiple_requirement; 274 | 275 | if(time(nullptr) != t) { 276 | t = time(nullptr); 277 | std::cerr << " " << offset/(_file_mapped_region.get_size()/100) << "% complete..." << std::endl; 278 | } 279 | } 280 | std::cerr << " Syncing buffers..." << std::endl; 281 | if(_file_mapped_region.flush(0, 0, false) == false) 282 | std::cerr << "CHAINBASE: ERROR: syncing buffers failed" << std::endl; 283 | std::cerr << " Complete" << std::endl; 284 | } 285 | 286 | pinnable_mapped_file::pinnable_mapped_file(pinnable_mapped_file&& o) : 287 | _mapped_file_lock(std::move(o._mapped_file_lock)), 288 | _data_file_path(std::move(o._data_file_path)), 289 | _database_name(std::move(o._database_name)), 290 | _file_mapped_region(std::move(o._file_mapped_region)), 291 | _mapped_region(std::move(o._mapped_region)) 292 | { 293 | _segment_manager = o._segment_manager; 294 | _writable = o._writable; 295 | o._writable = false; //prevent dtor from doing anything interesting 296 | } 297 | 298 | pinnable_mapped_file& pinnable_mapped_file::operator=(pinnable_mapped_file&& o) { 299 | _mapped_file_lock = std::move(o._mapped_file_lock); 300 | _data_file_path = std::move(o._data_file_path); 301 | _database_name = std::move(o._database_name); 302 | _file_mapped_region = std::move(o._file_mapped_region); 303 | _mapped_region = std::move(o._mapped_region); 304 | _segment_manager = o._segment_manager; 305 | _writable = o._writable; 306 | o._writable = false; //prevent dtor from doing anything interesting 307 | return *this; 308 | } 309 | 310 | pinnable_mapped_file::~pinnable_mapped_file() { 311 | if(_writable) { 312 | if(_mapped_region.get_address()) { //in heap or locked mode 313 | _file_mapped_region = bip::mapped_region(_file_mapping, bip::read_write); 314 | save_database_file(); 315 | } 316 | else 317 | if(_file_mapped_region.flush(0, 0, false) == false) 318 | std::cerr << "CHAINBASE: ERROR: syncing buffers failed" << std::endl; 319 | set_mapped_file_db_dirty(false); 320 | } 321 | } 322 | 323 | void pinnable_mapped_file::set_mapped_file_db_dirty(bool dirty) { 324 | *((char*)_file_mapped_region.get_address()+header_dirty_bit_offset) = dirty; 325 | if(_file_mapped_region.flush(0, 0, false) == false) 326 | std::cerr << "CHAINBASE: ERROR: syncing buffers failed" << std::endl; 327 | } 328 | 329 | std::istream& operator>>(std::istream& in, pinnable_mapped_file::map_mode& runtime) { 330 | std::string s; 331 | in >> s; 332 | if (s == "mapped") 333 | runtime = pinnable_mapped_file::map_mode::mapped; 334 | else if (s == "heap") 335 | runtime = pinnable_mapped_file::map_mode::heap; 336 | else if (s == "locked") 337 | runtime = pinnable_mapped_file::map_mode::locked; 338 | else 339 | in.setstate(std::ios_base::failbit); 340 | return in; 341 | } 342 | 343 | std::ostream& operator<<(std::ostream& osm, pinnable_mapped_file::map_mode m) { 344 | if(m == pinnable_mapped_file::map_mode::mapped) 345 | osm << "mapped"; 346 | else if (m == pinnable_mapped_file::map_mode::heap) 347 | osm << "heap"; 348 | else if (m == pinnable_mapped_file::map_mode::locked) 349 | osm << "locked"; 350 | 351 | return osm; 352 | } 353 | 354 | static std::string print_os(environment::os_t os) { 355 | switch(os) { 356 | case environment::OS_LINUX: return "Linux"; 357 | case environment::OS_MACOS: return "macOS"; 358 | case environment::OS_WINDOWS: return "Windows"; 359 | case environment::OS_OTHER: return "Unknown"; 360 | } 361 | return "error"; 362 | } 363 | static std::string print_arch(environment::arch_t arch) { 364 | switch(arch) { 365 | case environment::ARCH_X86_64: return "x86_64"; 366 | case environment::ARCH_ARM: return "ARM"; 367 | case environment::ARCH_RISCV: return "RISC-v"; 368 | case environment::ARCH_OTHER: return "Unknown"; 369 | } 370 | return "error"; 371 | } 372 | 373 | std::ostream& operator<<(std::ostream& os, const chainbase::environment& dt) { 374 | os << std::right << std::setw(17) << "Compiler: " << dt.compiler << std::endl; 375 | os << std::right << std::setw(17) << "Debug: " << (dt.debug ? "Yes" : "No") << std::endl; 376 | os << std::right << std::setw(17) << "OS: " << print_os(dt.os) << std::endl; 377 | os << std::right << std::setw(17) << "Arch: " << print_arch(dt.arch) << std::endl; 378 | os << std::right << std::setw(17) << "Boost: " << dt.boost_version/100000 << "." 379 | << dt.boost_version/100%1000 << "." 380 | << dt.boost_version%100 << std::endl; 381 | return os; 382 | } 383 | 384 | } 385 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB UNIT_TESTS "*.cpp") 2 | add_executable( chainbase_test ${UNIT_TESTS} ) 3 | target_link_libraries( chainbase_test chainbase Boost::unit_test_framework ${PLATFORM_SPECIFIC_LIBS} ) 4 | 5 | add_test(chainbase_test chainbase_test) 6 | -------------------------------------------------------------------------------- /test/grow_shrink.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace chainbase; 7 | 8 | const pinnable_mapped_file::map_mode test_modes[] = {pinnable_mapped_file::map_mode::mapped, pinnable_mapped_file::map_mode::heap}; 9 | 10 | BOOST_DATA_TEST_CASE(grow_shrink, boost::unit_test::data::make(test_modes), map_mode) { 11 | boost::filesystem::path temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); 12 | try { 13 | const size_t db_start_size = 8u*1024u*1024u; 14 | const size_t db_grow_size = 16u*1024u*1024u; 15 | const size_t db_shrunk_size = 2u*1024u*1024u; 16 | 17 | { 18 | chainbase::database db(temp, database::read_write, db_start_size, false, map_mode); 19 | } 20 | { 21 | chainbase::database db(temp, database::read_write, db_grow_size, false, map_mode); 22 | BOOST_CHECK(db.get_free_memory() > db_start_size); 23 | } 24 | { 25 | chainbase::database db(temp, database::read_write, db_shrunk_size, false, map_mode); 26 | BOOST_CHECK(db.get_free_memory() > db_start_size); 27 | } 28 | 29 | } catch(...) { 30 | bfs::remove_all(temp); 31 | throw; 32 | } 33 | bfs::remove_all(temp); 34 | } 35 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE chainbase test 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | using namespace chainbase; 13 | using namespace boost::multi_index; 14 | 15 | //BOOST_TEST_SUITE( serialization_tests, clean_database_fixture ) 16 | 17 | struct book : public chainbase::object<0, book> { 18 | 19 | template 20 | book( Constructor&& c, Allocator&& a ) { 21 | c(*this); 22 | } 23 | 24 | id_type id; 25 | int a = 0; 26 | int b = 1; 27 | }; 28 | 29 | typedef multi_index_container< 30 | book, 31 | indexed_by< 32 | ordered_unique< member >, 33 | ordered_unique< BOOST_MULTI_INDEX_MEMBER(book,int,a) >, 34 | ordered_unique< BOOST_MULTI_INDEX_MEMBER(book,int,b) > 35 | >, 36 | chainbase::node_allocator 37 | > book_index; 38 | 39 | CHAINBASE_SET_INDEX_TYPE( book, book_index ) 40 | 41 | 42 | BOOST_AUTO_TEST_CASE( open_and_create ) { 43 | boost::filesystem::path temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); 44 | try { 45 | std::cerr << temp << " \n"; 46 | 47 | chainbase::database db(temp, database::read_write, 1024*1024*8); 48 | chainbase::database db2(temp, database::read_only, 0, true); /// open an already created db 49 | BOOST_CHECK_THROW( db2.add_index< book_index >(), std::runtime_error ); /// index does not exist in read only database 50 | 51 | db.add_index< book_index >(); 52 | BOOST_CHECK_THROW( db.add_index(), std::logic_error ); /// cannot add same index twice 53 | 54 | 55 | db2.add_index< book_index >(); /// index should exist now 56 | 57 | 58 | BOOST_TEST_MESSAGE( "Creating book" ); 59 | const auto& new_book = db.create( []( book& b ) { 60 | b.a = 3; 61 | b.b = 4; 62 | } ); 63 | const auto& copy_new_book = db2.get( book::id_type(0) ); 64 | BOOST_REQUIRE( &new_book != ©_new_book ); ///< these are mapped to different address ranges 65 | 66 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 67 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 68 | 69 | db.modify( new_book, [&]( book& b ) { 70 | b.a = 5; 71 | b.b = 6; 72 | }); 73 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 74 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 75 | 76 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 77 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 78 | 79 | { 80 | auto session = db.start_undo_session(true); 81 | db.modify( new_book, [&]( book& b ) { 82 | b.a = 7; 83 | b.b = 8; 84 | }); 85 | 86 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 87 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 88 | } 89 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 90 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 91 | 92 | { 93 | auto session = db.start_undo_session(true); 94 | const auto& book2 = db.create( [&]( book& b ) { 95 | b.a = 9; 96 | b.b = 10; 97 | }); 98 | 99 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 100 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 101 | BOOST_REQUIRE_EQUAL( book2.a, 9 ); 102 | BOOST_REQUIRE_EQUAL( book2.b, 10 ); 103 | } 104 | BOOST_CHECK_THROW( db2.get( book::id_type(1) ), std::out_of_range ); 105 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 106 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 107 | 108 | 109 | { 110 | auto session = db.start_undo_session(true); 111 | db.modify( new_book, [&]( book& b ) { 112 | b.a = 7; 113 | b.b = 8; 114 | }); 115 | 116 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 117 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 118 | session.push(); 119 | } 120 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 121 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 122 | db.undo(); 123 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 124 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 125 | 126 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 127 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 128 | } catch ( ... ) { 129 | bfs::remove_all( temp ); 130 | throw; 131 | } 132 | bfs::remove_all( temp ); 133 | } 134 | 135 | // BOOST_AUTO_TEST_SUITE_END() 136 | -------------------------------------------------------------------------------- /test/undo_index.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace { 12 | int exception_counter = 0; 13 | int throw_at = -1; 14 | struct test_exception_base {}; 15 | template 16 | struct test_exception : E, test_exception_base { 17 | template 18 | test_exception(A&&... a) : E{static_cast(a)...} {} 19 | }; 20 | template 21 | void throw_point(A&&... a) { 22 | if(throw_at != -1 && exception_counter++ >= throw_at) { 23 | throw test_exception{static_cast(a)...}; 24 | } 25 | } 26 | template 27 | void test_exceptions(F&& f) { 28 | for(throw_at = 0; ; ++throw_at) { 29 | exception_counter = 0; 30 | try { 31 | f(); 32 | break; 33 | } catch(test_exception_base&) {} 34 | } 35 | throw_at = -1; 36 | exception_counter = 0; 37 | } 38 | 39 | struct throwing_copy { 40 | throwing_copy() { throw_point(); } 41 | throwing_copy(const throwing_copy&) { throw_point(); } 42 | throwing_copy(throwing_copy&&) noexcept = default; 43 | throwing_copy& operator=(const throwing_copy&) { throw_point(); return *this; } 44 | throwing_copy& operator=(throwing_copy&&) noexcept = default; 45 | }; 46 | 47 | template 48 | struct test_allocator : std::allocator { 49 | test_allocator() = default; 50 | template 51 | test_allocator(const test_allocator&) {} 52 | template 53 | struct rebind { using other = test_allocator; }; 54 | T* allocate(std::size_t count) { 55 | throw_point(); 56 | return std::allocator::allocate(count); 57 | } 58 | }; 59 | 60 | template 61 | struct scope_fail { 62 | scope_fail(F&& f) : _f{static_cast(f)}, _exception_count{std::uncaught_exceptions()} {} 63 | ~scope_fail() { 64 | if(_exception_count != std::uncaught_exceptions()) _f(); 65 | } 66 | F _f; 67 | int _exception_count; 68 | }; 69 | 70 | struct basic_element_t { 71 | template 72 | basic_element_t(C&& c, const std::allocator&) { c(*this); } 73 | uint64_t id; 74 | throwing_copy dummy; 75 | }; 76 | 77 | // TODO: Replace with boost::multi_index::key once we bump our minimum Boost version to at least 1.69 78 | template 79 | struct key_impl; 80 | template 81 | struct key_impl { template using fn = boost::multi_index::member; }; 82 | 83 | template 84 | using key = typename key_impl::template fn; 85 | 86 | } 87 | 88 | BOOST_AUTO_TEST_SUITE(undo_index_tests) 89 | 90 | #define EXCEPTION_TEST_CASE(name) \ 91 | void name##impl(); \ 92 | BOOST_AUTO_TEST_CASE(name) { test_exceptions(&name##impl); } \ 93 | void name##impl () 94 | 95 | EXCEPTION_TEST_CASE(test_simple) { 96 | chainbase::undo_index, boost::multi_index::ordered_unique>> i0; 97 | i0.emplace([](basic_element_t& elem) {}); 98 | const basic_element_t* element = i0.find(0); 99 | BOOST_TEST((element != nullptr && element->id == 0)); 100 | const basic_element_t* e1 = i0.find(1); 101 | BOOST_TEST(e1 == nullptr); 102 | i0.emplace([](basic_element_t& elem) {}); 103 | const basic_element_t* e2 = i0.find(1); 104 | BOOST_TEST((e2 != nullptr && e2->id == 1)); 105 | 106 | i0.modify(*element, [](basic_element_t& elem) {}); 107 | i0.remove(*element); 108 | element = i0.find(0); 109 | BOOST_TEST(element == nullptr); 110 | } 111 | 112 | struct test_element_t { 113 | template 114 | test_element_t(C&& c, const std::allocator&) { c(*this); } 115 | uint64_t id; 116 | int secondary; 117 | throwing_copy dummy; 118 | }; 119 | 120 | // If an exception is thrown while an undo session is active, undo will restore the state. 121 | template 122 | auto capture_state(const C& index) { 123 | std::vector> vec; 124 | for(const auto& elem : index) { 125 | vec.emplace_back(elem, &elem); 126 | } 127 | return scope_fail{[vec = std::move(vec), &index]{ 128 | BOOST_TEST(index.size() == vec.size()); 129 | for(const auto& [elem, ptr] : vec) { 130 | auto * actual0 = index.find(elem.id); 131 | BOOST_TEST(actual0 == ptr); // reference stability is guaranteed 132 | if (actual0 != nullptr) { 133 | BOOST_TEST(actual0->id == elem.id); 134 | BOOST_TEST(actual0->secondary == elem.secondary); 135 | } 136 | auto actual1iter = index.template get<1>().find(elem.secondary); 137 | BOOST_TEST((actual1iter != index.template get<1>().end() && &*actual1iter == actual0)); 138 | } 139 | }}; 140 | } 141 | 142 | EXCEPTION_TEST_CASE(test_insert_undo) { 143 | chainbase::undo_index, 144 | boost::multi_index::ordered_unique>, 145 | boost::multi_index::ordered_unique > > i0; 146 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 147 | BOOST_TEST(i0.find(0)->secondary == 42); 148 | { 149 | auto undo_checker = capture_state(i0); 150 | auto session = i0.start_undo_session(true); 151 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 152 | BOOST_TEST(i0.find(1)->secondary == 12); 153 | } 154 | BOOST_TEST(i0.find(0)->secondary == 42); 155 | BOOST_TEST(i0.find(1) == nullptr); 156 | } 157 | 158 | EXCEPTION_TEST_CASE(test_insert_squash) { 159 | chainbase::undo_index, 160 | boost::multi_index::ordered_unique>, 161 | boost::multi_index::ordered_unique > > i0; 162 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 163 | BOOST_TEST(i0.find(0)->secondary == 42); 164 | { 165 | auto undo_checker = capture_state(i0); 166 | auto session0 = i0.start_undo_session(true); 167 | auto session1 = i0.start_undo_session(true); 168 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 169 | BOOST_TEST(i0.find(1)->secondary == 12); 170 | session1.squash(); 171 | BOOST_TEST(i0.find(1)->secondary == 12); 172 | } 173 | BOOST_TEST(i0.find(0)->secondary == 42); 174 | BOOST_TEST(i0.find(1) == nullptr); 175 | } 176 | 177 | EXCEPTION_TEST_CASE(test_insert_push) { 178 | chainbase::undo_index, 179 | boost::multi_index::ordered_unique>, 180 | boost::multi_index::ordered_unique > > i0; 181 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 182 | BOOST_TEST(i0.find(0)->secondary == 42); 183 | { 184 | auto undo_checker = capture_state(i0); 185 | auto session = i0.start_undo_session(true); 186 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 187 | BOOST_TEST(i0.find(1)->secondary == 12); 188 | session.push(); 189 | i0.commit(i0.revision()); 190 | } 191 | BOOST_TEST(!i0.has_undo_session()); 192 | BOOST_TEST(i0.find(0)->secondary == 42); 193 | BOOST_TEST(i0.find(1)->secondary == 12); 194 | } 195 | 196 | EXCEPTION_TEST_CASE(test_modify_undo) { 197 | chainbase::undo_index, 198 | boost::multi_index::ordered_unique>, 199 | boost::multi_index::ordered_unique>> i0; 200 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 201 | BOOST_TEST(i0.find(0)->secondary == 42); 202 | { 203 | auto undo_checker = capture_state(i0); 204 | auto session = i0.start_undo_session(true); 205 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 206 | BOOST_TEST(i0.find(0)->secondary == 18); 207 | } 208 | BOOST_TEST(i0.find(0)->secondary == 42); 209 | } 210 | 211 | EXCEPTION_TEST_CASE(test_modify_squash) { 212 | chainbase::undo_index, 213 | boost::multi_index::ordered_unique>, 214 | boost::multi_index::ordered_unique>> i0; 215 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 216 | BOOST_TEST(i0.find(0)->secondary == 42); 217 | { 218 | auto undo_checker = capture_state(i0); 219 | auto session0 = i0.start_undo_session(true); 220 | auto session1 = i0.start_undo_session(true); 221 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 222 | BOOST_TEST(i0.find(0)->secondary == 18); 223 | session1.squash(); 224 | BOOST_TEST(i0.find(0)->secondary == 18); 225 | } 226 | BOOST_TEST(i0.find(0)->secondary == 42); 227 | } 228 | 229 | EXCEPTION_TEST_CASE(test_modify_push) { 230 | chainbase::undo_index, 231 | boost::multi_index::ordered_unique>, 232 | boost::multi_index::ordered_unique>> i0; 233 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 234 | BOOST_TEST(i0.find(0)->secondary == 42); 235 | { 236 | auto undo_checker = capture_state(i0); 237 | auto session = i0.start_undo_session(true); 238 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 239 | BOOST_TEST(i0.find(0)->secondary == 18); 240 | session.push(); 241 | i0.commit(i0.revision()); 242 | } 243 | BOOST_TEST(!i0.has_undo_session()); 244 | BOOST_TEST(i0.find(0)->secondary == 18); 245 | } 246 | 247 | EXCEPTION_TEST_CASE(test_remove_undo) { 248 | chainbase::undo_index, 249 | boost::multi_index::ordered_unique>, 250 | boost::multi_index::ordered_unique>> i0; 251 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 252 | BOOST_TEST(i0.find(0)->secondary == 42); 253 | { 254 | auto undo_checker = capture_state(i0); 255 | auto session = i0.start_undo_session(true); 256 | i0.remove(*i0.find(0)); 257 | BOOST_TEST(i0.find(0) == nullptr); 258 | } 259 | BOOST_TEST(i0.find(0)->secondary == 42); 260 | } 261 | 262 | EXCEPTION_TEST_CASE(test_remove_squash) { 263 | chainbase::undo_index, 264 | boost::multi_index::ordered_unique>, 265 | boost::multi_index::ordered_unique>> i0; 266 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 267 | BOOST_TEST(i0.find(0)->secondary == 42); 268 | { 269 | auto undo_checker = capture_state(i0); 270 | auto session0 = i0.start_undo_session(true); 271 | auto session1 = i0.start_undo_session(true); 272 | i0.remove(*i0.find(0)); 273 | BOOST_TEST(i0.find(0) == nullptr); 274 | session1.squash(); 275 | BOOST_TEST(i0.find(0) == nullptr); 276 | } 277 | BOOST_TEST(i0.find(0)->secondary == 42); 278 | } 279 | 280 | EXCEPTION_TEST_CASE(test_remove_push) { 281 | chainbase::undo_index, 282 | boost::multi_index::ordered_unique>, 283 | boost::multi_index::ordered_unique>> i0; 284 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 285 | BOOST_TEST(i0.find(0)->secondary == 42); 286 | { 287 | auto undo_checker = capture_state(i0); 288 | auto session = i0.start_undo_session(true); 289 | i0.remove(*i0.find(0)); 290 | BOOST_TEST(i0.find(0) == nullptr); 291 | session.push(); 292 | i0.commit(i0.revision()); 293 | } 294 | BOOST_TEST(!i0.has_undo_session()); 295 | BOOST_TEST(i0.find(0) == nullptr); 296 | } 297 | 298 | EXCEPTION_TEST_CASE(test_insert_modify) { 299 | chainbase::undo_index, 300 | boost::multi_index::ordered_unique>, 301 | boost::multi_index::ordered_unique>> i0; 302 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 303 | BOOST_TEST(i0.find(0)->secondary == 42); 304 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 305 | BOOST_TEST(i0.find(1)->secondary == 12); 306 | i0.modify(*i0.find(1), [](test_element_t& elem) { elem.secondary = 24; }); 307 | BOOST_TEST(i0.find(1)->secondary == 24); 308 | } 309 | 310 | EXCEPTION_TEST_CASE(test_insert_modify_undo) { 311 | chainbase::undo_index, 312 | boost::multi_index::ordered_unique>, 313 | boost::multi_index::ordered_unique>> i0; 314 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 315 | BOOST_TEST(i0.find(0)->secondary == 42); 316 | { 317 | auto undo_checker = capture_state(i0); 318 | auto session = i0.start_undo_session(true); 319 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 320 | BOOST_TEST(i0.find(1)->secondary == 12); 321 | i0.modify(*i0.find(1), [](test_element_t& elem) { elem.secondary = 24; }); 322 | BOOST_TEST(i0.find(1)->secondary == 24); 323 | } 324 | BOOST_TEST(i0.find(0)->secondary == 42); 325 | BOOST_TEST(i0.find(1) == nullptr); 326 | } 327 | 328 | 329 | EXCEPTION_TEST_CASE(test_insert_modify_squash) { 330 | chainbase::undo_index, 331 | boost::multi_index::ordered_unique>, 332 | boost::multi_index::ordered_unique>> i0; 333 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 334 | BOOST_TEST(i0.find(0)->secondary == 42); 335 | { 336 | auto undo_checker = capture_state(i0); 337 | auto session1 = i0.start_undo_session(true); 338 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 339 | BOOST_TEST(i0.find(1)->secondary == 12); 340 | auto session2 = i0.start_undo_session(true); 341 | i0.modify(*i0.find(1), [](test_element_t& elem) { elem.secondary = 24; }); 342 | BOOST_TEST(i0.find(1)->secondary == 24); 343 | session2.squash(); 344 | } 345 | BOOST_TEST(i0.find(0)->secondary == 42); 346 | BOOST_TEST(i0.find(1) == nullptr); 347 | } 348 | 349 | EXCEPTION_TEST_CASE(test_insert_remove_undo) { 350 | chainbase::undo_index, 351 | boost::multi_index::ordered_unique>, 352 | boost::multi_index::ordered_unique>> i0; 353 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 354 | BOOST_TEST(i0.find(0)->secondary == 42); 355 | { 356 | auto undo_checker = capture_state(i0); 357 | auto session = i0.start_undo_session(true); 358 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 359 | BOOST_TEST(i0.find(1)->secondary == 12); 360 | i0.remove(*i0.find(1)); 361 | BOOST_TEST(i0.find(1) == nullptr); 362 | } 363 | BOOST_TEST(i0.find(0)->secondary == 42); 364 | BOOST_TEST(i0.find(1) == nullptr); 365 | } 366 | 367 | EXCEPTION_TEST_CASE(test_insert_remove_squash) { 368 | chainbase::undo_index, 369 | boost::multi_index::ordered_unique>, 370 | boost::multi_index::ordered_unique>> i0; 371 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 372 | BOOST_TEST(i0.find(0)->secondary == 42); 373 | { 374 | auto undo_checker = capture_state(i0); 375 | auto session1 = i0.start_undo_session(true); 376 | i0.emplace([](test_element_t& elem) { elem.secondary = 12; }); 377 | BOOST_TEST(i0.find(1)->secondary == 12); 378 | auto session2 = i0.start_undo_session(true); 379 | i0.remove(*i0.find(1)); 380 | BOOST_TEST(i0.find(1) == nullptr); 381 | session2.squash(); 382 | } 383 | BOOST_TEST(i0.find(0)->secondary == 42); 384 | BOOST_TEST(i0.find(1) == nullptr); 385 | } 386 | 387 | EXCEPTION_TEST_CASE(test_modify_modify_undo) { 388 | chainbase::undo_index, 389 | boost::multi_index::ordered_unique>, 390 | boost::multi_index::ordered_unique>> i0; 391 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 392 | BOOST_TEST(i0.find(0)->secondary == 42); 393 | { 394 | auto undo_checker = capture_state(i0); 395 | auto session = i0.start_undo_session(true); 396 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 397 | BOOST_TEST(i0.find(0)->secondary == 18); 398 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 24; }); 399 | BOOST_TEST(i0.find(0)->secondary == 24); 400 | } 401 | BOOST_TEST(i0.find(0)->secondary == 42); 402 | } 403 | 404 | EXCEPTION_TEST_CASE(test_modify_modify_squash) { 405 | chainbase::undo_index, 406 | boost::multi_index::ordered_unique>, 407 | boost::multi_index::ordered_unique>> i0; 408 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 409 | BOOST_TEST(i0.find(0)->secondary == 42); 410 | { 411 | auto undo_checker = capture_state(i0); 412 | auto session1 = i0.start_undo_session(true); 413 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 414 | BOOST_TEST(i0.find(0)->secondary == 18); 415 | auto session2 = i0.start_undo_session(true); 416 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 24; }); 417 | BOOST_TEST(i0.find(0)->secondary == 24); 418 | session2.squash(); 419 | } 420 | BOOST_TEST(i0.find(0)->secondary == 42); 421 | } 422 | 423 | EXCEPTION_TEST_CASE(test_modify_remove_undo) { 424 | chainbase::undo_index, 425 | boost::multi_index::ordered_unique>, 426 | boost::multi_index::ordered_unique>> i0; 427 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 428 | BOOST_TEST(i0.find(0)->secondary == 42); 429 | { 430 | auto undo_checker = capture_state(i0); 431 | auto session = i0.start_undo_session(true); 432 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 433 | BOOST_TEST(i0.find(0)->secondary == 18); 434 | i0.remove(*i0.find(0)); 435 | BOOST_TEST(i0.find(0) == nullptr); 436 | } 437 | BOOST_TEST(i0.find(0)->secondary == 42); 438 | } 439 | 440 | EXCEPTION_TEST_CASE(test_modify_remove_squash) { 441 | chainbase::undo_index, 442 | boost::multi_index::ordered_unique>, 443 | boost::multi_index::ordered_unique>> i0; 444 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 445 | BOOST_TEST(i0.find(0)->secondary == 42); 446 | { 447 | auto undo_checker = capture_state(i0); 448 | auto session1 = i0.start_undo_session(true); 449 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 450 | BOOST_TEST(i0.find(0)->secondary == 18); 451 | auto session2 = i0.start_undo_session(true); 452 | i0.remove(*i0.find(0)); 453 | BOOST_TEST(i0.find(0) == nullptr); 454 | session2.squash(); 455 | } 456 | BOOST_TEST(i0.find(0)->secondary == 42); 457 | } 458 | 459 | EXCEPTION_TEST_CASE(test_squash_one) { 460 | chainbase::undo_index, 461 | boost::multi_index::ordered_unique>, 462 | boost::multi_index::ordered_unique>> i0; 463 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 464 | BOOST_TEST(i0.find(0)->secondary == 42); 465 | { 466 | i0.modify(*i0.find(0), [](test_element_t& elem) { elem.secondary = 18; }); 467 | BOOST_TEST(i0.find(0)->secondary == 18); 468 | auto session2 = i0.start_undo_session(true); 469 | i0.remove(*i0.find(0)); 470 | BOOST_TEST(i0.find(0) == nullptr); 471 | session2.squash(); 472 | } 473 | } 474 | 475 | EXCEPTION_TEST_CASE(test_insert_non_unique) { 476 | chainbase::undo_index, 477 | boost::multi_index::ordered_unique>, 478 | boost::multi_index::ordered_unique>> i0; 479 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 480 | BOOST_TEST(i0.find(0)->secondary == 42); 481 | BOOST_CHECK_THROW(i0.emplace([](test_element_t& elem) { elem.secondary = 42; }), std::exception); 482 | BOOST_TEST(i0.find(0)->secondary == 42); 483 | } 484 | 485 | struct conflict_element_t { 486 | template 487 | conflict_element_t(C&& c, const test_allocator&) { c(*this); } 488 | uint64_t id; 489 | int x0; 490 | int x1; 491 | int x2; 492 | throwing_copy dummy; 493 | }; 494 | 495 | EXCEPTION_TEST_CASE(test_modify_conflict) { 496 | chainbase::undo_index, 497 | boost::multi_index::ordered_unique>, 498 | boost::multi_index::ordered_unique>, 499 | boost::multi_index::ordered_unique>, 500 | boost::multi_index::ordered_unique>> i0; 501 | // insert 3 elements 502 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 0; elem.x1 = 10; elem.x2 = 10; }); 503 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 11; elem.x1 = 1; elem.x2 = 11; }); 504 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 12; elem.x1 = 12; elem.x2 = 2; }); 505 | { 506 | auto session = i0.start_undo_session(true); 507 | // set them to a different value 508 | i0.modify(*i0.find(0), [](conflict_element_t& elem) { elem.x0 = 10; elem.x1 = 10; elem.x2 = 10; }); 509 | i0.modify(*i0.find(1), [](conflict_element_t& elem) { elem.x0 = 11; elem.x1 = 11; elem.x2 = 11; }); 510 | i0.modify(*i0.find(2), [](conflict_element_t& elem) { elem.x0 = 12; elem.x1 = 12; elem.x2 = 12; }); 511 | // create a circular conflict with the original values 512 | i0.modify(*i0.find(0), [](conflict_element_t& elem) { elem.x0 = 10; elem.x1 = 1; elem.x2 = 10; }); 513 | i0.modify(*i0.find(1), [](conflict_element_t& elem) { elem.x0 = 11; elem.x1 = 11; elem.x2 = 2; }); 514 | i0.modify(*i0.find(2), [](conflict_element_t& elem) { elem.x0 = 0; elem.x1 = 12; elem.x2 = 12; }); 515 | } 516 | BOOST_TEST(i0.find(0)->x0 == 0); 517 | BOOST_TEST(i0.find(1)->x1 == 1); 518 | BOOST_TEST(i0.find(2)->x2 == 2); 519 | // Check lookup in the other indices 520 | BOOST_TEST(i0.get<1>().find(0)->x0 == 0); 521 | BOOST_TEST(i0.get<1>().find(11)->x0 == 11); 522 | BOOST_TEST(i0.get<1>().find(12)->x0 == 12); 523 | BOOST_TEST(i0.get<2>().find(10)->x1 == 10); 524 | BOOST_TEST(i0.get<2>().find(1)->x1 == 1); 525 | BOOST_TEST(i0.get<2>().find(12)->x1 == 12); 526 | BOOST_TEST(i0.get<3>().find(10)->x2 == 10); 527 | BOOST_TEST(i0.get<3>().find(11)->x2 == 11); 528 | BOOST_TEST(i0.get<3>().find(2)->x2 == 2); 529 | } 530 | 531 | BOOST_DATA_TEST_CASE(test_insert_fail, boost::unit_test::data::make({true, false}), use_undo) { 532 | chainbase::undo_index, 533 | boost::multi_index::ordered_unique>, 534 | boost::multi_index::ordered_unique>, 535 | boost::multi_index::ordered_unique>, 536 | boost::multi_index::ordered_unique>> i0; 537 | // insert 3 elements 538 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 10; elem.x1 = 10; elem.x2 = 10; }); 539 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 11; elem.x1 = 11; elem.x2 = 11; }); 540 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 12; elem.x1 = 12; elem.x2 = 12; }); 541 | { 542 | auto session = i0.start_undo_session(true); 543 | // Insert a value with a duplicate 544 | BOOST_CHECK_THROW(i0.emplace([](conflict_element_t& elem) { elem.x0 = 81; elem.x1 = 11; elem.x2 = 91; }), std::logic_error); 545 | } 546 | BOOST_TEST(i0.find(0)->x0 == 10); 547 | BOOST_TEST(i0.find(1)->x1 == 11); 548 | BOOST_TEST(i0.find(2)->x2 == 12); 549 | // Check lookup in the other indices 550 | BOOST_TEST(i0.get<1>().find(10)->x0 == 10); 551 | BOOST_TEST(i0.get<1>().find(11)->x0 == 11); 552 | BOOST_TEST(i0.get<1>().find(12)->x0 == 12); 553 | BOOST_TEST(i0.get<2>().find(10)->x1 == 10); 554 | BOOST_TEST(i0.get<2>().find(11)->x1 == 11); 555 | BOOST_TEST(i0.get<2>().find(12)->x1 == 12); 556 | BOOST_TEST(i0.get<3>().find(10)->x2 == 10); 557 | BOOST_TEST(i0.get<3>().find(11)->x2 == 11); 558 | BOOST_TEST(i0.get<3>().find(12)->x2 == 12); 559 | } 560 | 561 | EXCEPTION_TEST_CASE(test_modify_fail) { 562 | chainbase::undo_index, 563 | boost::multi_index::ordered_unique>, 564 | boost::multi_index::ordered_unique>, 565 | boost::multi_index::ordered_unique>, 566 | boost::multi_index::ordered_unique>> i0; 567 | // insert 3 elements 568 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 10; elem.x1 = 10; elem.x2 = 10; }); 569 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 11; elem.x1 = 11; elem.x2 = 11; }); 570 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 12; elem.x1 = 12; elem.x2 = 12; }); 571 | { 572 | auto session = i0.start_undo_session(true); 573 | // Insert a value with a duplicate 574 | i0.emplace([](conflict_element_t& elem) { elem.x0 = 71; elem.x1 = 81; elem.x2 = 91; }); 575 | BOOST_CHECK_THROW(i0.modify(i0.get(3), [](conflict_element_t& elem) { elem.x0 = 71; elem.x1 = 10; elem.x2 = 91; }), std::logic_error); 576 | } 577 | BOOST_TEST(i0.get<0>().size() == 3); 578 | BOOST_TEST(i0.get<1>().size() == 3); 579 | BOOST_TEST(i0.get<2>().size() == 3); 580 | BOOST_TEST(i0.get<3>().size() == 3); 581 | BOOST_TEST(i0.find(0)->x0 == 10); 582 | BOOST_TEST(i0.find(1)->x1 == 11); 583 | BOOST_TEST(i0.find(2)->x2 == 12); 584 | // Check lookup in the other indices 585 | BOOST_TEST(i0.get<1>().find(10)->x0 == 10); 586 | BOOST_TEST(i0.get<1>().find(11)->x0 == 11); 587 | BOOST_TEST(i0.get<1>().find(12)->x0 == 12); 588 | BOOST_TEST(i0.get<2>().find(10)->x1 == 10); 589 | BOOST_TEST(i0.get<2>().find(11)->x1 == 11); 590 | BOOST_TEST(i0.get<2>().find(12)->x1 == 12); 591 | BOOST_TEST(i0.get<3>().find(10)->x2 == 10); 592 | BOOST_TEST(i0.get<3>().find(11)->x2 == 11); 593 | BOOST_TEST(i0.get<3>().find(12)->x2 == 12); 594 | } 595 | 596 | struct by_secondary {}; 597 | 598 | BOOST_AUTO_TEST_CASE(test_project) { 599 | chainbase::undo_index, 600 | boost::multi_index::ordered_unique>, 601 | boost::multi_index::ordered_unique, key<&test_element_t::secondary>>> i0; 602 | i0.emplace([](test_element_t& elem) { elem.secondary = 42; }); 603 | BOOST_TEST(i0.project(i0.begin()) == i0.get().begin()); 604 | BOOST_TEST(i0.project(i0.end()) == i0.get().end()); 605 | BOOST_TEST(i0.project<1>(i0.begin()) == i0.get().begin()); 606 | BOOST_TEST(i0.project<1>(i0.end()) == i0.get().end()); 607 | } 608 | 609 | 610 | EXCEPTION_TEST_CASE(test_remove_tracking_session) { 611 | chainbase::undo_index, 612 | boost::multi_index::ordered_unique>, 613 | boost::multi_index::ordered_unique>> i0; 614 | i0.emplace([](test_element_t& elem) { elem.secondary = 20; }); 615 | auto session = i0.start_undo_session(true); 616 | auto tracker = i0.track_removed(); 617 | i0.emplace([](test_element_t& elem) { elem.secondary = 21; }); 618 | const test_element_t& elem0 = *i0.find(0); 619 | const test_element_t& elem1 = *i0.find(1); 620 | BOOST_CHECK(!tracker.is_removed(elem0)); 621 | BOOST_CHECK(!tracker.is_removed(elem1)); 622 | tracker.remove(elem0); 623 | tracker.remove(elem1); 624 | BOOST_CHECK(tracker.is_removed(elem0)); 625 | BOOST_CHECK(tracker.is_removed(elem1)); 626 | } 627 | 628 | 629 | EXCEPTION_TEST_CASE(test_remove_tracking_no_session) { 630 | chainbase::undo_index, 631 | boost::multi_index::ordered_unique>, 632 | boost::multi_index::ordered_unique>> i0; 633 | i0.emplace([](test_element_t& elem) { elem.secondary = 20; }); 634 | auto tracker = i0.track_removed(); 635 | i0.emplace([](test_element_t& elem) { elem.secondary = 21; }); 636 | const test_element_t& elem0 = *i0.find(0); 637 | const test_element_t& elem1 = *i0.find(1); 638 | BOOST_CHECK(!tracker.is_removed(elem0)); 639 | BOOST_CHECK(!tracker.is_removed(elem1)); 640 | tracker.remove(elem0); 641 | tracker.remove(elem1); 642 | BOOST_CHECK(tracker.is_removed(elem0)); 643 | BOOST_CHECK(tracker.is_removed(elem1)); 644 | } 645 | 646 | BOOST_AUTO_TEST_SUITE_END() 647 | --------------------------------------------------------------------------------