├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── include └── chainbase │ └── chainbase.hpp ├── src └── chainbase.cpp └── test ├── CMakeLists.txt └── test.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Defines ChainBase library target. 2 | project( ChainBase ) 3 | cmake_minimum_required( VERSION 2.8.12 ) 4 | 5 | #list( APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules" ) 6 | 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS "ON") 8 | 9 | SET(BOOST_COMPONENTS) 10 | LIST(APPEND BOOST_COMPONENTS thread 11 | date_time 12 | system 13 | filesystem 14 | chrono 15 | unit_test_framework 16 | locale) 17 | 18 | SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) 19 | 20 | IF( WIN32 ) 21 | SET(BOOST_ROOT $ENV{BOOST_ROOT}) 22 | set(Boost_USE_MULTITHREADED ON) 23 | set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries 24 | ENDIF(WIN32) 25 | 26 | FIND_PACKAGE(Boost 1.57 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) 27 | 28 | SET(PLATFORM_LIBRARIES) 29 | 30 | if( APPLE ) 31 | # Apple Specific Options Here 32 | message( STATUS "Configuring ChainBase on OS X" ) 33 | set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++11 -stdlib=libc++ -Wall -Wno-conversion" ) 34 | else( APPLE ) 35 | # Linux Specific Options Here 36 | message( STATUS "Configuring ChainBase on Linux" ) 37 | set( CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -std=c++11 -Wall" ) 38 | set( rt_library rt ) 39 | set( pthread_library pthread) 40 | if ( FULL_STATIC_BUILD ) 41 | set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++ -static-libgcc") 42 | endif ( FULL_STATIC_BUILD ) 43 | LIST( APPEND PLATFORM_LIBRARIES pthread ) 44 | endif( APPLE ) 45 | 46 | if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) 47 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp" ) 48 | endif() 49 | 50 | if( "${CMAKE_GENERATOR}" STREQUAL "Ninja" ) 51 | if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) 52 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics" ) 53 | endif() 54 | endif() 55 | 56 | # based on http://www.delorie.com/gnu/docs/gdb/gdb_70.html 57 | # uncomment this line to tell GDB about macros (slows compile times) 58 | # set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-2 -g3" ) 59 | 60 | set(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL "Build ChainBase for code coverage analysis") 61 | 62 | if(ENABLE_COVERAGE_TESTING) 63 | SET(CMAKE_CXX_FLAGS "--coverage ${CMAKE_CXX_FLAGS}") 64 | endif() 65 | 66 | 67 | file(GLOB HEADERS "include/*.hpp") 68 | add_library( chainbase src/chainbase.cpp ${HEADERS} ) 69 | target_link_libraries( chainbase ${Boost_LIBRARIES} ${PLATFORM_LIBRARIES} ) 70 | target_include_directories( chainbase PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ${Boost_INCLUDE_DIR} ) 71 | 72 | add_subdirectory( test ) 73 | 74 | install( TARGETS 75 | chainbase 76 | 77 | RUNTIME DESTINATION bin 78 | LIBRARY DESTINATION lib 79 | ARCHIVE DESTINATION lib 80 | ) 81 | install( FILES ${HEADERS} DESTINATION "include/" ) 82 | -------------------------------------------------------------------------------- /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-infinate 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++11 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 | 159 | -------------------------------------------------------------------------------- /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 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifndef CHAINBASE_NUM_RW_LOCKS 32 | #define CHAINBASE_NUM_RW_LOCKS 10 33 | #endif 34 | 35 | #ifdef CHAINBASE_CHECK_LOCKING 36 | #define CHAINBASE_REQUIRE_READ_LOCK(m, t) require_read_lock(m, typeid(t).name()) 37 | #define CHAINBASE_REQUIRE_WRITE_LOCK(m, t) require_write_lock(m, typeid(t).name()) 38 | #else 39 | #define CHAINBASE_REQUIRE_READ_LOCK(m, t) 40 | #define CHAINBASE_REQUIRE_WRITE_LOCK(m, t) 41 | #endif 42 | 43 | namespace chainbase { 44 | 45 | namespace bip = boost::interprocess; 46 | namespace bfs = boost::filesystem; 47 | using std::unique_ptr; 48 | using std::vector; 49 | 50 | template 51 | using allocator = bip::allocator; 52 | 53 | typedef bip::basic_string< char, std::char_traits< char >, allocator< char > > shared_string; 54 | 55 | template 56 | using shared_vector = std::vector >; 57 | 58 | struct strcmp_less 59 | { 60 | bool operator()( const shared_string& a, const shared_string& b )const 61 | { 62 | return less( a.c_str(), b.c_str() ); 63 | } 64 | 65 | bool operator()( const shared_string& a, const std::string& b )const 66 | { 67 | return less( a.c_str(), b.c_str() ); 68 | } 69 | 70 | bool operator()( const std::string& a, const shared_string& b )const 71 | { 72 | return less( a.c_str(), b.c_str() ); 73 | } 74 | private: 75 | inline bool less( const char* a, const char* b )const 76 | { 77 | return std::strcmp( a, b ) < 0; 78 | } 79 | }; 80 | 81 | typedef boost::interprocess::interprocess_sharable_mutex read_write_mutex; 82 | typedef boost::interprocess::sharable_lock< read_write_mutex > read_lock; 83 | typedef boost::unique_lock< read_write_mutex > write_lock; 84 | 85 | /** 86 | * Object ID type that includes the type of the object it references 87 | */ 88 | template 89 | class oid { 90 | public: 91 | oid( int64_t i = 0 ):_id(i){} 92 | 93 | oid& operator++() { ++_id; return *this; } 94 | 95 | friend bool operator < ( const oid& a, const oid& b ) { return a._id < b._id; } 96 | friend bool operator > ( const oid& a, const oid& b ) { return a._id > b._id; } 97 | friend bool operator == ( const oid& a, const oid& b ) { return a._id == b._id; } 98 | friend bool operator != ( const oid& a, const oid& b ) { return a._id != b._id; } 99 | int64_t _id = 0; 100 | }; 101 | 102 | template 103 | struct object 104 | { 105 | typedef oid id_type; 106 | static const uint16_t type_id = TypeNumber; 107 | }; 108 | 109 | /** this class is ment to be specified to enable lookup of index type by object type using 110 | * the SET_INDEX_TYPE macro. 111 | **/ 112 | template 113 | struct get_index_type {}; 114 | 115 | /** 116 | * This macro must be used at global scope and OBJECT_TYPE and INDEX_TYPE must be fully qualified 117 | */ 118 | #define CHAINBASE_SET_INDEX_TYPE( OBJECT_TYPE, INDEX_TYPE ) \ 119 | namespace chainbase { template<> struct get_index_type { typedef INDEX_TYPE type; }; } 120 | 121 | #define CHAINBASE_DEFAULT_CONSTRUCTOR( OBJECT_TYPE ) \ 122 | template \ 123 | OBJECT_TYPE( Constructor&& c, Allocator&& ) { c(*this); } 124 | 125 | template< typename value_type > 126 | class undo_state 127 | { 128 | public: 129 | typedef typename value_type::id_type id_type; 130 | typedef allocator< std::pair > id_value_allocator_type; 131 | typedef allocator< id_type > id_allocator_type; 132 | 133 | template 134 | undo_state( allocator al ) 135 | :old_values( id_value_allocator_type( al.get_segment_manager() ) ), 136 | removed_values( id_value_allocator_type( al.get_segment_manager() ) ), 137 | new_ids( id_allocator_type( al.get_segment_manager() ) ){} 138 | 139 | typedef boost::interprocess::map< id_type, value_type, std::less, id_value_allocator_type > id_value_type_map; 140 | typedef boost::interprocess::set< id_type, std::less, id_allocator_type > id_type_set; 141 | 142 | id_value_type_map old_values; 143 | id_value_type_map removed_values; 144 | id_type_set new_ids; 145 | id_type old_next_id = 0; 146 | int64_t revision = 0; 147 | }; 148 | 149 | /** 150 | * The code we want to implement is this: 151 | * 152 | * ++target; try { ... } finally { --target } 153 | * 154 | * In C++ the only way to implement finally is to create a class 155 | * with a destructor, so that's what we do here. 156 | */ 157 | class int_incrementer 158 | { 159 | public: 160 | int_incrementer( int32_t& target ) : _target(target) 161 | { ++_target; } 162 | ~int_incrementer() 163 | { --_target; } 164 | 165 | int32_t get()const 166 | { return _target; } 167 | 168 | private: 169 | int32_t& _target; 170 | }; 171 | 172 | /** 173 | * The value_type stored in the multiindex container must have a integer field with the name 'id'. This will 174 | * be the primary key and it will be assigned and managed by generic_index. 175 | * 176 | * Additionally, the constructor for value_type must take an allocator 177 | */ 178 | template 179 | class generic_index 180 | { 181 | public: 182 | typedef bip::managed_mapped_file::segment_manager segment_manager_type; 183 | typedef MultiIndexType index_type; 184 | typedef typename index_type::value_type value_type; 185 | typedef bip::allocator< generic_index, segment_manager_type > allocator_type; 186 | typedef undo_state< value_type > undo_state_type; 187 | 188 | generic_index( allocator a ) 189 | :_stack(a),_indices( a ),_size_of_value_type( sizeof(typename MultiIndexType::node_type) ),_size_of_this(sizeof(*this)){} 190 | 191 | void validate()const { 192 | if( sizeof(typename MultiIndexType::node_type) != _size_of_value_type || sizeof(*this) != _size_of_this ) 193 | BOOST_THROW_EXCEPTION( std::runtime_error("content of memory does not match data expected by executable") ); 194 | } 195 | 196 | /** 197 | * Construct a new element in the multi_index_container. 198 | * Set the ID to the next available ID, then increment _next_id and fire off on_create(). 199 | */ 200 | template 201 | const value_type& emplace( Constructor&& c ) { 202 | auto new_id = _next_id; 203 | 204 | auto constructor = [&]( value_type& v ) { 205 | v.id = new_id; 206 | c( v ); 207 | }; 208 | 209 | auto insert_result = _indices.emplace( constructor, _indices.get_allocator() ); 210 | 211 | if( !insert_result.second ) { 212 | BOOST_THROW_EXCEPTION( std::logic_error("could not insert object, most likely a uniqueness constraint was violated") ); 213 | } 214 | 215 | ++_next_id; 216 | on_create( *insert_result.first ); 217 | return *insert_result.first; 218 | } 219 | 220 | template 221 | void modify( const value_type& obj, Modifier&& m ) { 222 | on_modify( obj ); 223 | auto ok = _indices.modify( _indices.iterator_to( obj ), m ); 224 | if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); 225 | } 226 | 227 | void remove( const value_type& obj ) { 228 | on_remove( obj ); 229 | _indices.erase( _indices.iterator_to( obj ) ); 230 | } 231 | 232 | template 233 | const value_type* find( CompatibleKey&& key )const { 234 | auto itr = _indices.find( std::forward(key) ); 235 | if( itr != _indices.end() ) return &*itr; 236 | return nullptr; 237 | } 238 | 239 | template 240 | const value_type& get( CompatibleKey&& key )const { 241 | auto ptr = find( key ); 242 | if( !ptr ) BOOST_THROW_EXCEPTION( std::out_of_range("key not found") ); 243 | return *ptr; 244 | } 245 | 246 | const index_type& indices()const { return _indices; } 247 | 248 | class session { 249 | public: 250 | session( session&& mv ) 251 | :_index(mv._index),_apply(mv._apply){ mv._apply = false; } 252 | 253 | ~session() { 254 | if( _apply ) { 255 | _index.undo(); 256 | } 257 | } 258 | 259 | /** leaves the UNDO state on the stack when session goes out of scope */ 260 | void push() { _apply = false; } 261 | /** combines this session with the prior session */ 262 | void squash() { if( _apply ) _index.squash(); _apply = false; } 263 | void undo() { if( _apply ) _index.undo(); _apply = false; } 264 | 265 | session& operator = ( session&& mv ) { 266 | if( this == &mv ) return *this; 267 | if( _apply ) _index.undo(); 268 | _apply = mv._apply; 269 | mv._apply = false; 270 | return *this; 271 | } 272 | 273 | int64_t revision()const { return _revision; } 274 | 275 | private: 276 | friend class generic_index; 277 | 278 | session( generic_index& idx, int64_t revision ) 279 | :_index(idx),_revision(revision) { 280 | if( revision == -1 ) 281 | _apply = false; 282 | } 283 | 284 | generic_index& _index; 285 | bool _apply = true; 286 | int64_t _revision = 0; 287 | }; 288 | 289 | session start_undo_session( bool enabled ) { 290 | if( enabled ) { 291 | _stack.emplace_back( _indices.get_allocator() ); 292 | _stack.back().old_next_id = _next_id; 293 | _stack.back().revision = ++_revision; 294 | return session( *this, _revision ); 295 | } else { 296 | return session( *this, -1 ); 297 | } 298 | } 299 | 300 | const index_type& indicies()const { return _indices; } 301 | int64_t revision()const { return _revision; } 302 | 303 | 304 | /** 305 | * Restores the state to how it was prior to the current session discarding all changes 306 | * made between the last revision and the current revision. 307 | */ 308 | void undo() { 309 | if( !enabled() ) return; 310 | 311 | const auto& head = _stack.back(); 312 | 313 | for( auto& item : head.old_values ) { 314 | auto ok = _indices.modify( _indices.find( item.second.id ), [&]( value_type& v ) { 315 | v = std::move( item.second ); 316 | }); 317 | if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not modify object, most likely a uniqueness constraint was violated" ) ); 318 | } 319 | 320 | for( auto id : head.new_ids ) 321 | { 322 | _indices.erase( _indices.find( id ) ); 323 | } 324 | _next_id = head.old_next_id; 325 | 326 | for( auto& item : head.removed_values ) { 327 | bool ok = _indices.emplace( std::move( item.second ) ).second; 328 | if( !ok ) BOOST_THROW_EXCEPTION( std::logic_error( "Could not restore object, most likely a uniqueness constraint was violated" ) ); 329 | } 330 | 331 | _stack.pop_back(); 332 | --_revision; 333 | } 334 | 335 | /** 336 | * This method works similar to git squash, it merges the change set from the two most 337 | * recent revision numbers into one revision number (reducing the head revision number) 338 | * 339 | * This method does not change the state of the index, only the state of the undo buffer. 340 | */ 341 | void squash() 342 | { 343 | if( !enabled() ) return; 344 | if( _stack.size() == 1 ) { 345 | _stack.pop_front(); 346 | return; 347 | } 348 | 349 | auto& state = _stack.back(); 350 | auto& prev_state = _stack[_stack.size()-2]; 351 | 352 | // An object's relationship to a state can be: 353 | // in new_ids : new 354 | // in old_values (was=X) : upd(was=X) 355 | // in removed (was=X) : del(was=X) 356 | // not in any of above : nop 357 | // 358 | // When merging A=prev_state and B=state we have a 4x4 matrix of all possibilities: 359 | // 360 | // |--------------------- B ----------------------| 361 | // 362 | // +------------+------------+------------+------------+ 363 | // | new | upd(was=Y) | del(was=Y) | nop | 364 | // +------------+------------+------------+------------+------------+ 365 | // / | new | N/A | new A| nop C| new A| 366 | // | +------------+------------+------------+------------+------------+ 367 | // | | upd(was=X) | N/A | upd(was=X)A| del(was=X)C| upd(was=X)A| 368 | // A +------------+------------+------------+------------+------------+ 369 | // | | del(was=X) | N/A | N/A | N/A | del(was=X)A| 370 | // | +------------+------------+------------+------------+------------+ 371 | // \ | nop | new B| upd(was=Y)B| del(was=Y)B| nop AB| 372 | // +------------+------------+------------+------------+------------+ 373 | // 374 | // Each entry was composed by labelling what should occur in the given case. 375 | // 376 | // Type A means the composition of states contains the same entry as the first of the two merged states for that object. 377 | // Type B means the composition of states contains the same entry as the second of the two merged states for that object. 378 | // Type C means the composition of states contains an entry different from either of the merged states for that object. 379 | // Type N/A means the composition of states violates causal timing. 380 | // Type AB means both type A and type B simultaneously. 381 | // 382 | // The merge() operation is defined as modifying prev_state in-place to be the state object which represents the composition of 383 | // state A and B. 384 | // 385 | // Type A (and AB) can be implemented as a no-op; prev_state already contains the correct value for the merged state. 386 | // Type B (and AB) can be implemented by copying from state to prev_state. 387 | // Type C needs special case-by-case logic. 388 | // Type N/A can be ignored or assert(false) as it can only occur if prev_state and state have illegal values 389 | // (a serious logic error which should never happen). 390 | // 391 | 392 | // We can only be outside type A/AB (the nop path) if B is not nop, so it suffices to iterate through B's three containers. 393 | 394 | for( const auto& item : state.old_values ) 395 | { 396 | if( prev_state.new_ids.find( item.second.id ) != prev_state.new_ids.end() ) 397 | { 398 | // new+upd -> new, type A 399 | continue; 400 | } 401 | if( prev_state.old_values.find( item.second.id ) != prev_state.old_values.end() ) 402 | { 403 | // upd(was=X) + upd(was=Y) -> upd(was=X), type A 404 | continue; 405 | } 406 | // del+upd -> N/A 407 | assert( prev_state.removed_values.find(item.second.id) == prev_state.removed_values.end() ); 408 | // nop+upd(was=Y) -> upd(was=Y), type B 409 | prev_state.old_values.emplace( std::move(item) ); 410 | } 411 | 412 | // *+new, but we assume the N/A cases don't happen, leaving type B nop+new -> new 413 | for( auto id : state.new_ids ) 414 | prev_state.new_ids.insert(id); 415 | 416 | // *+del 417 | for( auto& obj : state.removed_values ) 418 | { 419 | if( prev_state.new_ids.find(obj.second.id) != prev_state.new_ids.end() ) 420 | { 421 | // new + del -> nop (type C) 422 | prev_state.new_ids.erase(obj.second.id); 423 | continue; 424 | } 425 | auto it = prev_state.old_values.find(obj.second.id); 426 | if( it != prev_state.old_values.end() ) 427 | { 428 | // upd(was=X) + del(was=Y) -> del(was=X) 429 | prev_state.removed_values.emplace( std::move(*it) ); 430 | prev_state.old_values.erase(obj.second.id); 431 | continue; 432 | } 433 | // del + del -> N/A 434 | assert( prev_state.removed_values.find( obj.second.id ) == prev_state.removed_values.end() ); 435 | // nop + del(was=Y) -> del(was=Y) 436 | prev_state.removed_values.emplace( std::move(obj) ); //[obj.second->id] = std::move(obj.second); 437 | } 438 | 439 | _stack.pop_back(); 440 | --_revision; 441 | } 442 | 443 | /** 444 | * Discards all undo history prior to revision 445 | */ 446 | void commit( int64_t revision ) 447 | { 448 | while( _stack.size() && _stack[0].revision <= revision ) 449 | { 450 | _stack.pop_front(); 451 | } 452 | } 453 | 454 | /** 455 | * Unwinds all undo states 456 | */ 457 | void undo_all() 458 | { 459 | while( enabled() ) 460 | undo(); 461 | } 462 | 463 | void set_revision( uint64_t revision ) 464 | { 465 | if( _stack.size() != 0 ) BOOST_THROW_EXCEPTION( std::logic_error("cannot set revision while there is an existing undo stack") ); 466 | _revision = revision; 467 | } 468 | 469 | void remove_object( int64_t id ) 470 | { 471 | const value_type* val = find( typename value_type::id_type(id) ); 472 | if( !val ) BOOST_THROW_EXCEPTION( std::out_of_range( boost::lexical_cast(id) ) ); 473 | remove( *val ); 474 | } 475 | 476 | private: 477 | bool enabled()const { return _stack.size(); } 478 | 479 | void on_modify( const value_type& v ) { 480 | if( !enabled() ) return; 481 | 482 | auto& head = _stack.back(); 483 | 484 | if( head.new_ids.find( v.id ) != head.new_ids.end() ) 485 | return; 486 | 487 | auto itr = head.old_values.find( v.id ); 488 | if( itr != head.old_values.end() ) 489 | return; 490 | 491 | head.old_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); 492 | } 493 | 494 | void on_remove( const value_type& v ) { 495 | if( !enabled() ) return; 496 | 497 | auto& head = _stack.back(); 498 | if( head.new_ids.count(v.id) ) { 499 | head.new_ids.erase( v.id ); 500 | return; 501 | } 502 | 503 | auto itr = head.old_values.find( v.id ); 504 | if( itr != head.old_values.end() ) { 505 | head.removed_values.emplace( std::move( *itr ) ); 506 | head.old_values.erase( v.id ); 507 | return; 508 | } 509 | 510 | if( head.removed_values.count( v.id ) ) 511 | return; 512 | 513 | head.removed_values.emplace( std::pair< typename value_type::id_type, const value_type& >( v.id, v ) ); 514 | } 515 | 516 | void on_create( const value_type& v ) { 517 | if( !enabled() ) return; 518 | auto& head = _stack.back(); 519 | 520 | head.new_ids.insert( v.id ); 521 | } 522 | 523 | boost::interprocess::deque< undo_state_type, allocator > _stack; 524 | 525 | /** 526 | * Each new session increments the revision, a squash will decrement the revision by combining 527 | * the two most recent revisions into one revision. 528 | * 529 | * Commit will discard all revisions prior to the committed revision. 530 | */ 531 | int64_t _revision = 0; 532 | typename value_type::id_type _next_id = 0; 533 | index_type _indices; 534 | uint32_t _size_of_value_type = 0; 535 | uint32_t _size_of_this = 0; 536 | }; 537 | 538 | class abstract_session { 539 | public: 540 | virtual ~abstract_session(){}; 541 | virtual void push() = 0; 542 | virtual void squash() = 0; 543 | virtual void undo() = 0; 544 | virtual int64_t revision()const = 0; 545 | }; 546 | 547 | template 548 | class session_impl : public abstract_session 549 | { 550 | public: 551 | session_impl( SessionType&& s ):_session( std::move( s ) ){} 552 | 553 | virtual void push() override { _session.push(); } 554 | virtual void squash() override{ _session.squash(); } 555 | virtual void undo() override { _session.undo(); } 556 | virtual int64_t revision()const override { return _session.revision(); } 557 | private: 558 | SessionType _session; 559 | }; 560 | 561 | class abstract_index 562 | { 563 | public: 564 | abstract_index( void* i ):_idx_ptr(i){} 565 | virtual ~abstract_index(){} 566 | virtual void set_revision( uint64_t revision ) = 0; 567 | virtual unique_ptr start_undo_session( bool enabled ) = 0; 568 | 569 | virtual int64_t revision()const = 0; 570 | virtual void undo()const = 0; 571 | virtual void squash()const = 0; 572 | virtual void commit( int64_t revision )const = 0; 573 | virtual void undo_all()const = 0; 574 | virtual uint32_t type_id()const = 0; 575 | 576 | virtual void remove_object( int64_t id ) = 0; 577 | 578 | void* get()const { return _idx_ptr; } 579 | private: 580 | void* _idx_ptr; 581 | }; 582 | 583 | template 584 | class index_impl : public abstract_index { 585 | public: 586 | index_impl( BaseIndex& base ):abstract_index( &base ),_base(base){} 587 | 588 | virtual unique_ptr start_undo_session( bool enabled ) override { 589 | return unique_ptr(new session_impl( _base.start_undo_session( enabled ) ) ); 590 | } 591 | 592 | virtual void set_revision( uint64_t revision ) override { _base.set_revision( revision ); } 593 | virtual int64_t revision()const override { return _base.revision(); } 594 | virtual void undo()const override { _base.undo(); } 595 | virtual void squash()const override { _base.squash(); } 596 | virtual void commit( int64_t revision )const override { _base.commit(revision); } 597 | virtual void undo_all() const override {_base.undo_all(); } 598 | virtual uint32_t type_id()const override { return BaseIndex::value_type::type_id; } 599 | 600 | virtual void remove_object( int64_t id ) override { return _base.remove_object( id ); } 601 | private: 602 | BaseIndex& _base; 603 | }; 604 | 605 | template 606 | class index : public index_impl { 607 | public: 608 | index( IndexType& i ):index_impl( i ){} 609 | }; 610 | 611 | 612 | class read_write_mutex_manager 613 | { 614 | public: 615 | read_write_mutex_manager() 616 | { 617 | _current_lock = 0; 618 | } 619 | 620 | ~read_write_mutex_manager(){} 621 | 622 | void next_lock() 623 | { 624 | _current_lock++; 625 | new( &_locks[ _current_lock % CHAINBASE_NUM_RW_LOCKS ] ) read_write_mutex(); 626 | } 627 | 628 | read_write_mutex& current_lock() 629 | { 630 | return _locks[ _current_lock % CHAINBASE_NUM_RW_LOCKS ]; 631 | } 632 | 633 | uint32_t current_lock_num() 634 | { 635 | return _current_lock; 636 | } 637 | 638 | private: 639 | std::array< read_write_mutex, CHAINBASE_NUM_RW_LOCKS > _locks; 640 | std::atomic< uint32_t > _current_lock; 641 | }; 642 | 643 | 644 | /** 645 | * This class 646 | */ 647 | class database 648 | { 649 | public: 650 | enum open_flags { 651 | read_only = 0, 652 | read_write = 1 653 | }; 654 | 655 | void open( const bfs::path& dir, uint32_t write = read_only, uint64_t shared_file_size = 0 ); 656 | bool is_open()const; 657 | void close(); 658 | void flush(); 659 | void wipe( const bfs::path& dir ); 660 | void set_require_locking( bool enable_require_locking ); 661 | 662 | #ifdef CHAINBASE_CHECK_LOCKING 663 | void require_lock_fail( const char* method, const char* lock_type, const char* tname )const; 664 | 665 | void require_read_lock( const char* method, const char* tname )const 666 | { 667 | if( BOOST_UNLIKELY( _enable_require_locking & _read_only & (_read_lock_count <= 0) ) ) 668 | require_lock_fail(method, "read", tname); 669 | } 670 | 671 | void require_write_lock( const char* method, const char* tname ) 672 | { 673 | if( BOOST_UNLIKELY( _enable_require_locking & (_write_lock_count <= 0) ) ) 674 | require_lock_fail(method, "write", tname); 675 | } 676 | #endif 677 | 678 | struct session { 679 | public: 680 | session( session&& s ):_index_sessions( std::move(s._index_sessions) ),_revision( s._revision ){} 681 | session( vector>&& s ):_index_sessions( std::move(s) ) 682 | { 683 | if( _index_sessions.size() ) 684 | _revision = _index_sessions[0]->revision(); 685 | } 686 | 687 | ~session() { 688 | undo(); 689 | } 690 | 691 | void push() 692 | { 693 | for( auto& i : _index_sessions ) i->push(); 694 | _index_sessions.clear(); 695 | } 696 | 697 | void squash() 698 | { 699 | for( auto& i : _index_sessions ) i->squash(); 700 | _index_sessions.clear(); 701 | } 702 | 703 | void undo() 704 | { 705 | for( auto& i : _index_sessions ) i->undo(); 706 | _index_sessions.clear(); 707 | } 708 | 709 | int64_t revision()const { return _revision; } 710 | 711 | private: 712 | friend class database; 713 | session(){} 714 | 715 | vector< std::unique_ptr > _index_sessions; 716 | int64_t _revision = -1; 717 | }; 718 | 719 | session start_undo_session( bool enabled ); 720 | 721 | int64_t revision()const { 722 | if( _index_list.size() == 0 ) return -1; 723 | return _index_list[0]->revision(); 724 | } 725 | 726 | void undo(); 727 | void squash(); 728 | void commit( int64_t revision ); 729 | void undo_all(); 730 | 731 | 732 | void set_revision( uint64_t revision ) 733 | { 734 | CHAINBASE_REQUIRE_WRITE_LOCK( "set_revision", uint64_t ); 735 | for( auto i : _index_list ) i->set_revision( revision ); 736 | } 737 | 738 | 739 | template 740 | void add_index() { 741 | const uint16_t type_id = generic_index::value_type::type_id; 742 | typedef generic_index index_type; 743 | typedef typename index_type::allocator_type index_alloc; 744 | 745 | std::string type_name = boost::core::demangle( typeid( typename index_type::value_type ).name() ); 746 | 747 | if( !( _index_map.size() <= type_id || _index_map[ type_id ] == nullptr ) ) { 748 | BOOST_THROW_EXCEPTION( std::logic_error( type_name + "::type_id is already in use" ) ); 749 | } 750 | 751 | index_type* idx_ptr = nullptr; 752 | if( !_read_only ) { 753 | idx_ptr = _segment->find_or_construct< index_type >( type_name.c_str() )( index_alloc( _segment->get_segment_manager() ) ); 754 | } else { 755 | idx_ptr = _segment->find< index_type >( type_name.c_str() ).first; 756 | if( !idx_ptr ) BOOST_THROW_EXCEPTION( std::runtime_error( "unable to find index for " + type_name + " in read only database" ) ); 757 | } 758 | 759 | idx_ptr->validate(); 760 | 761 | if( type_id >= _index_map.size() ) 762 | _index_map.resize( type_id + 1 ); 763 | 764 | auto new_index = new index( *idx_ptr ); 765 | _index_map[ type_id ].reset( new_index ); 766 | _index_list.push_back( new_index ); 767 | } 768 | 769 | auto get_segment_manager() -> decltype( ((bip::managed_mapped_file*)nullptr)->get_segment_manager()) { 770 | return _segment->get_segment_manager(); 771 | } 772 | 773 | size_t get_free_memory()const 774 | { 775 | return _segment->get_segment_manager()->get_free_memory(); 776 | } 777 | 778 | template 779 | const generic_index& get_index()const 780 | { 781 | CHAINBASE_REQUIRE_READ_LOCK("get_index", typename MultiIndexType::value_type); 782 | typedef generic_index index_type; 783 | typedef index_type* index_type_ptr; 784 | assert( _index_map.size() > index_type::value_type::type_id ); 785 | assert( _index_map[index_type::value_type::type_id] ); 786 | return *index_type_ptr( _index_map[index_type::value_type::type_id]->get() ); 787 | } 788 | 789 | template 790 | auto get_index()const -> decltype( ((generic_index*)( nullptr ))->indicies().template get() ) 791 | { 792 | CHAINBASE_REQUIRE_READ_LOCK("get_index", typename MultiIndexType::value_type); 793 | typedef generic_index index_type; 794 | typedef index_type* index_type_ptr; 795 | assert( _index_map.size() > index_type::value_type::type_id ); 796 | assert( _index_map[index_type::value_type::type_id] ); 797 | return index_type_ptr( _index_map[index_type::value_type::type_id]->get() )->indicies().template get(); 798 | } 799 | 800 | template 801 | generic_index& get_mutable_index() 802 | { 803 | CHAINBASE_REQUIRE_WRITE_LOCK("get_mutable_index", typename MultiIndexType::value_type); 804 | typedef generic_index index_type; 805 | typedef index_type* index_type_ptr; 806 | assert( _index_map.size() > index_type::value_type::type_id ); 807 | assert( _index_map[index_type::value_type::type_id] ); 808 | return *index_type_ptr( _index_map[index_type::value_type::type_id]->get() ); 809 | } 810 | 811 | template< typename ObjectType, typename IndexedByType, typename CompatibleKey > 812 | const ObjectType* find( CompatibleKey&& key )const 813 | { 814 | CHAINBASE_REQUIRE_READ_LOCK("find", ObjectType); 815 | typedef typename get_index_type< ObjectType >::type index_type; 816 | const auto& idx = get_index< index_type >().indicies().template get< IndexedByType >(); 817 | auto itr = idx.find( std::forward< CompatibleKey >( key ) ); 818 | if( itr == idx.end() ) return nullptr; 819 | return &*itr; 820 | } 821 | 822 | template< typename ObjectType > 823 | const ObjectType* find( oid< ObjectType > key = oid< ObjectType >() ) const 824 | { 825 | CHAINBASE_REQUIRE_READ_LOCK("find", ObjectType); 826 | typedef typename get_index_type< ObjectType >::type index_type; 827 | const auto& idx = get_index< index_type >().indices(); 828 | auto itr = idx.find( key ); 829 | if( itr == idx.end() ) return nullptr; 830 | return &*itr; 831 | } 832 | 833 | template< typename ObjectType, typename IndexedByType, typename CompatibleKey > 834 | const ObjectType& get( CompatibleKey&& key )const 835 | { 836 | CHAINBASE_REQUIRE_READ_LOCK("get", ObjectType); 837 | auto obj = find< ObjectType, IndexedByType >( std::forward< CompatibleKey >( key ) ); 838 | if( !obj ) BOOST_THROW_EXCEPTION( std::out_of_range( "unknown key" ) ); 839 | return *obj; 840 | } 841 | 842 | template< typename ObjectType > 843 | const ObjectType& get( const oid< ObjectType >& key = oid< ObjectType >() )const 844 | { 845 | CHAINBASE_REQUIRE_READ_LOCK("get", ObjectType); 846 | auto obj = find< ObjectType >( key ); 847 | if( !obj ) BOOST_THROW_EXCEPTION( std::out_of_range( "unknown key") ); 848 | return *obj; 849 | } 850 | 851 | template 852 | void modify( const ObjectType& obj, Modifier&& m ) 853 | { 854 | CHAINBASE_REQUIRE_WRITE_LOCK("modify", ObjectType); 855 | typedef typename get_index_type::type index_type; 856 | get_mutable_index().modify( obj, m ); 857 | } 858 | 859 | template 860 | void remove( const ObjectType& obj ) 861 | { 862 | CHAINBASE_REQUIRE_WRITE_LOCK("remove", ObjectType); 863 | typedef typename get_index_type::type index_type; 864 | return get_mutable_index().remove( obj ); 865 | } 866 | 867 | template 868 | const ObjectType& create( Constructor&& con ) 869 | { 870 | CHAINBASE_REQUIRE_WRITE_LOCK("create", ObjectType); 871 | typedef typename get_index_type::type index_type; 872 | return get_mutable_index().emplace( std::forward(con) ); 873 | } 874 | 875 | template< typename Lambda > 876 | auto with_read_lock( Lambda&& callback, uint64_t wait_micro = 1000000 ) -> decltype( (*(Lambda*)nullptr)() ) 877 | { 878 | read_lock lock( _rw_manager->current_lock(), bip::defer_lock_type() ); 879 | #ifdef CHAINBASE_CHECK_LOCKING 880 | BOOST_ATTRIBUTE_UNUSED 881 | int_incrementer ii( _read_lock_count ); 882 | #endif 883 | 884 | if( !wait_micro ) 885 | { 886 | lock.lock(); 887 | } 888 | else 889 | { 890 | 891 | if( !lock.timed_lock( boost::posix_time::microsec_clock::local_time() + boost::posix_time::microseconds( wait_micro ) ) ) 892 | BOOST_THROW_EXCEPTION( std::runtime_error( "unable to acquire lock" ) ); 893 | } 894 | 895 | return callback(); 896 | } 897 | 898 | template< typename Lambda > 899 | auto with_write_lock( Lambda&& callback, uint64_t wait_micro = 1000000 ) -> decltype( (*(Lambda*)nullptr)() ) 900 | { 901 | if( _read_only ) 902 | BOOST_THROW_EXCEPTION( std::logic_error( "cannot acquire write lock on read-only process" ) ); 903 | 904 | write_lock lock( _rw_manager->current_lock(), boost::defer_lock_t() ); 905 | #ifdef CHAINBASE_CHECK_LOCKING 906 | BOOST_ATTRIBUTE_UNUSED 907 | int_incrementer ii( _write_lock_count ); 908 | #endif 909 | 910 | if( !wait_micro ) 911 | { 912 | lock.lock(); 913 | } 914 | else 915 | { 916 | while( !lock.timed_lock( boost::posix_time::microsec_clock::local_time() + boost::posix_time::microseconds( wait_micro ) ) ) 917 | { 918 | _rw_manager->next_lock(); 919 | std::cerr << "Lock timeout, moving to lock " << _rw_manager->current_lock_num() << std::endl; 920 | lock = write_lock( _rw_manager->current_lock(), boost::defer_lock_t() ); 921 | } 922 | } 923 | 924 | return callback(); 925 | } 926 | 927 | private: 928 | unique_ptr _segment; 929 | unique_ptr _meta; 930 | read_write_mutex_manager* _rw_manager = nullptr; 931 | bool _read_only = false; 932 | bip::file_lock _flock; 933 | 934 | /** 935 | * This is a sparse list of known indicies kept to accelerate creation of undo sessions 936 | */ 937 | vector _index_list; 938 | 939 | /** 940 | * This is a full map (size 2^16) of all possible index designed for constant time lookup 941 | */ 942 | vector> _index_map; 943 | 944 | bfs::path _data_dir; 945 | 946 | int32_t _read_lock_count = 0; 947 | int32_t _write_lock_count = 0; 948 | bool _enable_require_locking = false; 949 | }; 950 | 951 | template 952 | using shared_multi_index_container = boost::multi_index_container >; 953 | } // namepsace chainbase 954 | 955 | -------------------------------------------------------------------------------- /src/chainbase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace chainbase { 7 | 8 | struct environment_check { 9 | environment_check() { 10 | memset( &compiler_version, 0, sizeof( compiler_version ) ); 11 | memcpy( &compiler_version, __VERSION__, std::min( strlen(__VERSION__), 256 ) ); 12 | #ifndef NDEBUG 13 | debug = true; 14 | #endif 15 | #ifdef __APPLE__ 16 | apple = true; 17 | #endif 18 | #ifdef WIN32 19 | windows = true; 20 | #endif 21 | } 22 | friend bool operator == ( const environment_check& a, const environment_check& b ) { 23 | return std::make_tuple( a.compiler_version, a.debug, a.apple, a.windows ) 24 | == std::make_tuple( b.compiler_version, b.debug, b.apple, b.windows ); 25 | } 26 | 27 | boost::array compiler_version; 28 | bool debug = false; 29 | bool apple = false; 30 | bool windows = false; 31 | }; 32 | 33 | void database::open( const bfs::path& dir, uint32_t flags, uint64_t shared_file_size ) { 34 | 35 | bool write = flags & database::read_write; 36 | 37 | if( !bfs::exists( dir ) ) { 38 | if( !write ) BOOST_THROW_EXCEPTION( std::runtime_error( "database file not found at " + dir.native() ) ); 39 | } 40 | 41 | bfs::create_directories( dir ); 42 | if( _data_dir != dir ) close(); 43 | 44 | _data_dir = dir; 45 | auto abs_path = bfs::absolute( dir / "shared_memory.bin" ); 46 | 47 | if( bfs::exists( abs_path ) ) 48 | { 49 | if( write ) 50 | { 51 | auto existing_file_size = bfs::file_size( abs_path ); 52 | if( shared_file_size > existing_file_size ) 53 | { 54 | if( !bip::managed_mapped_file::grow( abs_path.generic_string().c_str(), shared_file_size - existing_file_size ) ) 55 | BOOST_THROW_EXCEPTION( std::runtime_error( "could not grow database file to requested size." ) ); 56 | } 57 | 58 | _segment.reset( new bip::managed_mapped_file( bip::open_only, 59 | abs_path.generic_string().c_str() 60 | ) ); 61 | } else { 62 | _segment.reset( new bip::managed_mapped_file( bip::open_read_only, 63 | abs_path.generic_string().c_str() 64 | ) ); 65 | _read_only = true; 66 | } 67 | 68 | auto env = _segment->find< environment_check >( "environment" ); 69 | if( !env.first || !( *env.first == environment_check()) ) { 70 | BOOST_THROW_EXCEPTION( std::runtime_error( "database created by a different compiler, build, or operating system" ) ); 71 | } 72 | } else { 73 | _segment.reset( new bip::managed_mapped_file( bip::create_only, 74 | abs_path.generic_string().c_str(), shared_file_size 75 | ) ); 76 | _segment->find_or_construct< environment_check >( "environment" )(); 77 | } 78 | 79 | 80 | abs_path = bfs::absolute( dir / "shared_memory.meta" ); 81 | 82 | if( bfs::exists( abs_path ) ) 83 | { 84 | _meta.reset( new bip::managed_mapped_file( bip::open_only, abs_path.generic_string().c_str() 85 | ) ); 86 | 87 | _rw_manager = _meta->find< read_write_mutex_manager >( "rw_manager" ).first; 88 | if( !_rw_manager ) 89 | BOOST_THROW_EXCEPTION( std::runtime_error( "could not find read write lock manager" ) ); 90 | } 91 | else 92 | { 93 | _meta.reset( new bip::managed_mapped_file( bip::create_only, 94 | abs_path.generic_string().c_str(), sizeof( read_write_mutex_manager ) * 2 95 | ) ); 96 | 97 | _rw_manager = _meta->find_or_construct< read_write_mutex_manager >( "rw_manager" )(); 98 | } 99 | 100 | if( write ) 101 | { 102 | _flock = bip::file_lock( abs_path.generic_string().c_str() ); 103 | if( !_flock.try_lock() ) 104 | BOOST_THROW_EXCEPTION( std::runtime_error( "could not gain write access to the shared memory file" ) ); 105 | } 106 | } 107 | 108 | bool database::is_open() const 109 | { 110 | return _segment && _meta && !_data_dir.empty(); 111 | } 112 | 113 | void database::flush() { 114 | if( _segment ) 115 | _segment->flush(); 116 | if( _meta ) 117 | _meta->flush(); 118 | } 119 | 120 | void database::close() 121 | { 122 | _segment.reset(); 123 | _meta.reset(); 124 | _index_list.clear(); 125 | _index_map.clear(); 126 | _data_dir = bfs::path(); 127 | } 128 | 129 | void database::wipe( const bfs::path& dir ) 130 | { 131 | _segment.reset(); 132 | _meta.reset(); 133 | bfs::remove_all( dir / "shared_memory.bin" ); 134 | bfs::remove_all( dir / "shared_memory.meta" ); 135 | _data_dir = bfs::path(); 136 | _index_list.clear(); 137 | _index_map.clear(); 138 | } 139 | 140 | void database::set_require_locking( bool enable_require_locking ) 141 | { 142 | #ifdef CHAINBASE_CHECK_LOCKING 143 | _enable_require_locking = enable_require_locking; 144 | #endif 145 | } 146 | 147 | #ifdef CHAINBASE_CHECK_LOCKING 148 | void database::require_lock_fail( const char* method, const char* lock_type, const char* tname )const 149 | { 150 | std::string err_msg = "database::" + std::string( method ) + " require_" + std::string( lock_type ) + "_lock() failed on type " + std::string( tname ); 151 | std::cerr << err_msg << std::endl; 152 | BOOST_THROW_EXCEPTION( std::runtime_error( err_msg ) ); 153 | } 154 | #endif 155 | 156 | void database::undo() 157 | { 158 | for( auto& item : _index_list ) 159 | { 160 | item->undo(); 161 | } 162 | } 163 | 164 | void database::squash() 165 | { 166 | for( auto& item : _index_list ) 167 | { 168 | item->squash(); 169 | } 170 | } 171 | 172 | void database::commit( int64_t revision ) 173 | { 174 | for( auto& item : _index_list ) 175 | { 176 | item->commit( revision ); 177 | } 178 | } 179 | 180 | void database::undo_all() 181 | { 182 | for( auto& item : _index_list ) 183 | { 184 | item->undo_all(); 185 | } 186 | } 187 | 188 | database::session database::start_undo_session( bool enabled ) 189 | { 190 | if( enabled ) { 191 | vector< std::unique_ptr > _sub_sessions; 192 | _sub_sessions.reserve( _index_list.size() ); 193 | for( auto& item : _index_list ) { 194 | _sub_sessions.push_back( item->start_undo_session( enabled ) ); 195 | } 196 | return session( std::move( _sub_sessions ) ); 197 | } else { 198 | return session(); 199 | } 200 | } 201 | 202 | } // namespace chainbase 203 | 204 | 205 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB UNIT_TESTS "*.cpp") 2 | add_executable( chainbase_test ${UNIT_TESTS} ) 3 | target_link_libraries( chainbase_test chainbase ${PLATFORM_SPECIFIC_LIBS} ) 4 | 5 | -------------------------------------------------------------------------------- /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_non_unique< BOOST_MULTI_INDEX_MEMBER(book,int,a) >, 34 | ordered_non_unique< BOOST_MULTI_INDEX_MEMBER(book,int,b) > 35 | >, 36 | chainbase::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::unique_path(); 44 | try { 45 | std::cerr << temp.native() << " \n"; 46 | 47 | chainbase::database db; 48 | BOOST_CHECK_THROW( db.open( temp ), std::runtime_error ); /// temp does not exist 49 | 50 | db.open( temp, database::read_write, 1024*1024*8 ); 51 | 52 | chainbase::database db2; /// open an already created db 53 | db2.open( temp ); 54 | BOOST_CHECK_THROW( db2.add_index< book_index >(), std::runtime_error ); /// index does not exist in read only database 55 | 56 | db.add_index< book_index >(); 57 | BOOST_CHECK_THROW( db.add_index(), std::logic_error ); /// cannot add same index twice 58 | 59 | 60 | db2.add_index< book_index >(); /// index should exist now 61 | 62 | 63 | BOOST_TEST_MESSAGE( "Creating book" ); 64 | const auto& new_book = db.create( []( book& b ) { 65 | b.a = 3; 66 | b.b = 4; 67 | } ); 68 | const auto& copy_new_book = db2.get( book::id_type(0) ); 69 | BOOST_REQUIRE( &new_book != ©_new_book ); ///< these are mapped to different address ranges 70 | 71 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 72 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 73 | 74 | db.modify( new_book, [&]( book& b ) { 75 | b.a = 5; 76 | b.b = 6; 77 | }); 78 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 79 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 80 | 81 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 82 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 83 | 84 | { 85 | auto session = db.start_undo_session(true); 86 | db.modify( new_book, [&]( book& b ) { 87 | b.a = 7; 88 | b.b = 8; 89 | }); 90 | 91 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 92 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 93 | } 94 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 95 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 96 | 97 | { 98 | auto session = db.start_undo_session(true); 99 | const auto& book2 = db.create( [&]( book& b ) { 100 | b.a = 9; 101 | b.b = 10; 102 | }); 103 | 104 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 105 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 106 | BOOST_REQUIRE_EQUAL( book2.a, 9 ); 107 | BOOST_REQUIRE_EQUAL( book2.b, 10 ); 108 | } 109 | BOOST_CHECK_THROW( db2.get( book::id_type(1) ), std::out_of_range ); 110 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 111 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 112 | 113 | 114 | { 115 | auto session = db.start_undo_session(true); 116 | db.modify( new_book, [&]( book& b ) { 117 | b.a = 7; 118 | b.b = 8; 119 | }); 120 | 121 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 122 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 123 | session.push(); 124 | } 125 | BOOST_REQUIRE_EQUAL( new_book.a, 7 ); 126 | BOOST_REQUIRE_EQUAL( new_book.b, 8 ); 127 | db.undo(); 128 | BOOST_REQUIRE_EQUAL( new_book.a, 5 ); 129 | BOOST_REQUIRE_EQUAL( new_book.b, 6 ); 130 | 131 | BOOST_REQUIRE_EQUAL( new_book.a, copy_new_book.a ); 132 | BOOST_REQUIRE_EQUAL( new_book.b, copy_new_book.b ); 133 | } catch ( ... ) { 134 | bfs::remove_all( temp ); 135 | throw; 136 | } 137 | } 138 | 139 | // BOOST_AUTO_TEST_SUITE_END() 140 | --------------------------------------------------------------------------------